Last active
March 12, 2019 16:45
-
-
Save alexnoise79/47e67ffe6d055c6821a9fa27fef1a8f4 to your computer and use it in GitHub Desktop.
Angular2 date-selector component, made of 3 options
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
date-selector { | |
@include flexbox((display: flex)); | |
width: 100%; | |
select { | |
width: auto; | |
&.month { | |
@include flex(1 0 auto); | |
margin: 0 6px; | |
} | |
&.day, | |
&.year { | |
@include flex(0 1 auto); | |
} | |
} | |
&.ng-invalid.ng-touched { | |
background-color: transparent; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<select class="day" [(ngModel)]="date" (ngModelChange)="modelChanged($event, 'date')" [disabled]="isDisabled" (blur)="onBlur('date')" minlength="1" required> | |
<option [value]="null" *ngIf="!date">day</option> | |
<option [ngValue]="d" *ngFor="let d of dates">{{d}}</option> | |
</select> | |
<select class="month" [(ngModel)]="month" (ngModelChange)="modelChanged($event, 'month')" [disabled]="isDisabled" (blur)="onBlur('month')" required> | |
<option [value]="null" *ngIf="!month">month</option> | |
<option [ngValue]="z" *ngFor="let z of months">{{z.name}}</option> | |
</select> | |
<select class="year" [(ngModel)]="year" (ngModelChange)="modelChanged($event, 'year')" [disabled]="isDisabled" (blur)="onBlur('year')" minlength="4" required> | |
<option [value]="null" *ngIf="!year">year</option> | |
<option [ngValue]="y" *ngFor="let y of years">{{y}}</option> | |
</select> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Component, forwardRef, Input, OnInit } from '@angular/core'; | |
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; | |
import { TranslateService } from '@ngx-translate/core'; | |
interface Month { | |
name: string; | |
value: number; | |
} | |
@Component({ | |
selector: 'date-selector', | |
templateUrl: './date-selector.component.html', | |
providers: [ | |
{ | |
provide: NG_VALUE_ACCESSOR, | |
useExisting: forwardRef(() => DateSelectorComponent), | |
multi: true | |
} | |
] | |
}) | |
export class DateSelectorComponent implements OnInit, ControlValueAccessor { | |
dates: Array<number>; | |
months: Array<Month>; | |
years: Array<number>; | |
allMonths: Array<Month>; | |
year: number; | |
month: Month; | |
date: number; | |
modelDate: Date; | |
isDisabled: boolean; | |
touched: any; | |
@Input() | |
min: Date; | |
@Input() | |
max: Date; | |
constructor(translate: TranslateService) { | |
this.dates = new Array<number>(); | |
this.years = new Array<number>(); | |
this.months = new Array<Month>(); | |
this.touched = {}; | |
this.allMonths = this.getMonthsName(translate.getBrowserCultureLang(), 'long'); | |
} | |
writeValue(model: Date): void { | |
if (model) { | |
if (model && model < this.min || model > this.max) { | |
throw new Error('Provided date is not in range'); | |
} | |
this.modelDate = new Date(model); | |
if (this.modelDate) { | |
this.date = this.modelDate.getDate(); | |
this.month = this.months.find(month => month.value === this.modelDate.getMonth() + 1); | |
this.year = this.modelDate.getFullYear(); | |
} else { | |
this.date = this.month = this.year = null; | |
} | |
this.months = this.updateMonths(this.modelDate.getFullYear()); | |
} else { | |
this.date = this.month = this.year = null; | |
} | |
} | |
registerOnChange(fn: any): void { | |
this._onChange = fn; | |
} | |
registerOnTouched(fn: any): void { | |
this._onTouched = fn; | |
} | |
setDisabledState(isDisabled: boolean): void { | |
this.isDisabled = isDisabled; | |
} | |
ngOnInit() { | |
this.min = this.min ? new Date(this.min) : this.defaults(true); | |
this.max = this.max ? new Date(this.max) : this.defaults(false); | |
this.dates = this.updateDates(); | |
this.months = this.updateMonths(); | |
this.years = this.updateYears(); | |
} | |
onBlur(which: string) { | |
this.touched[which] = true; | |
if (this.touched.date && this.touched.month && this.touched.year) { | |
this._onTouched(); | |
} | |
} | |
modelChanged(event: any, select: string) { | |
switch (select) { | |
case 'year': | |
this.months = this.updateMonths(event); | |
break; | |
case 'month': | |
this.dates = this.updateDates(event); | |
break; | |
case 'date': | |
this.updateModel(); | |
break; | |
} | |
} | |
private getDaysInMonth = function (year, month): number { | |
return [31, (this.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; | |
}; | |
private _onChange(_model: string) { | |
} | |
private _onTouched() { | |
} | |
private pad(n) { | |
return n < 10 ? '0' + n : n; | |
} | |
private updateModel() { | |
if (this.year && this.month && this.date) { | |
const ISODate = this.pad(this.year) + '-' + this.pad(this.month.value) + '-' + this.pad(this.date); | |
this._onChange(new Date(ISODate).toISOString()); | |
} | |
} | |
private isLeapYear(year): boolean { | |
return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); | |
} | |
private defaults(isMin: boolean): Date { | |
const m = new Date(); | |
const y = isMin ? m.getFullYear() - 100 : m.getFullYear() + 50; | |
m.setFullYear(y); | |
return m; | |
} | |
private updateYears(): Array<number> { | |
const years = new Array<number>(); | |
for (let i = this.min.getFullYear(); i <= this.max.getFullYear(); i++) { | |
years.push(i); | |
} | |
return years; | |
} | |
private updateMonths(year?: number): Array<Month> { | |
let months = new Array<Month>(); | |
if (year === this.min.getFullYear() || year === this.max.getFullYear()) { | |
const minMonth = year && (this.min.getFullYear() === year) ? this.min.getMonth() : 0; | |
const maxMonth = year && (this.max.getFullYear() === year) ? this.max.getMonth() : 11; | |
for (let j = minMonth; j <= maxMonth; j++) { | |
months.push(this.allMonths[j]); | |
} | |
const sel = this.modelDate.getMonth() ? months.find(m => m.value === this.modelDate.getMonth() + 1) : months[0]; | |
this.dates = this.updateDates(sel); | |
} else { | |
this.dates = this.updateDates(this.month); | |
months = this.allMonths; | |
} | |
return months; | |
} | |
private getMonthsName(locale, size) { | |
const format = new Intl.DateTimeFormat(locale, {month: size}); | |
const months = []; | |
for (let month = 0; month < 12; month++) { | |
const testDate = new Date(Date.UTC(2000, month, 1, 0, 0, 0)); | |
months.push({name: format.format(testDate), value: month + 1}); | |
} | |
return months; | |
} | |
private updateDates(month?: Month): Array<number> { | |
let minDate, maxDate; | |
if (month) { | |
this.month = month; | |
} | |
const dates = new Array<number>(); | |
if (this.year && month && (this.min.getFullYear() === this.year && this.min.getMonth() === month.value - 1)) { | |
minDate = this.min.getDate(); | |
} else { | |
minDate = 1; | |
} | |
if (this.year && month && (this.max.getFullYear() === this.year && this.max.getMonth() === month.value - 1)) { | |
maxDate = this.max.getDate(); | |
} else if (this.year && month) { | |
maxDate = this.getDaysInMonth(this.year, month.value - 1); | |
} else { | |
maxDate = 31; | |
} | |
for (let i = minDate; i <= maxDate; i++) { | |
dates.push(i); | |
} | |
if (this.date > maxDate || this.date < minDate) { | |
this.date = 1; | |
} | |
this.updateModel(); | |
return dates; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment