mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
AAE-23950 Apply reactive forms for date&time and date widgets (#9852)
* AAE-19102 Date should be formatted correctly on the task form considering specified display format * validation fix * rebase * update validation * remove duplicated error widgets * changes after rebase * unit tests update * implement suggestions * test link-adf change * remove link-adf change * fix error for provide value with different format than specified * implement suggestion * fix timezone issue for min/max dates * fix another format issue * exported default date format
This commit is contained in:
@@ -20,7 +20,7 @@ import { DateFnsUtils } from './date-fns-utils';
|
|||||||
import { Inject, Injectable, Optional } from '@angular/core';
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
import { MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatDateFormats } from '@angular/material/core';
|
import { MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatDateFormats } from '@angular/material/core';
|
||||||
import { UserPreferenceValues, UserPreferencesService } from '../services/user-preferences.service';
|
import { UserPreferenceValues, UserPreferencesService } from '../services/user-preferences.service';
|
||||||
import { Locale } from 'date-fns';
|
import { isValid, Locale, parse } from 'date-fns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date-fns adapter with moment-to-date-fns conversion.
|
* Date-fns adapter with moment-to-date-fns conversion.
|
||||||
@@ -47,15 +47,17 @@ import { Locale } from 'date-fns';
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export const DEFAULT_DATE_FORMAT = 'dd-MM-yyyy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Material date formats for Date-fns
|
* Material date formats for Date-fns
|
||||||
*/
|
*/
|
||||||
export const ADF_DATE_FORMATS: MatDateFormats = {
|
export const ADF_DATE_FORMATS: MatDateFormats = {
|
||||||
parse: {
|
parse: {
|
||||||
dateInput: 'dd-MM-yyyy'
|
dateInput: DEFAULT_DATE_FORMAT
|
||||||
},
|
},
|
||||||
display: {
|
display: {
|
||||||
dateInput: 'dd-MM-yyyy',
|
dateInput: DEFAULT_DATE_FORMAT,
|
||||||
monthLabel: 'LLL',
|
monthLabel: 'LLL',
|
||||||
monthYearLabel: 'LLL uuuu',
|
monthYearLabel: 'LLL uuuu',
|
||||||
dateA11yLabel: 'PP',
|
dateA11yLabel: 'PP',
|
||||||
@@ -88,10 +90,11 @@ export class AdfDateFnsAdapter extends DateFnsAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override parse(value: any, parseFormat: string | string[]): Date {
|
override parse(value: any, parseFormat: string | string[]): Date {
|
||||||
|
const dateValue = this.isValid(value) ? value : this.parseAndValidateDate(value);
|
||||||
const format = Array.isArray(parseFormat)
|
const format = Array.isArray(parseFormat)
|
||||||
? parseFormat.map(DateFnsUtils.convertMomentToDateFnsFormat)
|
? parseFormat.map(DateFnsUtils.convertMomentToDateFnsFormat)
|
||||||
: DateFnsUtils.convertMomentToDateFnsFormat(parseFormat);
|
: DateFnsUtils.convertMomentToDateFnsFormat(parseFormat);
|
||||||
return super.parse(value, format);
|
return super.parse(dateValue, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
override format(date: Date, displayFormat: string): string {
|
override format(date: Date, displayFormat: string): string {
|
||||||
@@ -103,4 +106,9 @@ export class AdfDateFnsAdapter extends DateFnsAdapter {
|
|||||||
|
|
||||||
return super.format(date, displayFormat);
|
return super.format(date, displayFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private parseAndValidateDate(value: any): Date {
|
||||||
|
const parsedDate = parse(value, this.displayFormat || DEFAULT_DATE_FORMAT, new Date());
|
||||||
|
return isValid(parsedDate) ? parsedDate : value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,7 @@ import { Inject, Injectable, Optional } from '@angular/core';
|
|||||||
import { DateFnsUtils } from './date-fns-utils';
|
import { DateFnsUtils } from './date-fns-utils';
|
||||||
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimeFormats } from '@mat-datetimepicker/core';
|
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimeFormats } from '@mat-datetimepicker/core';
|
||||||
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
import { Locale, addHours, addMinutes } from 'date-fns';
|
import { Locale, addHours, addMinutes, isValid, parse } from 'date-fns';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Material date/time formats for Date-fns (mat-datetimepicker)
|
* Material date/time formats for Date-fns (mat-datetimepicker)
|
||||||
@@ -126,7 +126,8 @@ export class AdfDateTimeFnsAdapter extends DatetimeAdapter<Date> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override parse(value: any, parseFormat: any): Date {
|
override parse(value: any, parseFormat: any): Date {
|
||||||
return this._delegate.parse(value, parseFormat);
|
const dateToParse = isValid(new Date(value)) ? parse(value, this.displayFormat, new Date()) : value;
|
||||||
|
return this._delegate.parse(dateToParse, parseFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
override format(date: Date, displayFormat: any): string {
|
override format(date: Date, displayFormat: any): string {
|
||||||
|
@@ -110,7 +110,7 @@ describe('Form Renderer Component', () => {
|
|||||||
expectElementToBeHidden(displayTextElementContainer);
|
expectElementToBeHidden(displayTextElementContainer);
|
||||||
|
|
||||||
inputDateTestOne.value = '2019-11-19';
|
inputDateTestOne.value = '2019-11-19';
|
||||||
inputDateTestOne.dispatchEvent(new Event('change'));
|
inputDateTestOne.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
@@ -130,7 +130,7 @@ describe('Form Renderer Component', () => {
|
|||||||
expectElementToBeVisible(displayTextElementContainer);
|
expectElementToBeVisible(displayTextElementContainer);
|
||||||
|
|
||||||
inputDateTestOne.value = '2019-11-19';
|
inputDateTestOne.value = '2019-11-19';
|
||||||
inputDateTestOne.dispatchEvent(new Event('change'));
|
inputDateTestOne.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
@@ -1552,7 +1552,7 @@ export const formDateVisibility = {
|
|||||||
2: [
|
2: [
|
||||||
{
|
{
|
||||||
id: 'Text0pqd1u',
|
id: 'Text0pqd1u',
|
||||||
name: 'Text',
|
name: 'Text equal specific date',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
required: false,
|
required: false,
|
||||||
@@ -1589,7 +1589,7 @@ export const formDateVisibility = {
|
|||||||
1: [
|
1: [
|
||||||
{
|
{
|
||||||
id: 'Text0uyqd3',
|
id: 'Text0uyqd3',
|
||||||
name: 'Text',
|
name: 'Text NOT equal specific date',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
required: false,
|
required: false,
|
||||||
|
@@ -23,7 +23,7 @@ export class ErrorMessageModel {
|
|||||||
|
|
||||||
constructor(obj?: any) {
|
constructor(obj?: any) {
|
||||||
this.message = obj?.message || '';
|
this.message = obj?.message || '';
|
||||||
this.attributes = new Map();
|
this.attributes = obj?.attributes || new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
isActive(): boolean {
|
isActive(): boolean {
|
||||||
@@ -31,7 +31,7 @@ export class ErrorMessageModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAttributesAsJsonObj() {
|
getAttributesAsJsonObj() {
|
||||||
let result = {};
|
const result = {};
|
||||||
if (this.attributes.size > 0) {
|
if (this.attributes.size > 0) {
|
||||||
this.attributes.forEach((value, key) => {
|
this.attributes.forEach((value, key) => {
|
||||||
result[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
result[key] = typeof value === 'string' ? value : JSON.stringify(value);
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable @angular-eslint/component-selector */
|
/* eslint-disable @angular-eslint/component-selector */
|
||||||
|
|
||||||
export class FormFieldTypes {
|
export class FormFieldTypes {
|
||||||
static CONTAINER: string = 'container';
|
static CONTAINER: string = 'container';
|
||||||
@@ -50,20 +50,13 @@ export class FormFieldTypes {
|
|||||||
static DATA_TABLE: string = 'data-table';
|
static DATA_TABLE: string = 'data-table';
|
||||||
static DISPLAY_EXTERNAL_PROPERTY: string = 'display-external-property';
|
static DISPLAY_EXTERNAL_PROPERTY: string = 'display-external-property';
|
||||||
|
|
||||||
static READONLY_TYPES: string[] = [
|
static READONLY_TYPES: string[] = [FormFieldTypes.HYPERLINK, FormFieldTypes.DISPLAY_VALUE, FormFieldTypes.READONLY_TEXT, FormFieldTypes.GROUP];
|
||||||
FormFieldTypes.HYPERLINK,
|
|
||||||
FormFieldTypes.DISPLAY_VALUE,
|
|
||||||
FormFieldTypes.READONLY_TEXT,
|
|
||||||
FormFieldTypes.GROUP
|
|
||||||
];
|
|
||||||
|
|
||||||
static VALIDATABLE_TYPES: string[] = [
|
static VALIDATABLE_TYPES: string[] = [FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY];
|
||||||
FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
|
|
||||||
];
|
|
||||||
|
|
||||||
static CONSTANT_VALUE_TYPES: string[] = [
|
static REACTIVE_TYPES: string[] = [FormFieldTypes.DATE, FormFieldTypes.DATETIME];
|
||||||
FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
|
|
||||||
];
|
static CONSTANT_VALUE_TYPES: string[] = [FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY];
|
||||||
|
|
||||||
static isReadOnlyType(type: string) {
|
static isReadOnlyType(type: string) {
|
||||||
return FormFieldTypes.READONLY_TYPES.includes(type);
|
return FormFieldTypes.READONLY_TYPES.includes(type);
|
||||||
@@ -73,6 +66,10 @@ export class FormFieldTypes {
|
|||||||
return FormFieldTypes.VALIDATABLE_TYPES.includes(type);
|
return FormFieldTypes.VALIDATABLE_TYPES.includes(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static isReactiveType(type: string): boolean {
|
||||||
|
return FormFieldTypes.REACTIVE_TYPES.includes(type);
|
||||||
|
}
|
||||||
|
|
||||||
static isConstantValueType(type: string) {
|
static isConstantValueType(type: string) {
|
||||||
return FormFieldTypes.CONSTANT_VALUE_TYPES.includes(type);
|
return FormFieldTypes.CONSTANT_VALUE_TYPES.includes(type);
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { ErrorMessageModel } from './error-message.model';
|
import { ErrorMessageModel } from './error-message.model';
|
||||||
import { FormFieldOption } from './form-field-option';
|
|
||||||
import { FormFieldTypes } from './form-field-types';
|
import { FormFieldTypes } from './form-field-types';
|
||||||
import {
|
import {
|
||||||
FixedValueFieldValidator,
|
FixedValueFieldValidator,
|
||||||
@@ -27,11 +26,6 @@ import {
|
|||||||
NumberFieldValidator,
|
NumberFieldValidator,
|
||||||
RegExFieldValidator,
|
RegExFieldValidator,
|
||||||
RequiredFieldValidator,
|
RequiredFieldValidator,
|
||||||
MaxDateTimeFieldValidator,
|
|
||||||
MinDateTimeFieldValidator,
|
|
||||||
MaxDateFieldValidator,
|
|
||||||
MinDateFieldValidator,
|
|
||||||
DateTimeFieldValidator,
|
|
||||||
DecimalFieldValidator
|
DecimalFieldValidator
|
||||||
} from './form-field-validator';
|
} from './form-field-validator';
|
||||||
import { FormFieldModel } from './form-field.model';
|
import { FormFieldModel } from './form-field.model';
|
||||||
@@ -65,22 +59,6 @@ describe('FormFieldValidator', () => {
|
|||||||
expect(validator.validate(field)).toBe(true);
|
expect(validator.validate(field)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail (display error) for dropdown with empty value', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DROPDOWN,
|
|
||||||
value: '<empty>',
|
|
||||||
options: [{ id: 'empty', name: 'Choose option...' }],
|
|
||||||
hasEmptyValue: true,
|
|
||||||
required: true
|
|
||||||
});
|
|
||||||
|
|
||||||
field.emptyOption = { id: '<empty>' } as FormFieldOption;
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
|
|
||||||
field.value = '<non-empty>';
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) for multiple type dropdown with zero selection', () => {
|
it('should fail (display error) for multiple type dropdown with zero selection', () => {
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
const field = new FormFieldModel(new FormModel(), {
|
||||||
type: FormFieldTypes.DROPDOWN,
|
type: FormFieldTypes.DROPDOWN,
|
||||||
@@ -196,20 +174,6 @@ describe('FormFieldValidator', () => {
|
|||||||
expect(validator.validate(field)).toBe(true);
|
expect(validator.validate(field)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail (display error) for date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: null,
|
|
||||||
required: true
|
|
||||||
});
|
|
||||||
|
|
||||||
field.value = null;
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
|
|
||||||
field.value = '';
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for text', () => {
|
it('should succeed for text', () => {
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
const field = new FormFieldModel(new FormModel(), {
|
||||||
type: FormFieldTypes.TEXT,
|
type: FormFieldTypes.TEXT,
|
||||||
@@ -664,466 +628,6 @@ describe('FormFieldValidator', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('MaxDateTimeFieldValidator', () => {
|
|
||||||
let validator: MaxDateTimeFieldValidator;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validator = new MaxDateTimeFieldValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require maxValue defined', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME
|
|
||||||
});
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
|
|
||||||
field.maxValue = '9999-02-08 10:10 AM';
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support date time widgets only', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
maxValue: '9999-02-08 10:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
|
|
||||||
field.type = FormFieldTypes.TEXT;
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow empty values', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: null,
|
|
||||||
maxValue: '9999-02-08 10:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for unsupported types', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.TEXT
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take into account that max value is in UTC and NOT fail (display error) validating value checking the time', () => {
|
|
||||||
const localValidValue = '2018-03-30T22:59:00.000Z';
|
|
||||||
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: localValidValue,
|
|
||||||
maxValue: '2018-03-31T23:00:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take into account that max value is in UTC and fail (display error) validating value checking the time', () => {
|
|
||||||
const localInvalidValue = '2018-03-30T23:01:00.000Z';
|
|
||||||
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: localInvalidValue,
|
|
||||||
maxValue: `2018-03-30T23:00:00.000Z`
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value checking the time', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9999-02-08T09:10:00.000Z',
|
|
||||||
maxValue: '9999-02-08T10:10:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value checking the time', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9999-02-08T11:10:00.000Z',
|
|
||||||
maxValue: '9999-02-08T10:10:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9999-02-08T09:10:00.000Z',
|
|
||||||
maxValue: '9999-02-08T10:10:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '08-02-9999 12:10 AM',
|
|
||||||
maxValue: '9999-02-07 10:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('MinDateTimeFieldValidator', () => {
|
|
||||||
let validator: MinDateTimeFieldValidator;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validator = new MinDateTimeFieldValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require minValue defined', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME
|
|
||||||
});
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
|
|
||||||
field.minValue = '9999-02-08 09:10 AM';
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support date time widgets only', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
minValue: '9999-02-08 09:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
|
|
||||||
field.type = FormFieldTypes.TEXT;
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow empty values', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: null,
|
|
||||||
minValue: '9999-02-08 09:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for unsupported types', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.TEXT
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take into account that min value is in UTC and NOT fail (display error) validating value checking the time', () => {
|
|
||||||
const localValidValue = '2018-03-02T06:01:00.000Z';
|
|
||||||
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: localValidValue,
|
|
||||||
minValue: '2018-03-02T06:00:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should take into account that min value is in UTC and fail (display error) validating value checking the time', () => {
|
|
||||||
const localInvalidValue = '2018-3-02 05:59 AM';
|
|
||||||
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: localInvalidValue,
|
|
||||||
minValue: '2018-03-02T06:00:00+00:00'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value by time', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '08-02-9999 09:10 AM',
|
|
||||||
minValue: '9999-02-08 09:00 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value by date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '09-02-9999 09:10 AM',
|
|
||||||
minValue: '9999-02-08 09:10 AM'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value by time', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9999-08-02T08:10:00.000Z',
|
|
||||||
minValue: '9999-08-02T08:11:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value by date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9999-02-07T09:10:00.000Z',
|
|
||||||
minValue: '9999-02-08T09:10:00.000Z'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('MaxDateFieldValidator', () => {
|
|
||||||
let validator: MaxDateFieldValidator;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validator = new MaxDateFieldValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require maxValue defined', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE
|
|
||||||
});
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
|
|
||||||
field.maxValue = '9999-02-08';
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support date widgets only', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
maxValue: '9999-02-08'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
|
|
||||||
field.type = FormFieldTypes.TEXT;
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow empty values', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: null,
|
|
||||||
maxValue: '9999-02-08'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for unsupported types', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.TEXT
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
maxValue: '9999-02-09'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
maxValue: '9999-02-07'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate with APS1 format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
maxValue: '09-02-9999'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating with APS1 format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
maxValue: '07-02-9999'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('MinDateFieldValidator', () => {
|
|
||||||
let validator: MinDateFieldValidator;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validator = new MinDateFieldValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should require maxValue defined', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE
|
|
||||||
});
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
|
|
||||||
field.minValue = '9999-02-08';
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should support date widgets only', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
minValue: '9999-02-08'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.isSupported(field)).toBe(true);
|
|
||||||
|
|
||||||
field.type = FormFieldTypes.TEXT;
|
|
||||||
expect(validator.isSupported(field)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should allow empty values', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: null,
|
|
||||||
minValue: '9999-02-08'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed for unsupported types', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.TEXT
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should succeed validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
minValue: '9999-02-07'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating value checking the date', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
minValue: '9999-02-09'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate with APS1 format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
minValue: '07-02-9999'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail (display error) validating with APS1 format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATE,
|
|
||||||
value: '9999-02-08T00:00:00',
|
|
||||||
minValue: '09-02-9999'
|
|
||||||
});
|
|
||||||
|
|
||||||
field.validationSummary = new ErrorMessageModel();
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
expect(field.validationSummary).not.toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DateTimeFieldValidator', () => {
|
|
||||||
let validator: DateTimeFieldValidator;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
validator = new DateTimeFieldValidator();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate dateTime format with dateDisplayFormat', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '2021-06-09 14:10',
|
|
||||||
dateDisplayFormat: 'YYYY-MM-DD HH:mm'
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should validate dateTime format with default format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '9-6-2021 11:10 AM'
|
|
||||||
});
|
|
||||||
expect(field.value).toBe('9-6-2021 11:10 AM');
|
|
||||||
expect(field.dateDisplayFormat).toBe('D-M-YYYY hh:mm A');
|
|
||||||
expect(validator.validate(field)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not validate dateTime format with default format', () => {
|
|
||||||
const field = new FormFieldModel(new FormModel(), {
|
|
||||||
type: FormFieldTypes.DATETIME,
|
|
||||||
value: '2021-06-09 14:10 AM' // 14:10 does not conform to A
|
|
||||||
});
|
|
||||||
expect(field.value).toBe('2021-06-09 14:10 AM');
|
|
||||||
expect(field.dateDisplayFormat).toBe('D-M-YYYY hh:mm A');
|
|
||||||
expect(validator.validate(field)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DecimalFieldValidator', () => {
|
describe('DecimalFieldValidator', () => {
|
||||||
let decimalValidator: DecimalFieldValidator;
|
let decimalValidator: DecimalFieldValidator;
|
||||||
|
|
||||||
|
@@ -20,8 +20,6 @@
|
|||||||
import { FormFieldTypes } from './form-field-types';
|
import { FormFieldTypes } from './form-field-types';
|
||||||
import { isNumberValue } from './form-field-utils';
|
import { isNumberValue } from './form-field-utils';
|
||||||
import { FormFieldModel } from './form-field.model';
|
import { FormFieldModel } from './form-field.model';
|
||||||
import { DateFnsUtils } from '../../../../common/utils/date-fns-utils';
|
|
||||||
import { isValid as isDateValid, isBefore, isAfter } from 'date-fns';
|
|
||||||
|
|
||||||
export interface FormFieldValidator {
|
export interface FormFieldValidator {
|
||||||
isSupported(field: FormFieldModel): boolean;
|
isSupported(field: FormFieldModel): boolean;
|
||||||
@@ -42,8 +40,6 @@ export class RequiredFieldValidator implements FormFieldValidator {
|
|||||||
FormFieldTypes.UPLOAD,
|
FormFieldTypes.UPLOAD,
|
||||||
FormFieldTypes.AMOUNT,
|
FormFieldTypes.AMOUNT,
|
||||||
FormFieldTypes.DYNAMIC_TABLE,
|
FormFieldTypes.DYNAMIC_TABLE,
|
||||||
FormFieldTypes.DATE,
|
|
||||||
FormFieldTypes.DATETIME,
|
|
||||||
FormFieldTypes.ATTACH_FOLDER,
|
FormFieldTypes.ATTACH_FOLDER,
|
||||||
FormFieldTypes.DECIMAL,
|
FormFieldTypes.DECIMAL,
|
||||||
FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
|
FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
|
||||||
@@ -127,208 +123,6 @@ export class NumberFieldValidator implements FormFieldValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DateFieldValidator implements FormFieldValidator {
|
|
||||||
private supportedTypes = [FormFieldTypes.DATE];
|
|
||||||
|
|
||||||
// Validates that the input string is a valid date formatted as <dateFormat> (default D-M-YYYY)
|
|
||||||
static isValidDate(inputDate: string, dateFormat: string = 'D-M-YYYY'): boolean {
|
|
||||||
return DateFnsUtils.isValidDate(inputDate, dateFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
isSupported(field: FormFieldModel): boolean {
|
|
||||||
return field && this.supportedTypes.indexOf(field.type) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
validate(field: FormFieldModel): boolean {
|
|
||||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
|
||||||
if (DateFieldValidator.isValidDate(field.value, field.dateDisplayFormat)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
field.validationSummary.message = field.dateDisplayFormat;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DateTimeFieldValidator implements FormFieldValidator {
|
|
||||||
private supportedTypes = [FormFieldTypes.DATETIME];
|
|
||||||
|
|
||||||
isSupported(field: FormFieldModel): boolean {
|
|
||||||
return field && this.supportedTypes.indexOf(field.type) > -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isValidDateTime(input: string): boolean {
|
|
||||||
const date = DateFnsUtils.getDate(input);
|
|
||||||
return isDateValid(date);
|
|
||||||
}
|
|
||||||
|
|
||||||
validate(field: FormFieldModel): boolean {
|
|
||||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
|
||||||
if (DateTimeFieldValidator.isValidDateTime(field.value)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
field.validationSummary.message = field.dateDisplayFormat;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class BoundaryDateFieldValidator implements FormFieldValidator {
|
|
||||||
DATE_FORMAT_CLOUD = 'YYYY-MM-DD';
|
|
||||||
DATE_FORMAT = 'DD-MM-YYYY';
|
|
||||||
|
|
||||||
supportedTypes = [FormFieldTypes.DATE];
|
|
||||||
|
|
||||||
validate(field: FormFieldModel): boolean {
|
|
||||||
let isValid = true;
|
|
||||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
|
||||||
const dateFormat = field.dateDisplayFormat;
|
|
||||||
|
|
||||||
if (!DateFieldValidator.isValidDate(field.value, dateFormat)) {
|
|
||||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE';
|
|
||||||
isValid = false;
|
|
||||||
} else {
|
|
||||||
isValid = this.checkDate(field, dateFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
extractDateFormat(date: string): string {
|
|
||||||
const brokenDownDate = date.split('-');
|
|
||||||
return brokenDownDate[0].length === 4 ? this.DATE_FORMAT_CLOUD : this.DATE_FORMAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract checkDate(field: FormFieldModel, dateFormat: string);
|
|
||||||
abstract isSupported(field: FormFieldModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MinDateFieldValidator extends BoundaryDateFieldValidator {
|
|
||||||
checkDate(field: FormFieldModel, dateFormat: string): boolean {
|
|
||||||
let isValid = true;
|
|
||||||
const fieldValueData = DateFnsUtils.parseDate(field.value, dateFormat, { dateOnly: true });
|
|
||||||
const minValueDateFormat = this.extractDateFormat(field.minValue);
|
|
||||||
const min = DateFnsUtils.parseDate(field.minValue, minValueDateFormat);
|
|
||||||
|
|
||||||
if (DateFnsUtils.isBeforeDate(fieldValueData, min)) {
|
|
||||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
|
||||||
field.validationSummary.attributes.set('minValue', DateFnsUtils.formatDate(min, field.dateDisplayFormat).toLocaleUpperCase());
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSupported(field: FormFieldModel): boolean {
|
|
||||||
return field && this.supportedTypes.indexOf(field.type) > -1 && !!field.minValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MaxDateFieldValidator extends BoundaryDateFieldValidator {
|
|
||||||
checkDate(field: FormFieldModel, dateFormat: string): boolean {
|
|
||||||
let isValid = true;
|
|
||||||
const fieldValueData = DateFnsUtils.parseDate(field.value, dateFormat, { dateOnly: true });
|
|
||||||
const maxValueDateFormat = this.extractDateFormat(field.maxValue);
|
|
||||||
const max = DateFnsUtils.parseDate(field.maxValue, maxValueDateFormat);
|
|
||||||
|
|
||||||
if (DateFnsUtils.isAfterDate(fieldValueData, max)) {
|
|
||||||
field.validationSummary.message = `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
|
||||||
field.validationSummary.attributes.set('maxValue', DateFnsUtils.formatDate(max, field.dateDisplayFormat).toLocaleUpperCase());
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
isSupported(field: FormFieldModel): boolean {
|
|
||||||
return field && this.supportedTypes.indexOf(field.type) > -1 && !!field.maxValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export abstract class BoundaryDateTimeFieldValidator implements FormFieldValidator {
|
|
||||||
private supportedTypes = [FormFieldTypes.DATETIME];
|
|
||||||
|
|
||||||
isSupported(field: FormFieldModel): boolean {
|
|
||||||
return field && this.supportedTypes.indexOf(field.type) > -1 && !!field[this.getSubjectField()];
|
|
||||||
}
|
|
||||||
|
|
||||||
validate(field: FormFieldModel): boolean {
|
|
||||||
let isValid = true;
|
|
||||||
if (this.isSupported(field) && field.value && field.isVisible) {
|
|
||||||
if (!DateTimeFieldValidator.isValidDateTime(field.value)) {
|
|
||||||
field.validationSummary.message = 'FORM.FIELD.VALIDATOR.INVALID_DATE';
|
|
||||||
isValid = false;
|
|
||||||
} else {
|
|
||||||
isValid = this.checkDateTime(field);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkDateTime(field: FormFieldModel): boolean {
|
|
||||||
let isValid = true;
|
|
||||||
const fieldValueDate = DateFnsUtils.getDate(field.value);
|
|
||||||
const subjectFieldDate = DateFnsUtils.getDate(field[this.getSubjectField()]);
|
|
||||||
|
|
||||||
if (this.compareDates(fieldValueDate, subjectFieldDate)) {
|
|
||||||
field.validationSummary.message = this.getErrorMessage();
|
|
||||||
field.validationSummary.attributes.set(this.getSubjectField(), DateFnsUtils.formatDate(subjectFieldDate, field.dateDisplayFormat));
|
|
||||||
isValid = false;
|
|
||||||
}
|
|
||||||
return isValid;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract compareDates(fieldValueDate: Date, subjectFieldDate: Date): boolean;
|
|
||||||
|
|
||||||
protected abstract getSubjectField(): string;
|
|
||||||
|
|
||||||
protected abstract getErrorMessage(): string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the min constraint for the datetime value.
|
|
||||||
*
|
|
||||||
* Notes for developers:
|
|
||||||
* the format of the min/max values is always the ISO datetime: i.e. 2023-10-01T15:21:00.000Z.
|
|
||||||
* Min/Max values can be parsed with standard `new Date(value)` calls.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export class MinDateTimeFieldValidator extends BoundaryDateTimeFieldValidator {
|
|
||||||
protected compareDates(fieldValueDate: Date, subjectFieldDate: Date): boolean {
|
|
||||||
return isBefore(fieldValueDate, subjectFieldDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getSubjectField(): string {
|
|
||||||
return 'minValue';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getErrorMessage(): string {
|
|
||||||
return `FORM.FIELD.VALIDATOR.NOT_LESS_THAN`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates the max constraint for the datetime value.
|
|
||||||
*
|
|
||||||
* Notes for developers:
|
|
||||||
* the format of the min/max values is always the ISO datetime: i.e. 2023-10-01T15:21:00.000Z.
|
|
||||||
* Min/Max values can be parsed with standard `new Date(value)` calls.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export class MaxDateTimeFieldValidator extends BoundaryDateTimeFieldValidator {
|
|
||||||
protected compareDates(fieldValueDate: Date, subjectFieldDate: Date): boolean {
|
|
||||||
return isAfter(fieldValueDate, subjectFieldDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getSubjectField(): string {
|
|
||||||
return 'maxValue';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected getErrorMessage(): string {
|
|
||||||
return `FORM.FIELD.VALIDATOR.NOT_GREATER_THAN`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class MinLengthFieldValidator implements FormFieldValidator {
|
export class MinLengthFieldValidator implements FormFieldValidator {
|
||||||
private supportedTypes = [FormFieldTypes.TEXT, FormFieldTypes.MULTILINE_TEXT];
|
private supportedTypes = [FormFieldTypes.TEXT, FormFieldTypes.MULTILINE_TEXT];
|
||||||
|
|
||||||
@@ -523,12 +317,6 @@ export const FORM_FIELD_VALIDATORS = [
|
|||||||
new MinValueFieldValidator(),
|
new MinValueFieldValidator(),
|
||||||
new MaxValueFieldValidator(),
|
new MaxValueFieldValidator(),
|
||||||
new RegExFieldValidator(),
|
new RegExFieldValidator(),
|
||||||
new DateFieldValidator(),
|
|
||||||
new DateTimeFieldValidator(),
|
|
||||||
new MinDateFieldValidator(),
|
|
||||||
new MaxDateFieldValidator(),
|
|
||||||
new FixedValueFieldValidator(),
|
new FixedValueFieldValidator(),
|
||||||
new MinDateTimeFieldValidator(),
|
|
||||||
new MaxDateTimeFieldValidator(),
|
|
||||||
new DecimalFieldValidator()
|
new DecimalFieldValidator()
|
||||||
];
|
];
|
||||||
|
@@ -143,6 +143,10 @@ export class FormFieldModel extends FormWidgetModel {
|
|||||||
this._isValid = false;
|
this._isValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
markAsValid() {
|
||||||
|
this._isValid = true;
|
||||||
|
}
|
||||||
|
|
||||||
validate(): boolean {
|
validate(): boolean {
|
||||||
this.validationSummary = new ErrorMessageModel();
|
this.validationSummary = new ErrorMessageModel();
|
||||||
|
|
||||||
|
@@ -148,13 +148,13 @@ export class FormModel implements ProcessFormModel {
|
|||||||
validateForm(): void {
|
validateForm(): void {
|
||||||
const validateFormEvent: any = new ValidateFormEvent(this);
|
const validateFormEvent: any = new ValidateFormEvent(this);
|
||||||
|
|
||||||
const errorsField: FormFieldModel[] = [];
|
const errorsField: FormFieldModel[] = this.fieldsCache.filter((field) => {
|
||||||
|
if (!FormFieldTypes.isReactiveType(field.type)) {
|
||||||
for (let i = 0; i < this.fieldsCache.length; i++) {
|
return !field.validate();
|
||||||
if (!this.fieldsCache[i].validate()) {
|
} else {
|
||||||
errorsField.push(this.fieldsCache[i]);
|
return field.validationSummary.isActive();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
this.isValid = errorsField.length <= 0;
|
this.isValid = errorsField.length <= 0;
|
||||||
|
|
||||||
@@ -191,7 +191,7 @@ export class FormModel implements ProcessFormModel {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!field.validate()) {
|
if (!FormFieldTypes.isReactiveType(field.type) && !field.validate()) {
|
||||||
this.markAsInvalid();
|
this.markAsInvalid();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
<div class="{{ field.className }}"
|
<div class="{{ field.className }}"
|
||||||
id="data-time-widget"
|
id="data-time-widget"
|
||||||
[class.adf-invalid]="!field.isValid && isTouched()"
|
[class.adf-invalid]="datetimeInputControl.invalid && datetimeInputControl.touched"
|
||||||
[class.adf-left-label-input-container]="field.leftLabels">
|
[class.adf-left-label-input-container]="field.leftLabels">
|
||||||
<div *ngIf="field.leftLabels">
|
<div *ngIf="field.leftLabels">
|
||||||
<label class="adf-label adf-left-label" [attr.for]="field.id">
|
<label class="adf-label adf-left-label" [attr.for]="field.id">
|
||||||
@@ -17,29 +17,24 @@
|
|||||||
<input matInput
|
<input matInput
|
||||||
[matDatetimepicker]="datetimePicker"
|
[matDatetimepicker]="datetimePicker"
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[(ngModel)]="value"
|
[formControl]="datetimeInputControl"
|
||||||
[required]="isRequired()"
|
|
||||||
[disabled]="field.readOnly"
|
|
||||||
(change)="onValueChanged($event)"
|
|
||||||
(dateChange)="onDateChanged($event)"
|
|
||||||
(keydown.enter)="datetimePicker.open()"
|
(keydown.enter)="datetimePicker.open()"
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[title]="field.tooltip"
|
[title]="field.tooltip"
|
||||||
(blur)="markAsTouched()"
|
(blur)="updateField()"
|
||||||
[min]="minDate"
|
[min]="minDate"
|
||||||
[max]="maxDate">
|
[max]="maxDate">
|
||||||
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker"
|
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker"
|
||||||
[disabled]="field.readOnly"></mat-datetimepicker-toggle>
|
[disabled]="field.readOnly">
|
||||||
|
</mat-datetimepicker-toggle>
|
||||||
|
<mat-datetimepicker #datetimePicker
|
||||||
|
data-automation-id="adf-date-time-widget-picker"
|
||||||
|
type="datetime"
|
||||||
|
[touchUi]="true"
|
||||||
|
[timeInterval]="5"
|
||||||
|
[disabled]="field.readOnly">
|
||||||
|
</mat-datetimepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget *ngIf="datetimeInputControl.invalid && datetimeInputControl.touched" [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()"
|
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
|
||||||
<mat-datetimepicker #datetimePicker
|
|
||||||
data-automation-id="adf-date-time-widget-picker"
|
|
||||||
type="datetime"
|
|
||||||
[touchUi]="true"
|
|
||||||
[timeInterval]="5"
|
|
||||||
[disabled]="field.readOnly">
|
|
||||||
</mat-datetimepicker>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -22,12 +22,10 @@ import { DateTimeWidgetComponent } from './date-time.widget';
|
|||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { FormFieldTypes } from '../core/form-field-types';
|
import { FormFieldTypes } from '../core/form-field-types';
|
||||||
import { DateFieldValidator, DateTimeFieldValidator } from '../core';
|
|
||||||
import { HarnessLoader } from '@angular/cdk/testing';
|
import { HarnessLoader } from '@angular/cdk/testing';
|
||||||
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
|
||||||
import { MatInputHarness } from '@angular/material/input/testing';
|
import { MatInputHarness } from '@angular/material/input/testing';
|
||||||
import { addMinutes } from 'date-fns';
|
import { addMinutes } from 'date-fns';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
|
||||||
import { MatDialogModule } from '@angular/material/dialog';
|
import { MatDialogModule } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
|
||||||
@@ -35,6 +33,7 @@ import { MatMenuModule } from '@angular/material/menu';
|
|||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||||
|
|
||||||
describe('DateTimeWidgetComponent', () => {
|
describe('DateTimeWidgetComponent', () => {
|
||||||
let loader: HarnessLoader;
|
let loader: HarnessLoader;
|
||||||
@@ -47,7 +46,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [
|
imports: [
|
||||||
TranslateModule.forRoot(),
|
TranslateModule.forRoot(),
|
||||||
HttpClientModule,
|
HttpClientTestingModule,
|
||||||
NoopAnimationsModule,
|
NoopAnimationsModule,
|
||||||
MatDialogModule,
|
MatDialogModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
@@ -65,7 +64,6 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
widget = fixture.componentInstance;
|
widget = fixture.componentInstance;
|
||||||
|
|
||||||
form = new FormModel();
|
form = new FormModel();
|
||||||
form.fieldValidators = [new DateFieldValidator(), new DateTimeFieldValidator()];
|
|
||||||
loader = TestbedHarnessEnvironment.loader(fixture);
|
loader = TestbedHarnessEnvironment.loader(fixture);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -74,17 +72,16 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup min value for date picker', async () => {
|
it('should setup min value for date picker', () => {
|
||||||
const minValue = '1982-03-13T10:00:00Z';
|
const minValue = '1982-03-13T10:00:00Z';
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
type: 'datetime',
|
type: FormFieldTypes.DATETIME,
|
||||||
minValue
|
minValue
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.minDate.toISOString()).toBe(`1982-03-13T10:00:00.000Z`);
|
expect(widget.minDate.toISOString()).toBe(`1982-03-13T10:00:00.000Z`);
|
||||||
});
|
});
|
||||||
@@ -93,7 +90,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@@ -101,13 +98,12 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
expect(element.querySelector('#data-time-widget')).not.toBeNull();
|
expect(element.querySelector('#data-time-widget')).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should setup max value for date picker', async () => {
|
it('should setup max value for date picker', () => {
|
||||||
const maxValue = '1982-03-13T10:00:00Z';
|
const maxValue = '1982-03-13T10:00:00Z';
|
||||||
widget.field = new FormFieldModel(null, {
|
widget.field = new FormFieldModel(null, {
|
||||||
maxValue
|
maxValue
|
||||||
});
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.maxDate.toISOString()).toBe('1982-03-13T10:00:00.000Z');
|
expect(widget.maxDate.toISOString()).toBe('1982-03-13T10:00:00.000Z');
|
||||||
});
|
});
|
||||||
@@ -119,76 +115,70 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
widget.onDateChanged({ value: new Date('1982-03-13T10:00:00.000Z') } as any);
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
widget.datetimeInputControl.setValue(new Date('1982-03-13T10:00:00.000Z'));
|
||||||
|
|
||||||
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate the initial datetime value', async () => {
|
it('should validate the initial datetime value', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.whenStable();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(field.isValid).toBeTrue();
|
expect(field.isValid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate the updated datetime value', async () => {
|
it('should validate the updated datetime value', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.whenStable();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
let expectedDate = new Date('9999-09-12T09:10:00.000Z');
|
let expectedDate = new Date('9999-09-12T09:10:00.000Z');
|
||||||
expectedDate = addMinutes(expectedDate, expectedDate.getTimezoneOffset());
|
expectedDate = addMinutes(expectedDate, expectedDate.getTimezoneOffset());
|
||||||
widget.onDateChanged({ value: expectedDate } as any);
|
widget.datetimeInputControl.setValue(expectedDate);
|
||||||
|
|
||||||
expect(field.value).toBe('9999-09-12T09:10:00.000Z');
|
expect(field.value).toEqual(new Date('9999-09-12T09:10:00.000Z'));
|
||||||
expect(field.isValid).toBeTrue();
|
expect(field.isValid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should forwad the incorrect datetime input for further validation', async () => {
|
it('should forwad the incorrect datetime input for further validation', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
widget.onDateChanged({
|
widget.datetimeInputControl.setValue(new Date('123abc'));
|
||||||
value: null,
|
|
||||||
targetElement: {
|
|
||||||
value: '123abc'
|
|
||||||
}
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(field.value).toBe('123abc');
|
expect(widget.datetimeInputControl.invalid).toBeTrue();
|
||||||
expect(field.isValid).toBeFalse();
|
expect(field.isValid).toBeFalse();
|
||||||
expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A');
|
expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A');
|
||||||
});
|
});
|
||||||
@@ -198,7 +188,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
@@ -206,35 +196,31 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
fixture.whenStable();
|
fixture.whenStable();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
widget.onValueChanged({ target: { value: '9999-09-12T09:10:00.000Z' } } as any);
|
const input = await loader.getHarness(MatInputHarness);
|
||||||
|
await input.setValue('9999-09-12T09:10:00.000Z');
|
||||||
|
|
||||||
expect(field.value).toBe('9999-09-12T09:10:00.000Z');
|
expect(field.value).toEqual(new Date('9999-09-12T09:10:00.000Z'));
|
||||||
expect(field.isValid).toBeTrue();
|
expect(field.isValid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fail validating incorrect keyboard input', async () => {
|
it('should fail validating incorrect keyboard input', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
widget.onValueChanged({
|
const dateTimeInput = fixture.nativeElement.querySelector('input');
|
||||||
target: {
|
dateTimeInput.value = '123abc';
|
||||||
value: '123abc'
|
dateTimeInput.dispatchEvent(new Event('input'));
|
||||||
}
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
expect(widget.datetimeInputControl.invalid).toBeTrue();
|
||||||
await fixture.whenStable();
|
expect(field.value).toBe(null);
|
||||||
|
|
||||||
expect(field.value).toBe('123abc');
|
|
||||||
expect(field.isValid).toBeFalse();
|
expect(field.isValid).toBeFalse();
|
||||||
expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A');
|
expect(field.validationSummary.message).toBe('D-M-YYYY hh:mm A');
|
||||||
});
|
});
|
||||||
@@ -244,7 +230,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-09-12T09:00:00.000Z',
|
value: '9999-09-12T09:00:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
@@ -252,9 +238,12 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
fixture.whenStable();
|
fixture.whenStable();
|
||||||
await fixture.whenStable();
|
await fixture.whenStable();
|
||||||
|
|
||||||
widget.onDateChanged({ value: null, targetElement: { value: '' } } as any);
|
const input = await loader.getHarness(MatInputHarness);
|
||||||
|
await input.setValue(null);
|
||||||
|
|
||||||
expect(field.value).toBe('');
|
expect(widget.datetimeInputControl.value).toBe(null);
|
||||||
|
expect(widget.datetimeInputControl.valid).toBeTrue();
|
||||||
|
expect(field.value).toBe(null);
|
||||||
expect(field.isValid).toBeTrue();
|
expect(field.isValid).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -282,6 +271,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
type: FormFieldTypes.DATETIME,
|
type: FormFieldTypes.DATETIME,
|
||||||
required: true
|
required: true
|
||||||
});
|
});
|
||||||
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be marked as invalid after interaction', () => {
|
it('should be marked as invalid after interaction', () => {
|
||||||
@@ -296,8 +286,6 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to display label with asterisk', () => {
|
it('should be able to display label with asterisk', () => {
|
||||||
fixture.detectChanges();
|
|
||||||
|
|
||||||
const asterisk = element.querySelector<HTMLElement>('.adf-asterisk');
|
const asterisk = element.querySelector<HTMLElement>('.adf-asterisk');
|
||||||
|
|
||||||
expect(asterisk).not.toBeNull();
|
expect(asterisk).not.toBeNull();
|
||||||
@@ -311,7 +299,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-11-30T10:30:00.000Z',
|
value: '9999-11-30T10:30:00.000Z',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -327,7 +315,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-12-30T10:30:00.000Z',
|
value: '9999-12-30T10:30:00.000Z',
|
||||||
dateDisplayFormat: 'MM-DD-YYYY HH:mm A',
|
dateDisplayFormat: 'MM-DD-YYYY HH:mm A',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -343,7 +331,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-12-30T10:30:00.000Z',
|
value: '9999-12-30T10:30:00.000Z',
|
||||||
dateDisplayFormat: 'MM-DD-YYYY HH:mm A',
|
dateDisplayFormat: 'MM-DD-YYYY HH:mm A',
|
||||||
type: 'datetime'
|
type: FormFieldTypes.DATETIME
|
||||||
});
|
});
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
@@ -365,7 +353,7 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'datetime-field-name',
|
name: 'datetime-field-name',
|
||||||
value: '9999-12-30T10:30:00.000Z',
|
value: '9999-12-30T10:30:00.000Z',
|
||||||
type: 'datetime',
|
type: FormFieldTypes.DATETIME,
|
||||||
dateDisplayFormat: 'MM-DD-YYYY HH:mm A'
|
dateDisplayFormat: 'MM-DD-YYYY HH:mm A'
|
||||||
});
|
});
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
@@ -385,6 +373,31 @@ describe('DateTimeWidgetComponent', () => {
|
|||||||
expect(await input.getValue()).toBe('03-02-2020 00:00 AM');
|
expect(await input.getValue()).toBe('03-02-2020 00:00 AM');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should display value with specified format when format of provided datetime is different', async () => {
|
||||||
|
const field = new FormFieldModel(form, {
|
||||||
|
id: 'date-field-id',
|
||||||
|
name: 'datetime-field-name',
|
||||||
|
value: '9999-12-30T10:30:00.000Z',
|
||||||
|
type: FormFieldTypes.DATETIME,
|
||||||
|
dateDisplayFormat: 'MM/DD/YYYY HH;mm A'
|
||||||
|
});
|
||||||
|
widget.field = field;
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
const input = await loader.getHarness(MatInputHarness);
|
||||||
|
expect(await input.getValue()).toBe('12/30/9999 10;30 AM');
|
||||||
|
|
||||||
|
widget.field.value = '2020-03-02T00:00:00.000Z';
|
||||||
|
|
||||||
|
fixture.componentInstance.ngOnInit();
|
||||||
|
fixture.detectChanges();
|
||||||
|
await fixture.whenStable();
|
||||||
|
|
||||||
|
expect(await input.getValue()).toBe('03/02/2020 00;00 AM');
|
||||||
|
});
|
||||||
|
|
||||||
describe('when form model has left labels', () => {
|
describe('when form model has left labels', () => {
|
||||||
it('should have left labels classes on leftLabels true', () => {
|
it('should have left labels classes on leftLabels true', () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), {
|
||||||
|
@@ -18,18 +18,20 @@
|
|||||||
/* eslint-disable @angular-eslint/component-selector */
|
/* eslint-disable @angular-eslint/component-selector */
|
||||||
|
|
||||||
import { NgIf } from '@angular/common';
|
import { NgIf } from '@angular/common';
|
||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms';
|
||||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerInputEvent, MatDatetimepickerModule } from '@mat-datetimepicker/core';
|
import { DatetimeAdapter, MAT_DATETIME_FORMATS, MatDatetimepickerModule } from '@mat-datetimepicker/core';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { isValid } from 'date-fns';
|
|
||||||
import { ADF_DATE_FORMATS, ADF_DATETIME_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, DateFnsUtils } from '../../../../common';
|
import { ADF_DATE_FORMATS, ADF_DATETIME_FORMATS, AdfDateFnsAdapter, AdfDateTimeFnsAdapter, DateFnsUtils } from '../../../../common';
|
||||||
import { FormService } from '../../../services/form.service';
|
import { FormService } from '../../../services/form.service';
|
||||||
import { ErrorWidgetComponent } from '../error/error.component';
|
import { ErrorWidgetComponent } from '../error/error.component';
|
||||||
import { WidgetComponent } from '../widget.component';
|
import { WidgetComponent } from '../widget.component';
|
||||||
|
import { Subject } from 'rxjs';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { ErrorMessageModel } from '../core/error-message.model';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'date-time-widget',
|
selector: 'date-time-widget',
|
||||||
@@ -42,68 +44,116 @@ import { WidgetComponent } from '../widget.component';
|
|||||||
],
|
],
|
||||||
templateUrl: './date-time.widget.html',
|
templateUrl: './date-time.widget.html',
|
||||||
styleUrls: ['./date-time.widget.scss'],
|
styleUrls: ['./date-time.widget.scss'],
|
||||||
imports: [NgIf, TranslateModule, MatFormFieldModule, MatInputModule, MatDatetimepickerModule, FormsModule, ErrorWidgetComponent],
|
imports: [NgIf, TranslateModule, MatFormFieldModule, MatInputModule, MatDatetimepickerModule, ReactiveFormsModule, ErrorWidgetComponent],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class DateTimeWidgetComponent extends WidgetComponent implements OnInit {
|
export class DateTimeWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
|
||||||
minDate: Date;
|
minDate: Date;
|
||||||
maxDate: Date;
|
maxDate: Date;
|
||||||
|
datetimeInputControl: FormControl<Date> = new FormControl<Date>(null);
|
||||||
|
|
||||||
@Input()
|
private onDestroy$ = new Subject<void>();
|
||||||
value: any = null;
|
|
||||||
|
|
||||||
constructor(public formService: FormService, private dateAdapter: DateAdapter<Date>, private dateTimeAdapter: DatetimeAdapter<Date>) {
|
public readonly formService = inject(FormService);
|
||||||
super(formService);
|
private readonly dateAdapter = inject(DateAdapter);
|
||||||
|
private readonly dateTimeAdapter = inject(DatetimeAdapter);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.patchFormControl();
|
||||||
|
this.initDateAdapter();
|
||||||
|
this.initDateRange();
|
||||||
|
this.subscribeToDateChanges();
|
||||||
|
this.updateField();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
updateField(): void {
|
||||||
if (this.field.dateDisplayFormat) {
|
this.validateField();
|
||||||
|
this.onFieldChanged(this.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private patchFormControl(): void {
|
||||||
|
this.datetimeInputControl.setValue(this.field.value, { emitEvent: false });
|
||||||
|
this.datetimeInputControl.setValidators(this.isRequired() ? [Validators.required] : []);
|
||||||
|
if (this.field?.readOnly || this.readOnly) {
|
||||||
|
this.datetimeInputControl.disable({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.datetimeInputControl.updateValueAndValidity({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribeToDateChanges(): void {
|
||||||
|
this.datetimeInputControl.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((newDate: Date) => {
|
||||||
|
this.field.value = newDate;
|
||||||
|
this.updateField();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateField(): void {
|
||||||
|
if (this.datetimeInputControl?.invalid) {
|
||||||
|
this.handleErrors(this.datetimeInputControl.errors);
|
||||||
|
this.field.markAsInvalid();
|
||||||
|
} else {
|
||||||
|
this.resetErrors();
|
||||||
|
this.field.markAsValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleErrors(errors: ValidationErrors): void {
|
||||||
|
const errorAttributes = new Map<string, string>();
|
||||||
|
switch (true) {
|
||||||
|
case !!errors.matDatepickerParse:
|
||||||
|
this.updateValidationSummary(this.field.dateDisplayFormat || this.field.defaultDateTimeFormat);
|
||||||
|
break;
|
||||||
|
case !!errors.required:
|
||||||
|
this.updateValidationSummary('FORM.FIELD.REQUIRED');
|
||||||
|
break;
|
||||||
|
case !!errors.matDatepickerMin: {
|
||||||
|
const minValue = DateFnsUtils.formatDate(errors.matDatepickerMin.min, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('minValue', minValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_LESS_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case !!errors.matDatepickerMax: {
|
||||||
|
const maxValue = DateFnsUtils.formatDate(errors.matDatepickerMax.max, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('maxValue', maxValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateValidationSummary(message: string, attributes?: Map<string, string>): void {
|
||||||
|
this.field.validationSummary = new ErrorMessageModel({ message, attributes });
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetErrors(): void {
|
||||||
|
this.updateValidationSummary('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private initDateAdapter(): void {
|
||||||
|
if (this.field?.dateDisplayFormat) {
|
||||||
const dateAdapter = this.dateAdapter as AdfDateFnsAdapter;
|
const dateAdapter = this.dateAdapter as AdfDateFnsAdapter;
|
||||||
dateAdapter.displayFormat = this.field.dateDisplayFormat;
|
dateAdapter.displayFormat = this.field.dateDisplayFormat;
|
||||||
|
|
||||||
const dateTimeAdapter = this.dateTimeAdapter as AdfDateTimeFnsAdapter;
|
const dateTimeAdapter = this.dateTimeAdapter as AdfDateTimeFnsAdapter;
|
||||||
dateTimeAdapter.displayFormat = this.field.dateDisplayFormat;
|
dateTimeAdapter.displayFormat = this.field.dateDisplayFormat;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.field) {
|
private initDateRange(): void {
|
||||||
if (this.field.minValue) {
|
if (this.field?.minValue) {
|
||||||
this.minDate = DateFnsUtils.getDate(this.field.minValue);
|
this.minDate = DateFnsUtils.getDate(this.field.minValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.field.maxValue) {
|
if (this.field?.maxValue) {
|
||||||
this.maxDate = DateFnsUtils.getDate(this.field.maxValue);
|
this.maxDate = DateFnsUtils.getDate(this.field.maxValue);
|
||||||
}
|
|
||||||
|
|
||||||
if (this.field.value) {
|
|
||||||
this.value = DateFnsUtils.getDate(this.field.value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onValueChanged(event: Event) {
|
ngOnDestroy(): void {
|
||||||
const input = event.target as HTMLInputElement;
|
this.onDestroy$.next();
|
||||||
const newValue = this.dateTimeAdapter.parse(input.value, this.field.dateDisplayFormat);
|
this.onDestroy$.complete();
|
||||||
|
|
||||||
if (isValid(newValue)) {
|
|
||||||
this.field.value = newValue.toISOString();
|
|
||||||
} else {
|
|
||||||
this.field.value = input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.value = DateFnsUtils.getDate(this.field.value);
|
|
||||||
this.onFieldChanged(this.field);
|
|
||||||
}
|
|
||||||
|
|
||||||
onDateChanged(event: MatDatetimepickerInputEvent<Date>) {
|
|
||||||
const newValue = event.value;
|
|
||||||
const input = event.targetElement as HTMLInputElement;
|
|
||||||
|
|
||||||
if (newValue && isValid(newValue)) {
|
|
||||||
this.field.value = newValue.toISOString();
|
|
||||||
} else {
|
|
||||||
this.field.value = input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onFieldChanged(this.field);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="{{ field.className }}" id="data-widget" [class.adf-invalid]="!field.isValid && isTouched()">
|
<div class="{{ field.className }}" id="data-widget" [class.adf-invalid]="dateInputControl.invalid && dateInputControl.touched">
|
||||||
<mat-form-field [floatLabel]="'always'" class="adf-date-widget">
|
<mat-form-field [floatLabel]="'always'" class="adf-date-widget">
|
||||||
<mat-label class="adf-label"
|
<mat-label class="adf-label"
|
||||||
[id]="field.id + '-label'"
|
[id]="field.id + '-label'"
|
||||||
@@ -8,21 +8,16 @@
|
|||||||
<input matInput
|
<input matInput
|
||||||
[matDatepicker]="datePicker"
|
[matDatepicker]="datePicker"
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[(ngModel)]="value"
|
[formControl]="dateInputControl"
|
||||||
[required]="field.required"
|
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[min]="minDate"
|
[min]="minDate"
|
||||||
[max]="maxDate"
|
[max]="maxDate"
|
||||||
[disabled]="field.readOnly"
|
(blur)="updateField()">
|
||||||
(dateChange)="onDateChange($event)"
|
|
||||||
(blur)="markAsTouched()">
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #datePicker
|
<mat-datepicker #datePicker
|
||||||
[startAt]="startAt"
|
[startAt]="startAt"
|
||||||
[disabled]="field.readOnly">
|
[disabled]="field.readOnly">
|
||||||
</mat-datepicker>
|
</mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget *ngIf="dateInputControl.invalid && dateInputControl.touched" [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()"
|
|
||||||
required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -18,8 +18,10 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { DateAdapter } from '@angular/material/core';
|
import { DateAdapter } from '@angular/material/core';
|
||||||
import { CoreTestingModule } from '../../../../testing';
|
import { CoreTestingModule } from '../../../../testing';
|
||||||
import { DateFieldValidator, FormFieldModel, FormFieldTypes, FormModel, MaxDateFieldValidator, MinDateFieldValidator } from '../core';
|
import { FormFieldModel, FormFieldTypes, FormModel } from '../core';
|
||||||
import { DateWidgetComponent } from './date.widget';
|
import { DateWidgetComponent } from './date.widget';
|
||||||
|
import { DEFAULT_DATE_FORMAT } from '../../../../common';
|
||||||
|
import { isEqual } from 'date-fns';
|
||||||
|
|
||||||
describe('DateWidgetComponent', () => {
|
describe('DateWidgetComponent', () => {
|
||||||
let widget: DateWidgetComponent;
|
let widget: DateWidgetComponent;
|
||||||
@@ -34,7 +36,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
form = new FormModel();
|
form = new FormModel();
|
||||||
form.fieldValidators = [new DateFieldValidator(), new MinDateFieldValidator(), new MaxDateFieldValidator()];
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(DateWidgetComponent);
|
fixture = TestBed.createComponent(DateWidgetComponent);
|
||||||
adapter = fixture.debugElement.injector.get(DateAdapter);
|
adapter = fixture.debugElement.injector.get(DateAdapter);
|
||||||
@@ -54,8 +55,8 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should setup min value for date picker', () => {
|
it('should setup min value for date picker', () => {
|
||||||
const minValue = '13-03-1982';
|
const minValue = '1982-03-13';
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(null, {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
minValue
|
minValue
|
||||||
@@ -63,12 +64,12 @@ describe('DateWidgetComponent', () => {
|
|||||||
|
|
||||||
widget.ngOnInit();
|
widget.ngOnInit();
|
||||||
|
|
||||||
const expected = adapter.parse(minValue, widget.DATE_FORMAT) as Date;
|
const expected = adapter.parse(minValue, DEFAULT_DATE_FORMAT);
|
||||||
expect(adapter.compareDate(widget.minDate, expected)).toBe(0);
|
expect(isEqual(widget.minDate, expected)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate min date value constraint', async () => {
|
it('should validate min date value constraint', () => {
|
||||||
const minValue = '13-03-1982';
|
const minValue = '1982-03-13';
|
||||||
|
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
@@ -79,22 +80,19 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
widget.ngOnInit();
|
fixture.detectChanges();
|
||||||
|
|
||||||
widget.onDateChange({
|
widget.dateInputControl.setValue(new Date('1982/03/12'));
|
||||||
value: new Date('1982/03/12')
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.field.isValid).toBeFalsy();
|
expect(widget.field.isValid).toBeFalsy();
|
||||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN');
|
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_LESS_THAN');
|
||||||
expect(field.validationSummary.attributes.get('minValue')).toBe('13-03-1982');
|
expect(field.validationSummary.attributes.get('minValue')).toBe('13-03-1982');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should validate max date value constraint', async () => {
|
it('should validate max date value constraint', () => {
|
||||||
const maxValue = '13-03-1982';
|
const maxValue = '1982-03-13';
|
||||||
|
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
@@ -105,14 +103,11 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
widget.ngOnInit();
|
fixture.detectChanges();
|
||||||
|
|
||||||
widget.onDateChange({
|
widget.dateInputControl.setValue(new Date('2023/03/13'));
|
||||||
value: new Date('2023/03/13')
|
|
||||||
} as any);
|
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.field.isValid).toBeFalsy();
|
expect(widget.field.isValid).toBeFalsy();
|
||||||
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN');
|
expect(field.validationSummary.message).toBe('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN');
|
||||||
@@ -132,13 +127,13 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should setup max value for date picker', () => {
|
it('should setup max value for date picker', () => {
|
||||||
const maxValue = '31-03-1982';
|
const maxValue = '1982-03-31';
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
maxValue
|
maxValue
|
||||||
});
|
});
|
||||||
widget.ngOnInit();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const expected = adapter.parse(maxValue, widget.DATE_FORMAT) as Date;
|
const expected = adapter.parse(maxValue, DEFAULT_DATE_FORMAT) as Date;
|
||||||
expect(adapter.compareDate(widget.maxDate, expected)).toBe(0);
|
expect(adapter.compareDate(widget.maxDate, expected)).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -153,9 +148,10 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
widget.onDateChange({
|
|
||||||
value: new Date('12/12/2012')
|
fixture.detectChanges();
|
||||||
} as any);
|
|
||||||
|
widget.dateInputControl.setValue(new Date('12/12/2012'));
|
||||||
|
|
||||||
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
||||||
});
|
});
|
||||||
@@ -187,16 +183,15 @@ describe('DateWidgetComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show visible date widget', async () => {
|
it('should show visible date widget', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9-9-9999',
|
value: new Date('9-9-9999'),
|
||||||
type: 'date'
|
type: FormFieldTypes.DATE
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement).not.toBeNull();
|
expect(dateElement).not.toBeNull();
|
||||||
@@ -204,30 +199,20 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(dateElement?.value).toContain('9-9-9999');
|
expect(dateElement?.value).toContain('9-9-9999');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('[C310335] - Should be able to change display format for Date widget', async () => {
|
it('[C310335] - Should be able to change display format for Date widget', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '30-12-9999',
|
value: new Date('12-30-9999'),
|
||||||
type: 'date',
|
type: FormFieldTypes.DATE,
|
||||||
dateDisplayFormat: 'MM-DD-YYYY'
|
dateDisplayFormat: 'dd.MM.yyyy'
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
let dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
let dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement?.value).toContain('12-30-9999');
|
|
||||||
|
|
||||||
widget.field.value = '05-06-2019';
|
|
||||||
widget.field.dateDisplayFormat = 'DD.MM.YYYY';
|
|
||||||
|
|
||||||
fixture.componentInstance.ngOnInit();
|
|
||||||
fixture.detectChanges();
|
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement?.value).toContain('05.06.2019');
|
expect(dateElement?.value).toContain('30.12.9999');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable date button when is readonly', () => {
|
it('should disable date button when is readonly', () => {
|
||||||
@@ -235,7 +220,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9-9-9999',
|
value: '9-9-9999',
|
||||||
type: 'date'
|
type: FormFieldTypes.DATE
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -257,41 +242,44 @@ describe('DateWidgetComponent', () => {
|
|||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: 'aa',
|
value: 'aa',
|
||||||
type: 'date',
|
type: FormFieldTypes.DATE,
|
||||||
readOnly: 'false'
|
readOnly: 'false'
|
||||||
});
|
});
|
||||||
widget.field.isVisible = true;
|
widget.field.isVisible = true;
|
||||||
widget.field.readOnly = false;
|
widget.field.readOnly = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
widget.dateInputControl.setValue(new Date('invalid date'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(widget.field.isValid).toBeFalsy();
|
expect(widget.field.isValid).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display always the json value', async () => {
|
it('should display always the json value', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '30-12-9999',
|
value: new Date('12-30-9999'),
|
||||||
type: 'date',
|
type: FormFieldTypes.DATE,
|
||||||
dateDisplayFormat: 'MM-DD-YYYY'
|
dateDisplayFormat: 'MM-dd-yyyy'
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement).toBeDefined();
|
expect(dateElement).toBeDefined();
|
||||||
expect(dateElement.value).toContain('12-30-9999');
|
expect(dateElement.value).toContain('12-30-9999');
|
||||||
|
|
||||||
widget.field.value = '03-02-2020';
|
dateElement.value = '03-02-2020';
|
||||||
|
dateElement.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
fixture.componentInstance.ngOnInit();
|
fixture.componentInstance.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(dateElement.value).toContain('02-03-2020');
|
expect(dateElement.value).toContain('03-02-2020');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -18,18 +18,21 @@
|
|||||||
/* eslint-disable @angular-eslint/component-selector */
|
/* eslint-disable @angular-eslint/component-selector */
|
||||||
|
|
||||||
import { NgIf } from '@angular/common';
|
import { NgIf } from '@angular/common';
|
||||||
import { Component, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, inject, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormControl, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms';
|
||||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||||
import { MatDatepickerInputEvent, MatDatepickerModule } from '@angular/material/datepicker';
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { ADF_DATE_FORMATS, AdfDateFnsAdapter } from '../../../../common';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { ADF_DATE_FORMATS, AdfDateFnsAdapter, DateFnsUtils, DEFAULT_DATE_FORMAT } from '../../../../common';
|
||||||
import { FormService } from '../../../services/form.service';
|
import { FormService } from '../../../services/form.service';
|
||||||
import { ErrorWidgetComponent } from '../error/error.component';
|
import { ErrorWidgetComponent } from '../error/error.component';
|
||||||
import { WidgetComponent } from '../widget.component';
|
import { WidgetComponent } from '../widget.component';
|
||||||
|
import { ErrorMessageModel } from '../core/error-message.model';
|
||||||
|
import { parseISO } from 'date-fns';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'date-widget',
|
selector: 'date-widget',
|
||||||
@@ -50,62 +53,120 @@ import { WidgetComponent } from '../widget.component';
|
|||||||
'(invalid)': 'event($event)',
|
'(invalid)': 'event($event)',
|
||||||
'(select)': 'event($event)'
|
'(select)': 'event($event)'
|
||||||
},
|
},
|
||||||
imports: [MatFormFieldModule, TranslateModule, MatInputModule, MatDatepickerModule, FormsModule, ErrorWidgetComponent, NgIf],
|
imports: [MatFormFieldModule, TranslateModule, MatInputModule, MatDatepickerModule, ReactiveFormsModule, ErrorWidgetComponent, NgIf],
|
||||||
encapsulation: ViewEncapsulation.None
|
encapsulation: ViewEncapsulation.None
|
||||||
})
|
})
|
||||||
export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
|
export class DateWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
|
||||||
DATE_FORMAT = 'dd-MM-yyyy';
|
|
||||||
|
|
||||||
minDate: Date;
|
minDate: Date;
|
||||||
maxDate: Date;
|
maxDate: Date;
|
||||||
startAt: Date;
|
startAt: Date;
|
||||||
|
|
||||||
@Input()
|
dateInputControl: FormControl<Date> = new FormControl<Date>(null);
|
||||||
value: any = null;
|
|
||||||
|
|
||||||
private onDestroy$ = new Subject<boolean>();
|
private onDestroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(public formService: FormService, private dateAdapter: DateAdapter<Date>) {
|
public readonly formService = inject(FormService);
|
||||||
super(formService);
|
private readonly dateAdapter = inject(DateAdapter);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.patchFormControl();
|
||||||
|
this.initDateAdapter();
|
||||||
|
this.initDateRange();
|
||||||
|
this.initStartAt();
|
||||||
|
this.subscribeToDateChanges();
|
||||||
|
this.updateField();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
updateField(): void {
|
||||||
if (this.field.dateDisplayFormat) {
|
this.validateField();
|
||||||
|
this.onFieldChanged(this.field);
|
||||||
|
}
|
||||||
|
private patchFormControl(): void {
|
||||||
|
this.dateInputControl.setValue(this.field.value, { emitEvent: false });
|
||||||
|
this.dateInputControl.setValidators(this.isRequired() ? [Validators.required] : []);
|
||||||
|
if (this.field?.readOnly || this.readOnly) {
|
||||||
|
this.dateInputControl.disable({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dateInputControl.updateValueAndValidity({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribeToDateChanges(): void {
|
||||||
|
this.dateInputControl.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((newDate: Date) => {
|
||||||
|
this.field.value = newDate;
|
||||||
|
this.updateField();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateField(): void {
|
||||||
|
if (this.dateInputControl.invalid) {
|
||||||
|
this.handleErrors(this.dateInputControl.errors);
|
||||||
|
this.field.markAsInvalid();
|
||||||
|
} else {
|
||||||
|
this.resetErrors();
|
||||||
|
this.field.markAsValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleErrors(errors: ValidationErrors): void {
|
||||||
|
const errorAttributes = new Map<string, string>();
|
||||||
|
switch (true) {
|
||||||
|
case !!errors.matDatepickerParse:
|
||||||
|
this.updateValidationSummary(this.field.dateDisplayFormat || this.field.defaultDateTimeFormat);
|
||||||
|
break;
|
||||||
|
case !!errors.required:
|
||||||
|
this.updateValidationSummary('FORM.FIELD.REQUIRED');
|
||||||
|
break;
|
||||||
|
case !!errors.matDatepickerMin: {
|
||||||
|
const minValue = DateFnsUtils.formatDate(errors.matDatepickerMin.min, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('minValue', minValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_LESS_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case !!errors.matDatepickerMax: {
|
||||||
|
const maxValue = DateFnsUtils.formatDate(errors.matDatepickerMax.max, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('maxValue', maxValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateValidationSummary(message: string, attributes?: Map<string, string>): void {
|
||||||
|
this.field.validationSummary = new ErrorMessageModel({ message, attributes });
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetErrors(): void {
|
||||||
|
this.updateValidationSummary('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private initDateAdapter(): void {
|
||||||
|
if (this.field?.dateDisplayFormat) {
|
||||||
const adapter = this.dateAdapter as AdfDateFnsAdapter;
|
const adapter = this.dateAdapter as AdfDateFnsAdapter;
|
||||||
adapter.displayFormat = this.field.dateDisplayFormat;
|
adapter.displayFormat = this.field.dateDisplayFormat;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.field) {
|
private initDateRange(): void {
|
||||||
if (this.field.minValue) {
|
if (this.field?.minValue) {
|
||||||
this.minDate = this.dateAdapter.parse(this.field.minValue, this.DATE_FORMAT);
|
this.minDate = parseISO(this.field.minValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.field.maxValue) {
|
if (this.field?.maxValue) {
|
||||||
this.maxDate = this.dateAdapter.parse(this.field.maxValue, this.DATE_FORMAT);
|
this.maxDate = parseISO(this.field.maxValue);
|
||||||
}
|
|
||||||
|
|
||||||
if (this.field.value) {
|
|
||||||
this.startAt = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT);
|
|
||||||
this.value = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
private initStartAt(): void {
|
||||||
this.onDestroy$.next(true);
|
if (this.field?.value) {
|
||||||
|
this.startAt = this.dateAdapter.parse(this.field.value, DEFAULT_DATE_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onDestroy$.next();
|
||||||
this.onDestroy$.complete();
|
this.onDestroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDateChange(event: MatDatepickerInputEvent<Date>) {
|
|
||||||
const value = event.value;
|
|
||||||
const input = event.targetElement as HTMLInputElement;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
this.field.value = this.dateAdapter.format(value, this.DATE_FORMAT);
|
|
||||||
} else {
|
|
||||||
this.field.value = input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onFieldChanged(this.field);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, HostListener, OnInit } from '@angular/core';
|
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, HostListener, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Observable, of, forkJoin, Subject, Subscription } from 'rxjs';
|
import { Observable, of, forkJoin, Subject, Subscription } from 'rxjs';
|
||||||
import { switchMap, takeUntil, map, filter } from 'rxjs/operators';
|
import { switchMap, takeUntil, map, filter } from 'rxjs/operators';
|
||||||
import {
|
import {
|
||||||
@@ -130,7 +130,8 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
private dialog: MatDialog,
|
private dialog: MatDialog,
|
||||||
protected visibilityService: WidgetVisibilityService,
|
protected visibilityService: WidgetVisibilityService,
|
||||||
private readonly displayModeService: DisplayModeService,
|
private readonly displayModeService: DisplayModeService,
|
||||||
private spinnerService: FormCloudSpinnerService
|
private spinnerService: FormCloudSpinnerService,
|
||||||
|
private readonly changeDetector: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@@ -421,6 +422,7 @@ export class FormCloudComponent extends FormBaseComponent implements OnChanges,
|
|||||||
this.displayModeOn.emit(this.displayModeService.findConfiguration(this.displayMode, this.displayModeConfigurations));
|
this.displayModeOn.emit(this.displayModeService.findConfiguration(this.displayMode, this.displayModeConfigurations));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.changeDetector.detectChanges();
|
||||||
this.formLoaded.emit(form);
|
this.formLoaded.emit(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="!field.isValid && isTouched()" [class.adf-left-label-input-container]="field.leftLabels">
|
<div class="{{field.className}}" id="data-widget" [class.adf-invalid]="dateInputControl.invalid && dateInputControl.touched" [class.adf-left-label-input-container]="field.leftLabels">
|
||||||
<div *ngIf="field.leftLabels">
|
<div *ngIf="field.leftLabels">
|
||||||
<label class="adf-label adf-left-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
<label class="adf-label adf-left-label" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
@@ -7,24 +7,21 @@
|
|||||||
<mat-form-field class="adf-date-widget" [class.adf-left-label-input-datepicker]="field.leftLabels" [hideRequiredMarker]="true">
|
<mat-form-field class="adf-date-widget" [class.adf-left-label-input-datepicker]="field.leftLabels" [hideRequiredMarker]="true">
|
||||||
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">{{field.name | translate }} ({{field.dateDisplayFormat}})<span class="adf-asterisk"
|
||||||
*ngIf="isRequired()">*</span></label>
|
*ngIf="isRequired()">*</span></label>
|
||||||
<input matInput [matDatepicker]="datePicker"
|
<input matInput
|
||||||
|
[matDatepicker]="datePicker"
|
||||||
[id]="field.id"
|
[id]="field.id"
|
||||||
[(ngModel)]="value"
|
[formControl]="dateInputControl"
|
||||||
[required]="field.required"
|
|
||||||
[placeholder]="field.placeholder"
|
[placeholder]="field.placeholder"
|
||||||
[min]="minDate"
|
[min]="minDate"
|
||||||
[max]="maxDate"
|
[max]="maxDate"
|
||||||
[disabled]="field.readOnly"
|
|
||||||
[title]="field.tooltip"
|
[title]="field.tooltip"
|
||||||
(dateChange)="onDateChanged($event)"
|
(blur)="updateField()">
|
||||||
(blur)="markAsTouched()">
|
|
||||||
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly"></mat-datepicker-toggle>
|
<mat-datepicker-toggle matSuffix [for]="datePicker" [disabled]="field.readOnly"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #datePicker
|
<mat-datepicker #datePicker
|
||||||
[startAt]="startAt"
|
[startAt]="startAt"
|
||||||
[disabled]="field.readOnly">
|
[disabled]="field.readOnly">
|
||||||
</mat-datepicker>
|
</mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<error-widget [error]="field.validationSummary"></error-widget>
|
<error-widget *ngIf="dateInputControl.invalid && dateInputControl.touched" [error]="field.validationSummary"></error-widget>
|
||||||
<error-widget *ngIf="isInvalidFieldRequired() && isTouched()" required="{{ 'FORM.FIELD.REQUIRED' | translate }}"></error-widget>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { DateCloudWidgetComponent } from './date-cloud.widget';
|
import { DateCloudWidgetComponent } from './date-cloud.widget';
|
||||||
import { FormFieldModel, FormModel, FormFieldTypes, DateFieldValidator, MinDateFieldValidator, MaxDateFieldValidator } from '@alfresco/adf-core';
|
import { FormFieldModel, FormModel, FormFieldTypes, DEFAULT_DATE_FORMAT } from '@alfresco/adf-core';
|
||||||
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module';
|
||||||
import { DateAdapter } from '@angular/material/core';
|
import { DateAdapter } from '@angular/material/core';
|
||||||
import { isEqual, subDays, addDays } from 'date-fns';
|
import { isEqual, subDays, addDays } from 'date-fns';
|
||||||
@@ -35,7 +35,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
form = new FormModel();
|
form = new FormModel();
|
||||||
form.fieldValidators = [new DateFieldValidator(), new MinDateFieldValidator(), new MaxDateFieldValidator()];
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(DateCloudWidgetComponent);
|
fixture = TestBed.createComponent(DateCloudWidgetComponent);
|
||||||
adapter = fixture.debugElement.injector.get(DateAdapter);
|
adapter = fixture.debugElement.injector.get(DateAdapter);
|
||||||
@@ -52,9 +51,9 @@ describe('DateWidgetComponent', () => {
|
|||||||
minValue
|
minValue
|
||||||
});
|
});
|
||||||
|
|
||||||
widget.ngOnInit();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const expected = adapter.parse(minValue, widget.DATE_FORMAT);
|
const expected = adapter.parse(minValue, DEFAULT_DATE_FORMAT);
|
||||||
expect(isEqual(widget.minDate, expected)).toBeTrue();
|
expect(isEqual(widget.minDate, expected)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -77,9 +76,9 @@ describe('DateWidgetComponent', () => {
|
|||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
maxValue
|
maxValue
|
||||||
});
|
});
|
||||||
widget.ngOnInit();
|
fixture.detectChanges();
|
||||||
|
|
||||||
const expected = adapter.parse(maxValue, widget.DATE_FORMAT);
|
const expected = adapter.parse(maxValue, DEFAULT_DATE_FORMAT);
|
||||||
expect(isEqual(widget.maxDate, expected)).toBeTrue();
|
expect(isEqual(widget.maxDate, expected)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -95,7 +94,10 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
widget.onDateChanged({ value: adapter.today() } as any);
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
widget.dateInputControl.setValue(new Date('9999-9-9'));
|
||||||
|
|
||||||
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
expect(widget.onFieldChanged).toHaveBeenCalledWith(field);
|
||||||
});
|
});
|
||||||
@@ -106,17 +108,15 @@ describe('DateWidgetComponent', () => {
|
|||||||
TestBed.resetTestingModule();
|
TestBed.resetTestingModule();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should show visible date widget', async () => {
|
it('should show visible date widget', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
// always stored as dd-MM-yyyy
|
|
||||||
value: '9999-9-9',
|
value: '9999-9-9',
|
||||||
type: FormFieldTypes.DATE
|
type: FormFieldTypes.DATE
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement).not.toBeNull();
|
expect(dateElement).not.toBeNull();
|
||||||
@@ -128,10 +128,9 @@ describe('DateWidgetComponent', () => {
|
|||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
// always stored as dd-MM-yyyy
|
value: new Date('12-30-9999'),
|
||||||
value: '30-12-9999',
|
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dateDisplayFormat: 'YYYY-DD-MM'
|
dateDisplayFormat: 'yyyy-dd-MM'
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@@ -141,31 +140,29 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(dateElement.value).toContain('9999-30-12');
|
expect(dateElement.value).toContain('9999-30-12');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should disable date button when is readonly', async () => {
|
it('should disable date button when is readonly', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
value: '9999-9-9',
|
value: '9999-9-9',
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
readOnly: 'false'
|
readOnly: false
|
||||||
});
|
});
|
||||||
widget.field.isVisible = true;
|
widget.field.isVisible = true;
|
||||||
widget.field.readOnly = false;
|
widget.field.readOnly = false;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
let dateButton = element.querySelector<HTMLButtonElement>('button');
|
let dateButton = element.querySelector<HTMLButtonElement>('button');
|
||||||
expect(dateButton.disabled).toBeFalsy();
|
expect(dateButton.disabled).toBeFalsy();
|
||||||
|
|
||||||
widget.field.readOnly = true;
|
widget.field.readOnly = true;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
dateButton = element.querySelector<HTMLButtonElement>('button');
|
dateButton = element.querySelector<HTMLButtonElement>('button');
|
||||||
expect(dateButton.disabled).toBeTruthy();
|
expect(dateButton.disabled).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set isValid to false when the value is not a correct date value', async () => {
|
it('should set isValid to false when the value is not a correct date value', () => {
|
||||||
widget.field = new FormFieldModel(new FormModel(), {
|
widget.field = new FormFieldModel(new FormModel(), {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
@@ -177,42 +174,43 @@ describe('DateWidgetComponent', () => {
|
|||||||
widget.field.readOnly = false;
|
widget.field.readOnly = false;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
widget.dateInputControl.setValue(new Date('invalid date'));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(widget.field.isValid).toBeFalsy();
|
expect(widget.field.isValid).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should display always the json value', async () => {
|
it('should display always the json value', () => {
|
||||||
const field = new FormFieldModel(form, {
|
const field = new FormFieldModel(form, {
|
||||||
id: 'date-field-id',
|
id: 'date-field-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
// always stored as dd-MM-yyyy
|
value: new Date('12-30-9999'),
|
||||||
value: '30-12-9999',
|
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
readOnly: 'false',
|
readOnly: false,
|
||||||
dateDisplayFormat: 'MM-DD-YYYY'
|
dateDisplayFormat: 'MM-dd-yyyy'
|
||||||
});
|
});
|
||||||
widget.field = field;
|
widget.field = field;
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
const dateElement = element.querySelector<HTMLInputElement>('#date-field-id');
|
||||||
expect(dateElement).toBeDefined();
|
expect(dateElement).toBeDefined();
|
||||||
expect(dateElement.value).toContain('12-30-9999');
|
expect(dateElement.value).toContain('12-30-9999');
|
||||||
|
|
||||||
widget.field.value = '03-02-2020';
|
dateElement.value = '03-02-2020';
|
||||||
|
dateElement.dispatchEvent(new Event('input'));
|
||||||
|
|
||||||
fixture.componentInstance.ngOnInit();
|
fixture.componentInstance.ngOnInit();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(dateElement.value).toContain('02-03-2020');
|
expect(dateElement.value).toContain('03-02-2020');
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when form model has left labels', () => {
|
describe('when form model has left labels', () => {
|
||||||
it('should have left labels classes on leftLabels true', async () => {
|
it('should have left labels classes on leftLabels true', () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
@@ -223,7 +221,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
||||||
expect(widgetContainer).not.toBeNull();
|
expect(widgetContainer).not.toBeNull();
|
||||||
@@ -235,7 +232,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(adfLeftLabel).not.toBeNull();
|
expect(adfLeftLabel).not.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have left labels classes on leftLabels false', async () => {
|
it('should not have left labels classes on leftLabels false', () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: false }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: false }), {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
@@ -246,7 +243,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
||||||
expect(widgetContainer).toBeNull();
|
expect(widgetContainer).toBeNull();
|
||||||
@@ -258,7 +254,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(adfLeftLabel).toBeNull();
|
expect(adfLeftLabel).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not have left labels classes on leftLabels not present', async () => {
|
it('should not have left labels classes on leftLabels not present', () => {
|
||||||
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
|
||||||
id: 'date-id',
|
id: 'date-id',
|
||||||
name: 'date-name',
|
name: 'date-name',
|
||||||
@@ -269,7 +265,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
const widgetContainer = element.querySelector('.adf-left-label-input-container');
|
||||||
expect(widgetContainer).toBeNull();
|
expect(widgetContainer).toBeNull();
|
||||||
@@ -291,7 +286,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Minimum date range value and date', () => {
|
describe('Minimum date range value and date', () => {
|
||||||
it('should set minimum date range date to today if minimum date range value is 0', async () => {
|
it('should set minimum date range date to today if minimum date range value is 0', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -300,7 +295,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMinDate = adapter.today();
|
const expectedMinDate = adapter.today();
|
||||||
|
|
||||||
@@ -308,7 +302,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(widget.field.minValue).toBe(todayString);
|
expect(widget.field.minValue).toBe(todayString);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set minimum date range date to null if minimum date range value is null', async () => {
|
it('should set minimum date range date to null if minimum date range value is null', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -317,13 +311,12 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.minDate).toBeNull();
|
expect(widget.minDate).toBeNull();
|
||||||
expect(widget.field.minValue).toBeNull();
|
expect(widget.field.minValue).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set minimum date range date to today minus abs(minDateRangeValue) if minimum date range value is negative', async () => {
|
it('should set minimum date range date to today minus abs(minDateRangeValue) if minimum date range value is negative', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -332,7 +325,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMinDate = subDays(adapter.today(), 2);
|
const expectedMinDate = subDays(adapter.today(), 2);
|
||||||
|
|
||||||
@@ -340,7 +332,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(widget.field.minValue).toBe('20-02-2022');
|
expect(widget.field.minValue).toBe('20-02-2022');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set minimum date range date to today plus minDateRangeValue if minimum date range value is positive', async () => {
|
it('should set minimum date range date to today plus minDateRangeValue if minimum date range value is positive', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -349,7 +341,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMinDate = addDays(adapter.today(), 2);
|
const expectedMinDate = addDays(adapter.today(), 2);
|
||||||
|
|
||||||
@@ -359,7 +350,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('Maximum date range value and date', () => {
|
describe('Maximum date range value and date', () => {
|
||||||
it('should set maximum date range date to today if maximum date range value is 0', async () => {
|
it('should set maximum date range date to today if maximum date range value is 0', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -368,7 +359,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMaxDate = adapter.today();
|
const expectedMaxDate = adapter.today();
|
||||||
|
|
||||||
@@ -376,7 +366,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(widget.field.maxValue).toBe(todayString);
|
expect(widget.field.maxValue).toBe(todayString);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set maximum date range date to null if maximum date range value is null', async () => {
|
it('should set maximum date range date to null if maximum date range value is null', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -385,13 +375,12 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(widget.maxDate).toBeNull();
|
expect(widget.maxDate).toBeNull();
|
||||||
expect(widget.field.maxValue).toBeNull();
|
expect(widget.field.maxValue).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set maximum date range date to today minus abs(maxDateRangeValue) if maximum date range value is negative', async () => {
|
it('should set maximum date range date to today minus abs(maxDateRangeValue) if maximum date range value is negative', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -400,7 +389,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMaxDate = subDays(adapter.today(), 2);
|
const expectedMaxDate = subDays(adapter.today(), 2);
|
||||||
|
|
||||||
@@ -408,7 +396,7 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(widget.field.maxValue).toBe('20-02-2022');
|
expect(widget.field.maxValue).toBe('20-02-2022');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set maximum date range date to today plus maxDateRangeValue if maximum date range value is positive', async () => {
|
it('should set maximum date range date to today plus maxDateRangeValue if maximum date range value is positive', () => {
|
||||||
widget.field = new FormFieldModel(form, {
|
widget.field = new FormFieldModel(form, {
|
||||||
type: FormFieldTypes.DATE,
|
type: FormFieldTypes.DATE,
|
||||||
dynamicDateRangeSelection: true,
|
dynamicDateRangeSelection: true,
|
||||||
@@ -417,7 +405,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const expectedMaxDate = addDays(adapter.today(), 2);
|
const expectedMaxDate = addDays(adapter.today(), 2);
|
||||||
|
|
||||||
@@ -435,9 +422,8 @@ describe('DateWidgetComponent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be able to display label with asterisk', async () => {
|
it('should be able to display label with asterisk', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
|
||||||
|
|
||||||
@@ -445,9 +431,8 @@ describe('DateWidgetComponent', () => {
|
|||||||
expect(asterisk.textContent).toEqual('*');
|
expect(asterisk.textContent).toEqual('*');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be invalid after user interaction without typing', async () => {
|
it('should be invalid after user interaction without typing', () => {
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
expect(element.querySelector('.adf-invalid')).toBeFalsy();
|
||||||
|
|
||||||
@@ -455,7 +440,6 @@ describe('DateWidgetComponent', () => {
|
|||||||
dateCloudInput.dispatchEvent(new Event('blur'));
|
dateCloudInput.dispatchEvent(new Event('blur'));
|
||||||
|
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
await fixture.whenStable();
|
|
||||||
|
|
||||||
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
expect(element.querySelector('.adf-invalid')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@@ -17,15 +17,42 @@
|
|||||||
|
|
||||||
/* eslint-disable @angular-eslint/component-selector */
|
/* eslint-disable @angular-eslint/component-selector */
|
||||||
|
|
||||||
import { Component, OnInit, ViewEncapsulation, OnDestroy, Input } from '@angular/core';
|
import { Component, OnInit, ViewEncapsulation, OnDestroy, inject } from '@angular/core';
|
||||||
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { WidgetComponent, FormService, AdfDateFnsAdapter, DateFnsUtils, ADF_DATE_FORMATS } from '@alfresco/adf-core';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { MatDatepickerInputEvent } from '@angular/material/datepicker';
|
import {
|
||||||
import { addDays } from 'date-fns';
|
WidgetComponent,
|
||||||
|
FormService,
|
||||||
|
AdfDateFnsAdapter,
|
||||||
|
DateFnsUtils,
|
||||||
|
ADF_DATE_FORMATS,
|
||||||
|
ErrorWidgetComponent,
|
||||||
|
ErrorMessageModel,
|
||||||
|
DEFAULT_DATE_FORMAT
|
||||||
|
} from '@alfresco/adf-core';
|
||||||
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
|
import { addDays, parseISO } from 'date-fns';
|
||||||
|
import { FormControl, ReactiveFormsModule, ValidationErrors, Validators } from '@angular/forms';
|
||||||
|
import { NgIf } from '@angular/common';
|
||||||
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'date-widget',
|
selector: 'date-widget',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
NgIf,
|
||||||
|
TranslateModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatDatepickerModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
ErrorWidgetComponent
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS },
|
{ provide: MAT_DATE_FORMATS, useValue: ADF_DATE_FORMATS },
|
||||||
{ provide: DateAdapter, useClass: AdfDateFnsAdapter }
|
{ provide: DateAdapter, useClass: AdfDateFnsAdapter }
|
||||||
@@ -47,80 +74,143 @@ import { addDays } from 'date-fns';
|
|||||||
})
|
})
|
||||||
export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
|
export class DateCloudWidgetComponent extends WidgetComponent implements OnInit, OnDestroy {
|
||||||
typeId = 'DateCloudWidgetComponent';
|
typeId = 'DateCloudWidgetComponent';
|
||||||
readonly DATE_FORMAT = 'dd-MM-yyyy';
|
|
||||||
|
|
||||||
minDate: Date = null;
|
minDate: Date = null;
|
||||||
maxDate: Date = null;
|
maxDate: Date = null;
|
||||||
startAt: Date = null;
|
startAt: Date = null;
|
||||||
|
|
||||||
/**
|
dateInputControl: FormControl<Date> = new FormControl<Date>(null);
|
||||||
* Current date value.
|
|
||||||
* The value is always stored in the format `dd-MM-yyyy`,
|
|
||||||
* but displayed in the UI component using `dateDisplayFormat`
|
|
||||||
*/
|
|
||||||
@Input()
|
|
||||||
value: any = null;
|
|
||||||
|
|
||||||
private onDestroy$ = new Subject<boolean>();
|
private onDestroy$ = new Subject<void>();
|
||||||
|
|
||||||
constructor(public formService: FormService, private dateAdapter: DateAdapter<Date>) {
|
public readonly formService = inject(FormService);
|
||||||
super(formService);
|
private readonly dateAdapter = inject(DateAdapter);
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.patchFormControl();
|
||||||
|
this.initDateAdapter();
|
||||||
|
this.initRangeSelection();
|
||||||
|
this.initStartAt();
|
||||||
|
this.subscribeToDateChanges();
|
||||||
|
this.updateField();
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
updateField(): void {
|
||||||
if (this.field.dateDisplayFormat) {
|
this.validateField();
|
||||||
|
this.onFieldChanged(this.field);
|
||||||
|
}
|
||||||
|
|
||||||
|
private patchFormControl(): void {
|
||||||
|
this.dateInputControl.setValue(this.field.value, { emitEvent: false });
|
||||||
|
this.dateInputControl.setValidators(this.isRequired() ? [Validators.required] : []);
|
||||||
|
if (this.field?.readOnly || this.readOnly) {
|
||||||
|
this.dateInputControl.disable({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dateInputControl.updateValueAndValidity({ emitEvent: false });
|
||||||
|
}
|
||||||
|
|
||||||
|
private subscribeToDateChanges(): void {
|
||||||
|
this.dateInputControl.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((newDate: Date) => {
|
||||||
|
this.field.value = newDate;
|
||||||
|
this.updateField();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private validateField(): void {
|
||||||
|
if (this.dateInputControl.invalid) {
|
||||||
|
this.handleErrors(this.dateInputControl.errors);
|
||||||
|
this.field.markAsInvalid();
|
||||||
|
} else {
|
||||||
|
this.resetErrors();
|
||||||
|
this.field.markAsValid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleErrors(errors: ValidationErrors): void {
|
||||||
|
const errorAttributes = new Map<string, string>();
|
||||||
|
switch (true) {
|
||||||
|
case !!errors.matDatepickerParse:
|
||||||
|
this.updateValidationSummary(this.field.dateDisplayFormat || this.field.defaultDateTimeFormat);
|
||||||
|
break;
|
||||||
|
case !!errors.required:
|
||||||
|
this.updateValidationSummary('FORM.FIELD.REQUIRED');
|
||||||
|
break;
|
||||||
|
case !!errors.matDatepickerMin: {
|
||||||
|
const minValue = DateFnsUtils.formatDate(errors.matDatepickerMin.min, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('minValue', minValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_LESS_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case !!errors.matDatepickerMax: {
|
||||||
|
const maxValue = DateFnsUtils.formatDate(errors.matDatepickerMax.max, this.field.dateDisplayFormat).toLocaleUpperCase();
|
||||||
|
errorAttributes.set('maxValue', maxValue);
|
||||||
|
this.updateValidationSummary('FORM.FIELD.VALIDATOR.NOT_GREATER_THAN', errorAttributes);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateValidationSummary(message: string, attributes?: Map<string, string>): void {
|
||||||
|
this.field.validationSummary = new ErrorMessageModel({ message, attributes });
|
||||||
|
}
|
||||||
|
|
||||||
|
private resetErrors(): void {
|
||||||
|
this.updateValidationSummary('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private initDateAdapter(): void {
|
||||||
|
if (this.field?.dateDisplayFormat) {
|
||||||
const adapter = this.dateAdapter as AdfDateFnsAdapter;
|
const adapter = this.dateAdapter as AdfDateFnsAdapter;
|
||||||
adapter.displayFormat = this.field.dateDisplayFormat;
|
adapter.displayFormat = this.field.dateDisplayFormat;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.field) {
|
private initStartAt(): void {
|
||||||
if (this.field.dynamicDateRangeSelection) {
|
if (this.field?.value) {
|
||||||
if (this.field.minDateRangeValue === null) {
|
this.startAt = this.dateAdapter.parse(this.field.value, DEFAULT_DATE_FORMAT);
|
||||||
this.minDate = null;
|
|
||||||
this.field.minValue = null;
|
|
||||||
} else {
|
|
||||||
this.minDate = addDays(this.dateAdapter.today(), this.field.minDateRangeValue);
|
|
||||||
this.field.minValue = DateFnsUtils.formatDate(this.minDate, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
if (this.field.maxDateRangeValue === null) {
|
|
||||||
this.maxDate = null;
|
|
||||||
this.field.maxValue = null;
|
|
||||||
} else {
|
|
||||||
this.maxDate = addDays(this.dateAdapter.today(), this.field.maxDateRangeValue);
|
|
||||||
this.field.maxValue = DateFnsUtils.formatDate(this.maxDate, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (this.field.minValue) {
|
|
||||||
this.minDate = this.dateAdapter.parse(this.field.minValue, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.field.maxValue) {
|
|
||||||
this.maxDate = this.dateAdapter.parse(this.field.maxValue, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.field.value) {
|
|
||||||
this.startAt = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT);
|
|
||||||
this.value = this.dateAdapter.parse(this.field.value, this.DATE_FORMAT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
private initRangeSelection(): void {
|
||||||
this.onDestroy$.next(true);
|
if (this.field?.dynamicDateRangeSelection) {
|
||||||
|
this.setDynamicRangeSelection();
|
||||||
|
} else {
|
||||||
|
this.setStaticRangeSelection();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setDynamicRangeSelection(): void {
|
||||||
|
if (this.field.minDateRangeValue === null) {
|
||||||
|
this.minDate = null;
|
||||||
|
this.field.minValue = null;
|
||||||
|
} else {
|
||||||
|
this.minDate = addDays(this.dateAdapter.today(), this.field.minDateRangeValue);
|
||||||
|
this.field.minValue = DateFnsUtils.formatDate(this.minDate, DEFAULT_DATE_FORMAT);
|
||||||
|
}
|
||||||
|
if (this.field.maxDateRangeValue === null) {
|
||||||
|
this.maxDate = null;
|
||||||
|
this.field.maxValue = null;
|
||||||
|
} else {
|
||||||
|
this.maxDate = addDays(this.dateAdapter.today(), this.field.maxDateRangeValue);
|
||||||
|
this.field.maxValue = DateFnsUtils.formatDate(this.maxDate, DEFAULT_DATE_FORMAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setStaticRangeSelection(): void {
|
||||||
|
if (this.field?.minValue) {
|
||||||
|
this.minDate = parseISO(this.field.minValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.field?.maxValue) {
|
||||||
|
this.maxDate = parseISO(this.field.maxValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.onDestroy$.next();
|
||||||
this.onDestroy$.complete();
|
this.onDestroy$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
onDateChanged(event: MatDatepickerInputEvent<Date>) {
|
|
||||||
const value = event.value;
|
|
||||||
const input = event.targetElement as HTMLInputElement;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
this.field.value = this.dateAdapter.format(value, this.DATE_FORMAT);
|
|
||||||
} else {
|
|
||||||
this.field.value = input.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.onFieldChanged(this.field);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -29,8 +29,6 @@ import {
|
|||||||
CONTENT_UPLOAD_DIRECTIVES,
|
CONTENT_UPLOAD_DIRECTIVES,
|
||||||
ContentNodeSelectorModule
|
ContentNodeSelectorModule
|
||||||
} from '@alfresco/adf-content-services';
|
} from '@alfresco/adf-content-services';
|
||||||
|
|
||||||
import { DateCloudWidgetComponent } from './components/widgets/date/date-cloud.widget';
|
|
||||||
import { DropdownCloudWidgetComponent } from './components/widgets/dropdown/dropdown-cloud.widget';
|
import { DropdownCloudWidgetComponent } from './components/widgets/dropdown/dropdown-cloud.widget';
|
||||||
import { GroupCloudWidgetComponent } from './components/widgets/group/group-cloud.widget';
|
import { GroupCloudWidgetComponent } from './components/widgets/group/group-cloud.widget';
|
||||||
import { PeopleCloudWidgetComponent } from './components/widgets/people/people-cloud.widget';
|
import { PeopleCloudWidgetComponent } from './components/widgets/people/people-cloud.widget';
|
||||||
@@ -78,7 +76,6 @@ import { FormCloudSpinnerService } from './services/spinner/form-cloud-spinner.s
|
|||||||
DropdownCloudWidgetComponent,
|
DropdownCloudWidgetComponent,
|
||||||
RadioButtonsCloudWidgetComponent,
|
RadioButtonsCloudWidgetComponent,
|
||||||
AttachFileCloudWidgetComponent,
|
AttachFileCloudWidgetComponent,
|
||||||
DateCloudWidgetComponent,
|
|
||||||
PeopleCloudWidgetComponent,
|
PeopleCloudWidgetComponent,
|
||||||
GroupCloudWidgetComponent,
|
GroupCloudWidgetComponent,
|
||||||
PropertiesViewerWrapperComponent,
|
PropertiesViewerWrapperComponent,
|
||||||
@@ -96,7 +93,6 @@ import { FormCloudSpinnerService } from './services/spinner/form-cloud-spinner.s
|
|||||||
DropdownCloudWidgetComponent,
|
DropdownCloudWidgetComponent,
|
||||||
RadioButtonsCloudWidgetComponent,
|
RadioButtonsCloudWidgetComponent,
|
||||||
AttachFileCloudWidgetComponent,
|
AttachFileCloudWidgetComponent,
|
||||||
DateCloudWidgetComponent,
|
|
||||||
PeopleCloudWidgetComponent,
|
PeopleCloudWidgetComponent,
|
||||||
GroupCloudWidgetComponent,
|
GroupCloudWidgetComponent,
|
||||||
PropertiesViewerWidgetComponent,
|
PropertiesViewerWidgetComponent,
|
||||||
|
Reference in New Issue
Block a user