Skip to content

Commit

Permalink
Merge pull request #1258 from cnomes/feature/stark-date-range-picker-…
Browse files Browse the repository at this point in the history
…MatFormFieldControl

feat(stark-ui): implement `ControlValueAccessor` on `DateRangePicker` component
  • Loading branch information
christophercr authored Jun 5, 2019
2 parents c300ca8 + 6f7fb72 commit 9429f4f
Show file tree
Hide file tree
Showing 26 changed files with 1,659 additions and 531 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import {
} from "@angular/core";
import { MatDatepicker, MatDatepickerInput, MatDatepickerInputEvent } from "@angular/material/datepicker";
import moment from "moment";
import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core";
import { AbstractControl, ControlValueAccessor, NG_VALIDATORS, NgControl, ValidationErrors, Validator } from "@angular/forms";
import { isStarkTimestampMaskConfig, StarkTimestampMaskConfig } from "../../input-mask-directives";
import { MAT_DATE_FORMATS, MatDateFormats } from "@angular/material/core";
import { MatFormFieldControl } from "@angular/material/form-field";
import { AbstractStarkUiComponent } from "../../../common/classes/abstract-component";
import { Subject, Subscription } from "rxjs";
import { FocusMonitor, FocusOrigin } from "@angular/cdk/a11y";
import { coerceBooleanProperty } from "@angular/cdk/coercion";
import { TranslateService } from "@ngx-translate/core";
import { Subject, Subscription } from "rxjs";
import { STARK_LOGGING_SERVICE, StarkLoggingService } from "@nationalbankbelgium/stark-core";
import { isStarkTimestampMaskConfig, StarkTimestampMaskConfig } from "../../input-mask-directives/directives";
import { AbstractStarkUiComponent } from "../../../common/classes/abstract-component";
import isEqual from "lodash-es/isEqual";

/**
Expand Down Expand Up @@ -179,13 +179,36 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
* Dynamically translated via the @ngx-translate service if the provided text is defined in the translation keys).
*/
@Input()
public placeholder = "";
public set placeholder(value: string) {
this.originalPlaceholder = value || "";
// Handle translation internally because mat-form-field uses the value of `@Input public placeholder` to display the label / placeholder
this._placeholder = this.originalPlaceholder ? this.translateService.instant(this.originalPlaceholder) : this.originalPlaceholder;
this.stateChanges.next();
}

public get placeholder(): string {
return this._placeholder;
}

private _placeholder = "";

/**
* If the date-picker is required or not. by default, the date-picker is not required
*/
@Input()
public required = false;
public get required(): boolean {
return this._required;
}

public set required(isRequired: boolean) {
this._required = coerceBooleanProperty(isRequired);
}

/**
* @ignore
* @internal
*/
private _required = false;

/**
* Source Date to be bound to the datepicker model
Expand Down Expand Up @@ -361,8 +384,6 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
* Component lifecycle hook
*/
public ngOnInit(): void {
this.logger.debug(componentName + ": component initialized");

// tslint:disable-next-line:no-null-keyword
this.ngControl = this.injector.get<NgControl>(<Type<NgControl>>NgControl, <any>null);

Expand All @@ -371,14 +392,12 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
}

this.translateOnLangChangeSubscription = this.translateService.onLangChange.subscribe(() => {
// Handle translation internally because mat-form-field uses the value of `@Input public placeholder` to display the label / placeholder
this.placeholder = this.originalPlaceholder
? this.translateService.instant(this.originalPlaceholder)
: this.originalPlaceholder;
this.stateChanges.next();
// re-assign the placeholder to refresh the translation (see 'placeholder' setter)
this.placeholder = this.originalPlaceholder;
});

super.ngOnInit();
this.logger.debug(componentName + ": component initialized");
}

