UNPKG

ngx-daterangepicker

Version:
459 lines (393 loc) 14.3 kB
import { Component, OnInit, HostListener, ElementRef, forwardRef, Input, OnChanges, SimpleChange, ViewChild, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '@angular/forms'; import * as dateFns from 'date-fns'; import {locales} from './constants' export interface NgxDateRangePickerDates { from: { year: number, month: number, day: number } to: { year: number, month: number, day: number } } export interface NgxMenuItem { alias: string; text: string; operation: string; active?: boolean; } export interface NgxDateRangePickerOptions { theme: 'default' | 'green' | 'teal' | 'cyan' | 'grape' | 'red' | 'gray'; range?: string; locale?: string; labels: string[]; menu: NgxMenuItem[]; dateFormat: string; outputFormat: string; startOfWeek: number; outputType?: 'string' | 'object'; date?: NgxDateRangePickerDates; mobilePx?: number; } export interface IDay { date: Date; day: number; weekday: number; today: boolean; firstMonthDay: boolean; lastMonthDay: boolean; visible: boolean; from: boolean; to: boolean; isWithinRange: boolean; } export let DATERANGEPICKER_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NgxDateRangePickerComponent), multi: true }; @Component({ selector: 'ngx-daterangepicker', templateUrl: 'ngx-daterangepicker.component.html', styleUrls: ['ngx-daterangepicker.sass'], providers: [DATERANGEPICKER_VALUE_ACCESSOR] }) export class NgxDateRangePickerComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnChanges { @ViewChild('fromInput') fromInput: ElementRef; @Input() options: NgxDateRangePickerOptions; @Input() set isOpen(open: false | 'from' | 'to') { if (!open) { this._opened = false; } else if (open !== 'from' && open !== 'to') { this._opened = open; } else { this._opened = 'from'; } } public mobileMode = false; get isOpen(): false | 'from' | 'to' { return this._opened; } modelValue: string|Object; _opened: false | 'from' | 'to'; date: Date; dateFrom: Date; dateTo: Date; dayNames: string[]; days: IDay[]; range: string; defaultOptions: NgxDateRangePickerOptions = { theme: 'default', labels: ['Start', 'End'], locale: 'en', menu: [], dateFormat: 'DD-MM-YYYY', outputFormat: 'DD-MM-YYYY', outputType: 'string', startOfWeek: 1, date: null, mobilePx: 545 }; arrowLeft: number; private onTouchedCallback: () => void = () => { }; private onChangeCallback: (_: any) => void = () => { }; constructor(private elementRef: ElementRef, private cdr: ChangeDetectorRef) { } get value(): string|Object { return this.modelValue; } set value(value: string|Object) { if (!value) { return; } this.modelValue = value; this.onChangeCallback(value); } writeValue(value: string) { if (!value) { return; } this.modelValue = value; } registerOnChange(fn: any) { this.onChangeCallback = fn; } registerOnTouched(fn: any) { this.onTouchedCallback = fn; } ngAfterViewInit(): void { this.arrowLeft = this.fromInput.nativeElement.offsetWidth; this.cdr.detectChanges(); } @HostListener('window:resize', ['$event']) onResize() { this.mobileMode = !!(this.options.mobilePx && window.innerWidth < this.options.mobilePx); console.log(this.mobileMode) } ngOnInit() { this._opened = false; this.defaultOptions.date = { from: { year: dateFns.getYear(this.date), month: dateFns.getMonth(this.date), day: dateFns.getDay(this.date) }, to: { year: dateFns.getYear(this.date), month: dateFns.getMonth(this.date), day: dateFns.getDay(dateFns.addDays(this.date, 1)) } }; this.options = this.options || this.defaultOptions; this.options.mobilePx = this.options.mobilePx == undefined ? this.defaultOptions.mobilePx : this.options.mobilePx; this.initNames(); this.onResize(); ; if (this.options.range) { this.selectRange(this.options.menu.filter((item) => { return this.options.range === item.alias; })[0]); } else { if (!this.options.date) { this.options.date = this.defaultOptions.date; } this.selectDates(this.options.date); } } ngOnChanges(changes: {[propName: string]: SimpleChange}) { this.options = this.options || this.defaultOptions; if (this.options.date) { this.selectDates(this.options.date); } this.initNames(); } initNames(): void { this.dayNames = []; for (let i = 1; i < 7; ++i) { this.dayNames.push(this.getDayOfWeek(i)); } this.dayNames.push(this.getDayOfWeek(0)); } getDayOfWeek(day: number): string { let date = new Date(); let dayOfWeek = dateFns.format(dateFns.setDay(date, day, {weekStartsOn: 1}), "dd", { locale: locales[this.options.locale]}); return dayOfWeek[0].toUpperCase() + dayOfWeek.substring(1); } generateCalendar(): void { this.days = []; let start: Date = dateFns.startOfMonth(this.date); let end: Date = dateFns.endOfMonth(this.date); let days: IDay[] = dateFns.eachDay(start, end).map(d => { let startOfWeek = this.options.startOfWeek; let endOfWeek = startOfWeek === 0 ? 6 : 0; return { date: d, day: dateFns.getDate(d), weekday: dateFns.getDay(d), startOfWeek, endOfWeek, today: dateFns.isToday(d), firstMonthDay: dateFns.isFirstDayOfMonth(d), lastMonthDay: dateFns.isLastDayOfMonth(d), visible: true, from: dateFns.isSameDay(this.dateFrom, d), to: dateFns.isSameDay(this.dateTo, d), isWithinRange: dateFns.isWithinRange(d, this.dateFrom, this.dateTo) }; }); let prevMonthDayNum = dateFns.getDay(start) - 1; let prevMonthDays: IDay[] = []; if (prevMonthDayNum > 0) { prevMonthDays = Array.from(Array(prevMonthDayNum).keys()).map(i => { let d = dateFns.subDays(start, prevMonthDayNum - i); return { date: d, day: dateFns.getDate(d), weekday: dateFns.getDay(d), firstMonthDay: dateFns.isFirstDayOfMonth(d), lastMonthDay: dateFns.isLastDayOfMonth(d), today: false, visible: false, from: false, to: false, isWithinRange: false }; }); } this.days = prevMonthDays.concat(days); if (this.options.outputType === 'object') { this.value = { from: dateFns.format(this.dateFrom, this.options.outputFormat), to: dateFns.format(this.dateTo, this.options.outputFormat) }; } else { this.value = `${dateFns.format(this.dateFrom, this.options.outputFormat)}-${dateFns.format(this.dateTo, this.options.outputFormat)}`; } } toggleCalendar(e: MouseEvent, selection: 'from' | 'to'): void { // Arrow position if (selection === 'from') { this.arrowLeft = this.fromInput.nativeElement.offsetWidth * 0.4; } else { this.arrowLeft = this.fromInput.nativeElement.offsetWidth + this.fromInput.nativeElement.offsetWidth * 0.4; } if (this._opened && this._opened !== selection) { this._opened = selection; } else { this._opened = this._opened ? false : selection; } } closeCalendar(e: MouseEvent): void { this._opened = false; } selectDate(e: MouseEvent, index: number): void { e.preventDefault(); let selectedDate: Date = this.days[index].date; if ((this._opened === 'to' && dateFns.isBefore(selectedDate, this.dateFrom))) { this._opened = 'from'; } if ((this._opened === 'from' && dateFns.isAfter(selectedDate, this.dateTo))) { this.dateFrom = selectedDate; this.dateTo = selectedDate; } if (this._opened === 'from') { this.dateFrom = selectedDate; this._opened = 'to'; } else if (this._opened === 'to') { this.dateTo = selectedDate; this._opened = 'from'; } if (this._opened === 'from') { this.arrowLeft = this.fromInput.nativeElement.offsetWidth * 0.4; } else { this.arrowLeft = this.fromInput.nativeElement.offsetWidth + this.fromInput.nativeElement.offsetWidth * 0.4; } if (this.options.menu && this.options.menu.length > 0) { this.options.menu.map((item) => { item.active = false; }); } this.generateCalendar(); } prevMonth(): void { this.date = dateFns.subMonths(this.date, 1); this.generateCalendar(); } nextMonth(): void { this.date = dateFns.addMonths(this.date, 1); this.generateCalendar(); } selectDates(dates: NgxDateRangePickerDates): void { this.dateFrom = dateFns.startOfDay(new Date(dates.from.year, dates.from.month - 1, dates.from.day)); this.dateTo = dateFns.startOfDay(new Date(dates.to.year, dates.to.month - 1, dates.to.day)); if (dateFns.isAfter(this.dateFrom, this.dateTo)) { this.dateTo = this.dateFrom; } this.date = dateFns.startOfDay(this.dateFrom); this.generateCalendar(); } selectRange(range: NgxMenuItem): void { let today = dateFns.startOfDay(new Date()); let fromDate = today; let toDate = today; this.options.menu.map((item) => { item.active = item.alias === range.alias; }); let operand = range.operation.charAt(0) === '-' ? -1 : 1; let amount = Math.abs(parseInt(range.operation, 10)); let ope = range.operation.match(/[d,w,m,y]t?/); let unit = ope.length > 0 ? ope[0] : ''; switch (unit) { case 'm': if (amount) { fromDate = dateFns.addMonths(fromDate, amount * operand); toDate = dateFns.addMonths(fromDate, (amount - 1)); } this.dateFrom = dateFns.startOfMonth(fromDate); this.dateTo = dateFns.endOfMonth(toDate); break; case 'w': if (amount) { fromDate = dateFns.addWeeks(fromDate, amount * operand); toDate = dateFns.addWeeks(fromDate, (amount - 1)); } this.dateFrom = dateFns.startOfWeek(fromDate, {weekStartsOn: this.options.startOfWeek}); this.dateTo = dateFns.endOfWeek(toDate, {weekStartsOn: this.options.startOfWeek}); break; case 'y': if (amount) { fromDate = dateFns.addYears(fromDate, amount * operand); toDate = dateFns.addYears(fromDate, (amount - 1)); } this.dateFrom = dateFns.startOfYear(fromDate); this.dateTo = dateFns.endOfYear(toDate); break; case 'd': if (amount) { fromDate = dateFns.addDays(fromDate, amount * operand); toDate = dateFns.addDays(fromDate, (amount - 1)); } this.dateFrom = dateFns.startOfDay(fromDate); this.dateTo = dateFns.startOfDay(toDate); break; // From today case 'mt': if (operand < 0) { fromDate = dateFns.subMonths(today, amount); } else { toDate = dateFns.addMonths(today, amount); } this.dateFrom = fromDate; this.dateTo = toDate; break; case 'wt': if (operand < 0) { fromDate = dateFns.subWeeks(today, amount); } else { toDate = dateFns.addWeeks(today, amount); } this.dateFrom = fromDate; this.dateTo = toDate; break; case 'yt': if (operand < 0) { fromDate = dateFns.subYears(today, amount); } else { toDate = dateFns.addYears(today, amount); } this.dateFrom = fromDate; this.dateTo = toDate; break; default: if (operand < 0) { fromDate = dateFns.subDays(today, amount); } else { toDate = dateFns.addDays(today, amount); } this.dateFrom = fromDate; this.dateTo = toDate; break; } this.date = dateFns.startOfDay(this.dateFrom); this.range = range.alias; this.generateCalendar(); } @HostListener('document:click', ['$event']) handleBlurClick(e: MouseEvent) { let target = e.srcElement || e.target; if (!this.elementRef.nativeElement.contains(e.target) && !(<Element>target).classList.contains('day-num')) { this._opened = false; } } }