/** * picker.component */ import { AfterViewInit, Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output, Renderer2, ViewChild } from '@angular/core'; import { animate, state, style, transition, trigger } from '@angular/animations'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { parse, isValid, startOfMonth, getDate, getDay, addDays, addMonths, isSameDay, isSameMonth, isToday, getMonth, setMonth, getYear, addYears, differenceInCalendarDays, setYear, getHours, setHours, getMinutes, setMinutes, getSeconds, setSeconds, isBefore, isAfter, compareAsc, startOfDay, format, endOfDay, } from 'date-fns'; import { NumberFixedLenPipe } from './numberedFixLen.pipe'; export interface LocaleSettings { firstDayOfWeek?: number; dayNames: string[]; dayNamesShort: string[]; monthNames: string[]; monthNamesShort: string[]; dateFns: any; confirm: string; } export enum DialogType { Time, Date, Month, Year, } export const DATETIMEPICKER_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DateTimePickerComponent), multi: true }; @Component({ selector: 'plx-datepicker', templateUrl: './picker.component.html', styleUrls: ['./picker.component.less'], providers: [NumberFixedLenPipe, DATETIMEPICKER_VALUE_ACCESSOR], animations: [ trigger('fadeInOut', [ state('hidden', style({ opacity: 0, display: 'none' })), state('visible', style({ opacity: 1, display: 'block' })), transition('visible => hidden', animate('200ms ease-in')), transition('hidden => visible', animate('400ms ease-out')) ]) ], }) export class DateTimePickerComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor { @ViewChild('timepicker') public timepicker; /** * Type of the value to write back to ngModel * @default 'date' -- Javascript Date type * @type {'string' | 'date'} * */ @Input() dataType: 'string' | 'date' = 'date'; /** * Format of the date * @default 'y/MM/dd' * @type {String} * */ @Input() dateFormat: string = 'YYYY-MM-DD HH:mm'; /** * When present, it specifies that the component should be disabled * @default false * @type {Boolean} * */ @Input() disabled: boolean; /** * @default false * @type {Boolean} * */ @Input() supportKeyboardInput: boolean = false; /** * Array with dates that should be disabled (not selectable) * @default null * @type {Date[]} * */ @Input() disabledDates: Date[] = []; /** * Array with weekday numbers that should be disabled (not selectable) * @default null * @type {number[]} * */ @Input() disabledDays: number[]; /** * When enabled, displays the calendar as inline * @default false -- popup mode * @type {boolean} * */ @Input() inline: boolean; /** * Identifier of the focus input to match a label defined for the component * @default null * @type {String} * */ @Input() inputId: string; /** * Inline style of the picker text input * @default null * @type {any} * */ @Input() inputStyle: any; /** * Style class of the picker text input * @default null * @type {String} * */ @Input() inputStyleClass: string; /** * Maximum number of selectable dates in multiple mode * @default null * @type {number} * */ @Input() maxDateCount: number; /** * The minimum selectable date time * @default null * @type {Date | string} * */ private _max: Date; @Input() get max() { return this._max; } set max(val: Date | string) { this._max = this.parseToDate(val); } @Input() get maxDate() { return this._max; } set maxDate(val: Date | string) { this._max = this.parseToDate(val); } /** * The maximum selectable date time * @default null * @type {Date | string } * */ private _min: Date; @Input() get min() { return this._min; } set min(val: Date | string) { this._min = this.parseToDate(val); } @Input() get minDate() { return this._min; } set minDate(val: Date | string) { this._min = this.parseToDate(val); } @Input() get dateValue() { return this.value; } set dateValue(val: Date | string) { let newvalue = this.parseToDate(val); if(newvalue!==undefined) { this.updateModel(newvalue); this.updateCalendar(newvalue); this.updateTimer(newvalue); this.updateFormattedValue(); } } /** * Picker input placeholder value * @default * @type {String} * */ @Input() placeHolder: string = 'yyyy-mm-dd hh:mm'; /** * When present, it specifies that an input field must be filled out before submitting the form * @default false * @type {Boolean} * */ @Input() required: boolean; /** * Defines the quantity of the selection * 'single' -- allow only a date value to be selected * 'multiple' -- allow multiple date value to be selected * 'range' -- allow to select an range ot date values * @default 'single' * @type {string} * */ @Input() selectionMode: 'single' | 'multiple' | 'range' = 'single'; /** * Whether to show the picker dialog header * @default false * @type {Boolean} * */ @Input() showHeader: boolean; @Input() canClear: boolean = true; /** * Whether to show the second's timer * @default false * @type {Boolean} * */ @Input() showSeconds: boolean; /** * Inline style of the element * @default null * @type {any} * */ @Input() style: any; /** * Style class of the element * @default null * @type {String} * */ @Input() styleClass: string; /** * Index of the element in tabbing order * @default null * @type {Number} * */ @Input() tabIndex: number; /** * Set the type of the dateTime picker * 'both' -- show both calendar and timer * 'calendar' -- show only calendar * 'timer' -- show only timer * @default 'both' * @type {'both' | 'calendar' | 'timer'} * */ @Input() type: 'both' | 'calendar' | 'timer' = 'calendar'; //附加方法 @Input() set timeOnly(value: boolean) { if (value) { this.type = 'timer'; } else { this.type = "both"; } } @Input() set showTime(value: boolean) { if (value) { this.type = 'both'; } else { this.type = "calendar"; } } /** * An object having regional configuration properties for the dateTimePicker * */ @Input() get locale(): any { return this._locale; } set locale(val: any) { if (val !== undefined) { this._locale = val; this._userlocale = true; } } @Input() localePrefab: 'Zh' | 'En' = 'En'; /** * Determine the hour format (12 or 24) * @default '24' * @type {'24'| '12'} * */ @Input() hourFormat: '12' | '24' = '24'; /** * When it is set to false, only show current month's days in calendar * @default true * @type {boolean} * */ @Input() showOtherMonths: boolean = true; /** * Callback to invoke when dropdown gets focus. * */ @Output() onFocus: any = new EventEmitter(); /** * Callback to invoke when dropdown gets focus. * */ @Output() onConfirm: any = new EventEmitter(); /** * Callback to invoke when dropdown loses focus. * */ @Output() onBlur: any = new EventEmitter(); /** * Callback to invoke when a invalid date is selected. * */ @Output() onInvalid: any = new EventEmitter(); @ViewChild('container') containerElm: ElementRef; @ViewChild('textInput') textInputElm: ElementRef; @ViewChild('dialog') dialogElm: ElementRef; public calendarDays: Array; public calendarWeekdays: string[]; public calendarMonths: Array; public calendarYears: Array = []; public dialogType: DialogType = DialogType.Date; public dialogVisible: boolean; public focus: boolean; public formattedValue: string = ''; public value: any; public pickerMoment: Date; public pickerMonth: string; public pickerYear: string; public hourValue: number; public minValue: number; public secValue: number; public meridianValue: string = 'AM'; private _userlocale: boolean = false; private _locale: LocaleSettings = { firstDayOfWeek: 0, dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], //dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], dateFns: null, confirm: 'OK' }; private _localeEn: LocaleSettings = { firstDayOfWeek: 0, dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'], monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], dateFns: null, confirm: 'OK' }; private _localeZh: LocaleSettings = { firstDayOfWeek: 0, dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'], dayNamesShort: ['日', '一', '二', '三', '四', '五', '六'], monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'], dateFns: null, confirm: '确定' }; private dialogClick: boolean; private documentClickListener: Function; private valueIndex: number = 0; private onModelChange: Function = () => { // } private onModelTouched: Function = () => { // } constructor(private renderer: Renderer2, private numFixedLenPipe: NumberFixedLenPipe) { } private updateDate(newvalue : Date) { if(newvalue!==undefined&&newvalue!==null) { if(this.min) { newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min); } if(this.max) { newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max); } this.updateModel(newvalue); this.updateCalendar(newvalue); this.updateTimer(newvalue); this.updateFormattedValue(); return; } } public onInputChange(event:any): void { let newvalue = this.parseToDate(event.target.value); if(newvalue!==undefined&&newvalue!==null) { if(this.min) { newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min); } if(this.max) { newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max); } this.updateModel(newvalue); this.updateCalendar(newvalue); this.updateTimer(newvalue); this.updateFormattedValue(); return; } this.updateModel(null); this.updateCalendar(null); this.updateTimer(null); this.updateFormattedValue(); } public ngOnInit(): void { if ((!this._userlocale) || this.locale === null && this.locale === undefined) { switch (this.localePrefab) { case 'Zh': { this._locale = this._localeZh; break; } case 'En': { this._locale = this._localeEn; break; } default: { this._locale = this._localeEn; break; } } } this.pickerMoment = new Date(); if (this.type === 'both' || this.type === 'calendar') { this.generateWeekDays(); this.generateMonthList(); } this.updateTimer(this.value); } public ngAfterViewInit(): void { this.updateCalendar(this.value); this.updateTimer(this.value); } public ngOnDestroy(): void { this.unbindDocumentClickListener(); } public writeValue(obj: any): void { if (obj instanceof Array) { this.value = []; for (let o of obj) { let v = this.parseToDate(o); this.value.push(v); } this.updateCalendar(this.value[0]); this.updateTimer(this.value[0]); } else { this.value = this.parseToDate(obj); this.updateCalendar(this.value); this.updateTimer(this.value); } this.updateFormattedValue(); } public registerOnChange(fn: any): void { this.onModelChange = fn; } public registerOnTouched(fn: any): void { this.onModelTouched = fn; } public setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; } private initflag = true; /** * Handle click event on the text input * @param {any} event * @return {void} * */ public onInputClick(event: any): void { if (this.timepicker !== undefined && this.initflag) { this.initflag = false; if (this.value !== undefined && this.value !== null) { this.timepicker.updateHour(this.value.getHours()); this.timepicker.updateMinute(this.value.getMinutes()); this.timepicker.updateSecond(this.value.getSeconds()); } else { this.timepicker.updateHour(0); this.timepicker.updateMinute(0); this.timepicker.updateSecond(0); this.updateModel(null); this.updateFormattedValue(); } } if (this.disabled) { event.preventDefault(); return; } this.dialogClick = true; if (!this.dialogVisible) { this.show(); } event.preventDefault(); return; } /** * Set the element on focus * @param {any} event * @return {void} * */ public onInputFocus(event: any): void { this.focus = true; this.onFocus.emit(event); event.preventDefault(); return; } /** * Set the element on blur * @param {any} event * @return {void} * */ public onInputBlur(event: any): void { this.focus = false; this.onModelTouched(); this.onBlur.emit(event); event.preventDefault(); return; } /** * Handle click event on the dialog * @param {any} event * @return {void} * */ public onDialogClick(event: any): void { this.dialogClick = true; } /** * Go to previous month * @param {any} event * @return {void} * */ public prevMonth(event: any): void { if (this.disabled) { event.preventDefault(); return; } this.pickerMoment = addMonths(this.pickerMoment, -1); this.generateCalendar(); if(this.value!==undefined&&this.value!==null) { let nowvalue = new Date(this.value); nowvalue.setMonth(this.pickerMoment.getMonth()); this.updateDate(nowvalue); } event.preventDefault(); return; } /** * Go to next month * @param {any} event * @return {void} * */ public nextMonth(event: any): void { if (this.disabled) { event.preventDefault(); return; } this.pickerMoment = addMonths(this.pickerMoment, 1); this.generateCalendar(); if(this.value!==undefined&&this.value!==null) { let nowvalue = new Date(this.value); nowvalue.setMonth(this.pickerMoment.getMonth()); this.updateDate(nowvalue); } event.preventDefault(); return; } /** * Select a date * @param {any} event * @param {Date} date * @return {void} * */ public selectDate(event: any, date: Date): void { if (this.disabled || !date) { event.preventDefault(); return; } let temp: Date; // check if the selected date is valid if (this.isValidValue(date)) { temp = date; } else { if (isSameDay(date, this._min)) { temp = new Date(this._min); } else if (isSameDay(date, this._max)) { temp = new Date(this._max); } else { this.onInvalid.emit({ originalEvent: event, value: date }); return; } } if (this.minValue !== undefined) { temp.setMinutes(this.minValue); } if (this.secValue !== undefined) { temp.setSeconds(this.secValue); } if (this.hourValue !== undefined) { temp.setHours(this.hourValue); } let selected; if (this.isSingleSelection()) { if (!isSameDay(this.value, temp)) { selected = temp; } } else if (this.isRangeSelection()) { if (this.value && this.value.length) { let startDate = this.value[0]; let endDate = this.value[1]; if (!endDate && temp.getTime() > startDate.getTime()) { endDate = temp; this.valueIndex = 1; } else { startDate = temp; endDate = null; this.valueIndex = 0; } selected = [startDate, endDate]; } else { selected = [temp, null]; this.valueIndex = 0; } } else if (this.isMultiSelection()) { // check if it exceeds the maxDateCount limit if (this.maxDateCount && this.value && this.value.length && this.value.length >= this.maxDateCount) { this.onInvalid.emit({ originalEvent: event, value: 'Exceed max date count.' }); return; } if (this.isSelectedDay(temp)) { selected = this.value.filter((d: Date) => { return !isSameDay(d, temp); }); } else { selected = this.value ? [...this.value, temp] : [temp]; this.valueIndex = selected.length - 1; } } if (selected) { this.updateModel(selected); if (this.value instanceof Array) { this.updateCalendar(this.value[this.valueIndex]); this.updateTimer(this.value[this.valueIndex]); } else { this.updateCalendar(this.value); this.updateTimer(this.value); } this.updateFormattedValue(); } } /** * Set a pickerMoment's month * @param {Number} monthNum * @return {void} * */ public selectMonth(monthNum: number): void { this.pickerMoment = setMonth(this.pickerMoment, monthNum); this.generateCalendar(); if(this.value!==undefined&&this.value!==null) { let nowvalue = new Date(this.value); nowvalue.setMonth(monthNum); this.updateDate(nowvalue); } this.changeDialogType(DialogType.Month); } /** * Set a pickerMoment's year * @param {Number} yearNum * @return {void} * */ public selectYear(yearNum: number): void { this.pickerMoment = setYear(this.pickerMoment, yearNum); this.generateCalendar(); if(this.value!==undefined&&this.value!==null) { let nowvalue = new Date(this.value); nowvalue.setFullYear(yearNum); this.updateDate(nowvalue); } this.changeDialogType(DialogType.Year); } /** * Set the selected moment's meridian * @param {any} event * @return {void} * */ public toggleMeridian(event: any): void { let value = this.value ? (this.value.length ? this.value[this.valueIndex] : this.value) : null; if (this.disabled) { event.preventDefault(); return; } if (!value) { this.meridianValue = this.meridianValue === 'AM' ? 'PM' : 'AM'; return; } let hours = getHours(value); if (this.meridianValue === 'AM') { hours += 12; } else if (this.meridianValue === 'PM') { hours -= 12; } let selectedTime = setHours(value, hours); this.setSelectedTime(selectedTime); event.preventDefault(); return; } /** * Set the selected moment's hour * @param {any} event * @param {'increase' | 'decrease' | number} val * 'increase' -- increase hour value by 1 * 'decrease' -- decrease hour value by 1 * number -- set hour value to val * @param {HTMLInputElement} input -- optional * @return {boolean} * */ public setHours(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean { let value; if (this.value) { if (this.value.length) { value = this.value[this.valueIndex]; } else { value = this.value; } } else { if (this.type === 'timer') { value = new Date(); } else { value = null; } } if (this.disabled || !value) { event.preventDefault(); return false; } let hours = getHours(value); if (val === 'increase') { hours += 1; } else if (val === 'decrease') { hours -= 1; } else { hours = val; } if (hours > 23) { hours = 0; } else if (hours < 0) { hours = 23; } let selectedTime = setHours(value, hours); let done = this.setSelectedTime(selectedTime); // Focus the input and select its value when model updated if (input) { setTimeout(() => { input.focus(); }, 0); } event.preventDefault(); return done; } /** * Set the selected moment's minute * @param {any} event * @param {'increase' | 'decrease' | number} val * 'increase' -- increase minute value by 1 * 'decrease' -- decrease minute value by 1 * number -- set minute value to val * @param {HTMLInputElement} input -- optional * @return {boolean} * */ public setMinutes(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean { let value; if (this.value) { if (this.value.length) { value = this.value[this.valueIndex]; } else { value = this.value; } } else { if (this.type === 'timer') { value = new Date(); } else { value = null; } } if (this.disabled || !value) { event.preventDefault(); return false; } let minutes = getMinutes(value); if (val === 'increase') { minutes += 1; } else if (val === 'decrease') { minutes -= 1; } else { minutes = val; } if (minutes > 59) { minutes = 0; } else if (minutes < 0) { minutes = 59; } let selectedTime = setMinutes(value, minutes); let done = this.setSelectedTime(selectedTime); // Focus the input and select its value when model updated if (input) { setTimeout(() => { input.focus(); }, 0); } event.preventDefault(); return done; } /** * Set the selected moment's second * @param {any} event * @param {'increase' | 'decrease' | number} val * 'increase' -- increase second value by 1 * 'decrease' -- decrease second value by 1 * number -- set second value to val * @param {HTMLInputElement} input -- optional * @return {boolean} * */ public setSeconds(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean { let value; if (this.value) { if (this.value.length) { value = this.value[this.valueIndex]; } else { value = this.value; } } else { if (this.type === 'timer') { value = new Date(); } else { value = null; } } if (this.disabled || !value) { event.preventDefault(); return false; } let seconds = getSeconds(value); if (val === 'increase') { seconds = this.secValue + 1; } else if (val === 'decrease') { seconds = this.secValue - 1; } else { seconds = val; } if (seconds > 59) { seconds = 0; } else if (seconds < 0) { seconds = 59; } let selectedTime = setSeconds(value, seconds); let done = this.setSelectedTime(selectedTime); // Focus the input and select its value when model updated if (input) { setTimeout(() => { input.focus(); }, 0); } event.preventDefault(); return done; } /** * Check if the date is selected * @param {Date} date * @return {Boolean} * */ public isSelectedDay(date: Date): boolean { if (this.isSingleSelection()) { return this.value && isSameDay(this.value, date); } else if (this.isRangeSelection() && this.value && this.value.length) { if (this.value[1]) { return (isSameDay(this.value[0], date) || isSameDay(this.value[1], date) || this.isDayBetween(this.value[0], this.value[1], date)) && this.isValidDay(date); } else { return isSameDay(this.value[0], date); } } else if (this.isMultiSelection() && this.value && this.value.length) { let selected; for (let d of this.value) { selected = isSameDay(d, date); if (selected) { break; } } return selected; } return false; } /** * Check if a day is between two specific days * @param {Date} start * @param {Date} end * @param {Date} day * @return {boolean} * */ public isDayBetween(start: Date, end: Date, day: Date): boolean { if (start && end) { return isAfter(day, start) && isBefore(day, end); } else { return false; } } /** * Check if the calendar day is a valid day * @param {Date} date * @return {Boolean} * */ public isValidDay(date: Date): boolean { let isValid = true; if (this.disabledDates && this.disabledDates.length) { for (let disabledDate of this.disabledDates) { if (isSameDay(disabledDate, date)) { return false; } } } if (isValid && this.disabledDays && this.disabledDays.length) { let weekdayNum = getDay(date); isValid = this.disabledDays.indexOf(weekdayNum) === -1; } if (isValid && this.min) { isValid = isValid && !isBefore(date, startOfDay(this.min)); } if (isValid && this.max) { isValid = isValid && !isAfter(date, endOfDay(this.max)); } return isValid; } /** * Check if the month is current pickerMoment's month * @param {Number} monthNum * @return {Boolean} * */ public isCurrentMonth(monthNum: number): boolean { return getMonth(this.pickerMoment) === monthNum; } /** * Check if the year is current pickerMoment's year * @param {Number} yearNum * @return {Boolean} * */ public isCurrentYear(yearNum: any): boolean { return getYear(this.pickerMoment) === yearNum||(getYear(this.pickerMoment)+"") === yearNum; } /** * Change the dialog type * @param {DialogType} type * @return {void} * */ public changeDialogType(type: DialogType): void { if (this.dialogType === type) { this.dialogType = DialogType.Date; return; } else { this.dialogType = type; } if (this.dialogType === DialogType.Year) { this.generateYearList(); } } /** * Handle blur event on timer input * @param {any} event * @param {HTMLInputElement} input * @param {string} type * @param {number} modelValue * @return {void} * */ public onTimerInputBlur(event: any, input: HTMLInputElement, type: string, modelValue: number): void { let val = +input.value; if (this.disabled || val === modelValue) { event.preventDefault(); return; } let done; if (!isNaN(val)) { switch (type) { case 'hours': if (this.hourFormat === '24' && val >= 0 && val <= 23) { done = this.setHours(event, val); } else if (this.hourFormat === '12' && val >= 1 && val <= 12) { if (this.meridianValue === 'AM' && val === 12) { val = 0; } else if (this.meridianValue === 'PM' && val < 12) { val = val + 12; } done = this.setHours(event, val); } break; case 'minutes': if (val >= 0 && val <= 59) { done = this.setMinutes(event, val); } break; case 'seconds': if (val >= 0 && val <= 59) { done = this.setSeconds(event, val); } break; } } if (!done) { input.value = this.numFixedLenPipe.transform(modelValue, 2); input.focus(); return; } event.preventDefault(); return; } /** * Set value to null * @param {any} event * @return {void} * */ public clearValue(event: any): void { this.dialogClick = true; this.updateModel(null); this.updateTimer(this.value); if (this.timepicker!==undefined) { this.timepicker.settime(undefined); } this.updateFormattedValue(); if(this.value!==null) { event.date=new Date(this.value); } this.onConfirm.emit(event); event.preventDefault(); } /** * Show the dialog * @return {void} * */ private show(): void { this.alignDialog(); this.dialogVisible = true; this.dialogType = DialogType.Date; this.bindDocumentClickListener(); return; } private nextNav(event : any):void { if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month) { this.nextMonth(event); } else if(this.dialogType===DialogType.Year){ this.generateYearList('next'); } } private prevNav(event : any):void { if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month) { this.prevMonth(event); } else if(this.dialogType===DialogType.Year){ this.generateYearList('prev'); } } /** * Hide the dialog * @return {void} * */ private hide(): void { this.dialogVisible = false; this.timepicker ? this.timepicker.closeProp() : 0; this.unbindDocumentClickListener(); if(this.value!==null) { event["date"]=new Date(this.value); } this.onConfirm.emit(event); return; } /** * Set the dialog position * @return {void} * */ private alignDialog(): void { let element = this.dialogElm.nativeElement; let target = this.containerElm.nativeElement; let elementDimensions = element.offsetParent ? { width: element.offsetWidth, height: element.offsetHeight } : this.getHiddenElementDimensions(element); let targetHeight = target.offsetHeight; let targetWidth = target.offsetWidth; let targetOffset = target.getBoundingClientRect(); let viewport = this.getViewport(); let top, left; if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height) { top = -1 * (elementDimensions.height); if (targetOffset.top + top < 0) { top = 0; } } else { top = targetHeight; } if ((targetOffset.left + elementDimensions.width) > viewport.width) { left = targetWidth - elementDimensions.width; } else { left = 0; } element.style.top = top + 'px'; element.style.left = left + 'px'; } /** * Bind click event on document * @return {void} * */ private bindDocumentClickListener(): void { let firstClick = true; if (!this.documentClickListener) { this.documentClickListener = this.renderer.listen('document', 'click', () => { if (!firstClick && !this.dialogClick) { this.hide(); } firstClick = false; this.dialogClick = false; }); } return; } /** * Unbind click event on document * @return {void} * */ private unbindDocumentClickListener(): void { if (this.documentClickListener) { this.documentClickListener(); this.documentClickListener = null; } return; } /** * Parse a object to Date object * @param {any} val * @return {Date} * */ private parseToDate(val: any): Date { if (!val) { return; } let parsedVal; if (typeof val === 'string') { parsedVal = parse(val); } else { parsedVal = val; } return isValid(parsedVal) ? parsedVal : null; } /** * Generate the calendar days array * @return {void} * */ private generateCalendar(): void { if (!this.pickerMoment) { return; } this.calendarDays = []; let startDateOfMonth = startOfMonth(this.pickerMoment); let startWeekdayOfMonth = getDay(startDateOfMonth); let dayDiff = 0 - (startWeekdayOfMonth + (7 - this.locale.firstDayOfWeek)) % 7; for (let i = 1; i < 7; i++) { let week = []; for (let j = 0; j < 7; j++) { let date = addDays(startDateOfMonth, dayDiff); let inOtherMonth = !isSameMonth(date, this.pickerMoment); week.push({ date, num: getDate(date), today: isToday(date), otherMonth: inOtherMonth, hide: !this.showOtherMonths && inOtherMonth, }); dayDiff += 1; } this.calendarDays.push(week); } this.pickerMonth = this.locale.monthNames[getMonth(this.pickerMoment)]; this.pickerYear = getYear(this.pickerMoment).toString(); } /** * Generate the calendar weekdays array * */ private generateWeekDays(): void { this.calendarWeekdays = []; let dayIndex = this.locale.firstDayOfWeek; for (let i = 0; i < 7; i++) { this.calendarWeekdays.push(this.locale.dayNamesShort[dayIndex]); dayIndex = (dayIndex === 6) ? 0 : ++dayIndex; } } /** * Generate the calendar month array * @return {void} * */ private generateMonthList(): void { this.calendarMonths = []; let monthIndex = 0; for (let i = 0; i < 4; i++) { let months = []; for (let j = 0; j < 3; j++) { months.push(this.locale.monthNamesShort[monthIndex]); monthIndex += 1; } this.calendarMonths.push(months); } } /** * Generate the calendar year array * @return {void} * */ public generateYearList(dir?: string): void { if (!this.pickerMoment) { return; } let start; if (dir === 'prev') { start = +this.calendarYears[0][0] - 12; if(start<0) { start=0; } } else if (dir === 'next') { start = +this.calendarYears[3][2] + 1; } else { start = getYear(addYears(this.pickerMoment, -4)); } for (let i = 0; i < 4; i++) { let years = []; for (let j = 0; j < 3; j++) { let year = (start + i * 3 + j).toString(); years.push(year); } this.calendarYears[i] = years; } return; } /** * Update the calendar * @param {Date} value * @return {void} * */ private updateCalendar(value: Date): void { // if the dateTime picker is only the timer, // no need to update the update Calendar. if (this.type === 'timer') { return; } if (value && (!this.calendarDays || !isSameMonth(value, this.pickerMoment))) { this.pickerMoment = setMonth(this.pickerMoment, getMonth(value)); this.pickerMoment = setYear(this.pickerMoment, getYear(value)); this.generateCalendar(); } else if (!value && !this.calendarDays) { this.generateCalendar(); } return; } /** * Update the timer * @param {Date} value * @return {boolean} * */ private updateTimer(value: Date): boolean { // if the dateTime picker is only the calendar, // no need to update the timer if (this.type === 'calendar') { return false; } if (!value) { this.hourValue = null; this.minValue = null; this.secValue = null; this.mtime.hour = 0; this.mtime.minute = 0; this.mtime.second = 0; return true; } this.mtime.hour = value.getHours(); this.mtime.minute = value.getMinutes(); this.mtime.second = value.getSeconds();; let time = value; let hours = getHours(time); if (this.hourFormat === '12') { if (hours < 12 && hours > 0) { this.hourValue = hours; this.meridianValue = 'AM'; } else if (hours > 12) { this.hourValue = hours - 12; this.meridianValue = 'PM'; } else if (hours === 12) { this.hourValue = 12; this.meridianValue = 'PM'; } else if (hours === 0) { this.hourValue = 12; this.meridianValue = 'AM'; } } else if (this.hourFormat === '24') { this.hourValue = hours; } this.minValue = getMinutes(time); this.secValue = getSeconds(time); if(this.value!==undefined&&this.timepicker!==undefined) { this.timepicker.settime(new Date(this.value)); } return true; } /** * Update ngModel * @param {Date} value * @return {Boolean} * */ private updateModel(value: Date | Date[]): boolean { this.value = value; if (this.dataType === 'date') { this.onModelChange(this.value); } else if (this.dataType === 'string') { if (this.value && this.value.length) { let formatted = []; for (let v of this.value) { if (v) { formatted.push(format(v, this.dateFormat, { locale: this.locale.dateFns })); } else { formatted.push(null); } } this.onModelChange(formatted); } else { this.onModelChange(format(this.value, this.dateFormat, { locale: this.locale.dateFns })); } } return true; } /** * Update variable formattedValue * @return {void} * */ private updateFormattedValue(): void { let formattedValue = ''; if (this.value) { if (this.isSingleSelection()) { formattedValue = format(this.value, this.dateFormat, { locale: this.locale.dateFns }); } else if (this.isRangeSelection()) { let startDate = this.value[0]; let endDate = this.value[1]; formattedValue = format(startDate, this.dateFormat, { locale: this.locale.dateFns }); if (endDate) { formattedValue += ' - ' + format(endDate, this.dateFormat, { locale: this.locale.dateFns }); } else { formattedValue += ' - ' + this.dateFormat; } } else if (this.isMultiSelection()) { for (let i = 0; i < this.value.length; i++) { let dateAsString = format(this.value[i], this.dateFormat, { locale: this.locale.dateFns }); formattedValue += dateAsString; if (i !== (this.value.length - 1)) { formattedValue += ', '; } } } } this.formattedValue = formattedValue; return; } /** * Set the time * @param {Date} val * @return {boolean} * */ public setSelectedTime(val: Date): boolean { let done; if (this.isValidValue(val)) { if (this.value instanceof Array) { this.value[this.valueIndex] = val; done = this.updateModel(this.value); done = done && this.updateTimer(this.value[this.valueIndex]); } else { done = this.updateModel(val); done = done && this.updateTimer(this.value); } this.updateFormattedValue(); } else { this.onInvalid.emit({ originalEvent: event, value: val }); done = false; } return done; } private isValidValue(value: Date): boolean { let isValid = true; if (this.disabledDates && this.disabledDates.length) { for (let disabledDate of this.disabledDates) { if (isSameDay(disabledDate, value)) { return false; } } } if (isValid && this.disabledDays && this.disabledDays.length) { let weekdayNum = getDay(value); isValid = this.disabledDays.indexOf(weekdayNum) === -1; } if (isValid && this.min) { isValid = isValid && !isBefore(value, this.min); } if (isValid && this.max) { isValid = isValid && !isAfter(value, this.max); } return isValid; } /** * Check if the selection mode is 'single' * @return {boolean} * */ private isSingleSelection(): boolean { return this.selectionMode === 'single'; } /** * Check if the selection mode is 'range' * @return {boolean} * */ private isRangeSelection(): boolean { return this.selectionMode === 'range'; } /** * Check if the selection mode is 'multiple' * @return {boolean} * */ private isMultiSelection(): boolean { return this.selectionMode === 'multiple'; } private getHiddenElementDimensions(element: any): any { let dimensions: any = {}; element.style.visibility = 'hidden'; element.style.display = 'block'; dimensions.width = element.offsetWidth; dimensions.height = element.offsetHeight; element.style.display = 'none'; element.style.visibility = 'visible'; return dimensions; } private getViewport(): any { let win = window, d = document, e = d.documentElement, g = d.getElementsByTagName('body')[0], w = win.innerWidth || e.clientWidth || g.clientWidth, h = win.innerHeight || e.clientHeight || g.clientHeight; return { width: w, height: h }; } public confirm() { this.hide(); } public seconds = false; public mtime: any = { hour: 0, minute: 0, second: 0 }; public TimerChange(time: any) { let value; if (this.value) { if (this.value.length) { value = this.value[this.valueIndex]; } else { value = this.value; } } else { if (this.type === 'timer') { value = new Date(); } else { value = new Date(); } } if (this.disabled || !value) { event.preventDefault(); return false; } let minute = time.minute; let hour = time.hour; let second = time.second; this.minValue = minute; this.hourValue = hour; this.secValue = second; let selectedTime = setMinutes(value, minute); selectedTime = setHours(selectedTime, hour); selectedTime = setSeconds(selectedTime, second); let done = this.setSelectedTime(selectedTime); // Focus the input and select its value when model updated event.preventDefault(); return done; } private mouseIn :boolean = false; private Mouseout(event:any) { this.mouseIn = false; } private Mouseover(event:any) { this.mouseIn = true; } }