/**
Expand All @@ -401,28 +420,19 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
*/
// tslint:disable-next-line:cognitive-complexity
public ngOnChanges(changes: SimpleChanges): void {
if (this.max && this.min && (changes["max"] || changes["min"]) && this.max < this.min) {
if ((changes["max"] || changes["min"]) && this.max && this.min && this.max.getTime() < this.min.getTime()) {
this.logger.error(
componentName + ": min date [" + this.min.toDateString() + "] cannot be after max date [" + this.max.toDateString() + "]"
);
}

if (changes["required"]) {
this.required = coerceBooleanProperty(changes["required"].currentValue);
}

if (changes["max"] || changes["min"] || changes["required"]) {
this.stateChanges.next();
this.cdRef.detectChanges();
this._onValidatorChange();
}

if (changes["placeholder"]) {
this.originalPlaceholder = changes["placeholder"].currentValue || "";
// Handle translation internally because mat-form-field uses the value of `@Input public placeholder` to display the label / placeholder
this.placeholder = this.originalPlaceholder
? this.translateService.instant(this.originalPlaceholder)
: this.originalPlaceholder;
// IMPORTANT: the '_onValidatorChange()' callback from Validator API should not be called here to update the validity of the control because it triggers a valueChange event!
// therefore we call 'updateValueAndValidity()' manually on the control instead and without emitting the valueChanges event :)
if (this.ngControl && this.ngControl.control) {
this.ngControl.control.updateValueAndValidity({ emitEvent: false });
}
this.stateChanges.next();
}

Expand Down Expand Up @@ -453,7 +463,7 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
componentName +
': dateMask.format ["' +
this.dateMaskConfig.format +
'"] and provided dateFormats ["' +
'"] and the provided parse format(s) in MAT_DATE_FORMATS ["' +
dateInputFormats.join('","') +
'"] are NOT compatible. Please adapt one of them.'
);
Expand Down Expand Up @@ -492,15 +502,6 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
/*noop*/
};

/**
* @ignore
* @internal
* The registered callback function called when the validator inputs change.
*/
private _onValidatorChange: () => void = () => {
/*noop*/
};

/**
* Part of {@link ControlValueAccessor} API
* Registers a function to be called when the control value changes.
Expand Down Expand Up @@ -530,7 +531,6 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
public setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
this.stateChanges.next();
this._onValidatorChange();
}

/**
Expand Down Expand Up @@ -567,20 +567,19 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent
}

/**
* Registers a callback function to call when the validator inputs change.
*
* @param fn - The callback function
* Part of {@link Validator} API
* @ignore
* @internal
*/
public registerOnValidatorChange(fn: () => void): void {
this._onValidatorChange = fn;
public registerOnValidatorChange(_fn: () => void): void {
// we don't need to keep a reference to the callback function (i.e. in a '_onValidatorChange' property)
// because such callback, when it is called to update the validity of the control, it triggers a valueChange event too!
}

/**
* Method that performs synchronous validation against the provided control.
*
* @param control - The control to validate against.
*
* @returns A map of validation errors if validation fails, otherwise null.
* Part of {@link Validator} API
* @ignore
* @internal
*/
public validate(control: AbstractControl): ValidationErrors | null {
return this.pickerInput.validate(control);
Expand Down Expand Up @@ -633,7 +632,6 @@ export class StarkDatePickerComponent extends AbstractStarkUiComponent

const value: Date | undefined = this.value ? this.value : undefined;
this._onChange(value);
this._onValidatorChange();
// emit after the model has actually changed
this.dateChange.emit(value);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/* ============================================================================== */
/* stark-ui: src/modules/date-range-picker/components/date-range-picker/_date-range-picker.component.scss */

.stark-date-range-picker stark-date-picker:first-child {
.stark-date-range-picker mat-form-field {
margin-right: 16px;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,37 @@
#startPicker
[pickerId]="rangePickerId + '-start'"
[pickerName]="rangePickerName + '-start'"
[value]="startDate"
[dateFilter]="dateFilter"
[disabled]="isDisabled"
[formControl]="startDateFormControl"
[placeholder]="startDateLabel | translate"
[dateFilter]="dateFilter"
[dateMask]="dateMask"
[min]="startMinDate"
[max]="startMaxDate"
[dateMask]="dateMask"
(dateChange)="onDateStartChanged($event)"
[required]="required"
>
</stark-date-picker>
<!-- <mat-error> should always be a direct child of <mat-form-field> -->
<mat-error>
<ng-content select="[start-date-errors]"></ng-content>
</mat-error>
</mat-form-field>

<mat-form-field>
<stark-date-picker
#endPicker
[pickerId]="rangePickerId + '-end'"
[pickerName]="rangePickerName + '-end'"
[value]="endDate"
[dateFilter]="dateFilter"
[disabled]="isDisabled"
[formControl]="endDateFormControl"
[placeholder]="endDateLabel | translate"
[dateFilter]="dateFilter"
[dateMask]="dateMask"
[min]="endMinDate"
[max]="endMaxDate"
[dateMask]="dateMask"
(dateChange)="onDateEndChanged($event)"
[required]="required"
>
</stark-date-picker>
<!-- <mat-error> should always be a direct child of <mat-form-field> -->
<mat-error>
<ng-content select="[end-date-errors]"></ng-content>
</mat-error>
</mat-form-field>
Loading

0 comments on commit 9429f4f

Please sign in to comment.