7 Component, ElementRef, EventEmitter, forwardRef, Input, OnDestroy, OnInit, Output, Renderer2,
9 } from '@angular/core';
10 import { animate, state, style, transition, trigger } from '@angular/animations';
11 import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
27 differenceInCalendarDays,
42 import { NumberFixedLenPipe } from './numberedFixLen.pipe';
44 export interface LocaleSettings {
45 firstDayOfWeek?: number;
47 dayNamesShort: string[];
49 monthNamesShort: string[];
54 export enum DialogType {
61 export const DATETIMEPICKER_VALUE_ACCESSOR: any = {
62 provide: NG_VALUE_ACCESSOR,
63 useExisting: forwardRef(() => DateTimePickerComponent),
68 selector: 'plx-datepicker',
69 templateUrl: './picker.component.html',
70 styleUrls: ['./picker.component.less'],
71 providers: [NumberFixedLenPipe, DATETIMEPICKER_VALUE_ACCESSOR],
73 trigger('fadeInOut', [
74 state('hidden', style({
78 state('visible', style({
82 transition('visible => hidden', animate('200ms ease-in')),
83 transition('hidden => visible', animate('400ms ease-out'))
88 export class DateTimePickerComponent implements OnInit, AfterViewInit, OnDestroy, ControlValueAccessor {
90 @ViewChild('timepicker') public timepicker;
93 * Type of the value to write back to ngModel
94 * @default 'date' -- Javascript Date type
95 * @type {'string' | 'date'}
97 @Input() dataType: 'string' | 'date' = 'date';
104 @Input() dateFormat: string = 'YYYY-MM-DD HH:mm';
106 * When present, it specifies that the component should be disabled
110 @Input() disabled: boolean;
115 @Input() supportKeyboardInput: boolean = false;
117 * Array with dates that should be disabled (not selectable)
121 @Input() disabledDates: Date[] = [];
124 * Array with weekday numbers that should be disabled (not selectable)
128 @Input() disabledDays: number[];
131 * When enabled, displays the calendar as inline
132 * @default false -- popup mode
135 @Input() inline: boolean;
138 * Identifier of the focus input to match a label defined for the component
142 @Input() inputId: string;
145 * Inline style of the picker text input
149 @Input() inputStyle: any;
152 * Style class of the picker text input
156 @Input() inputStyleClass: string;
159 * Maximum number of selectable dates in multiple mode
163 @Input() maxDateCount: number;
166 * The minimum selectable date time
168 * @type {Date | string}
176 set max(val: Date | string) {
177 this._max = this.parseToDate(val);
185 set maxDate(val: Date | string) {
186 this._max = this.parseToDate(val);
190 * The maximum selectable date time
192 * @type {Date | string }
200 set min(val: Date | string) {
201 this._min = this.parseToDate(val);
208 set minDate(val: Date | string) {
209 this._min = this.parseToDate(val);
217 set dateValue(val: Date | string) {
218 let newvalue = this.parseToDate(val);
219 if(newvalue!==undefined)
221 this.updateModel(newvalue);
222 this.updateCalendar(newvalue);
223 this.updateTimer(newvalue);
224 this.updateFormattedValue();
230 * Picker input placeholder value
234 @Input() placeHolder: string = 'yyyy-mm-dd hh:mm';
237 * When present, it specifies that an input field must be filled out before submitting the form
241 @Input() required: boolean;
244 * Defines the quantity of the selection
245 * 'single' -- allow only a date value to be selected
246 * 'multiple' -- allow multiple date value to be selected
247 * 'range' -- allow to select an range ot date values
251 @Input() selectionMode: 'single' | 'multiple' | 'range' = 'single';
254 * Whether to show the picker dialog header
258 @Input() showHeader: boolean;
260 @Input() canClear: boolean = true;
263 * Whether to show the second's timer
267 @Input() showSeconds: boolean;
270 * Inline style of the element
277 * Style class of the element
281 @Input() styleClass: string;
284 * Index of the element in tabbing order
288 @Input() tabIndex: number;
291 * Set the type of the dateTime picker
292 * 'both' -- show both calendar and timer
293 * 'calendar' -- show only calendar
294 * 'timer' -- show only timer
296 * @type {'both' | 'calendar' | 'timer'}
298 @Input() type: 'both' | 'calendar' | 'timer' = 'calendar';
302 set timeOnly(value: boolean) {
312 set showTime(value: boolean) {
317 this.type = "calendar";
322 * An object having regional configuration properties for the dateTimePicker
328 set locale(val: any) {
329 if (val !== undefined) {
331 this._userlocale = true;
335 @Input() localePrefab: 'Zh' | 'En' = 'En';
337 * Determine the hour format (12 or 24)
341 @Input() hourFormat: '12' | '24' = '24';
345 * When it is set to false, only show current month's days in calendar
349 @Input() showOtherMonths: boolean = true;
352 * Callback to invoke when dropdown gets focus.
354 @Output() onFocus: any = new EventEmitter<any>();
357 * Callback to invoke when dropdown gets focus.
359 @Output() onConfirm: any = new EventEmitter<any>();
362 * Callback to invoke when dropdown loses focus.
364 @Output() onBlur: any = new EventEmitter<any>();
367 * Callback to invoke when a invalid date is selected.
369 @Output() onInvalid: any = new EventEmitter<any>();
373 @ViewChild('container') containerElm: ElementRef;
374 @ViewChild('textInput') textInputElm: ElementRef;
375 @ViewChild('dialog') dialogElm: ElementRef;
377 public calendarDays: Array<any[]>;
378 public calendarWeekdays: string[];
379 public calendarMonths: Array<string[]>;
380 public calendarYears: Array<string[]> = [];
381 public dialogType: DialogType = DialogType.Date;
382 public dialogVisible: boolean;
383 public focus: boolean;
384 public formattedValue: string = '';
386 public pickerMoment: Date;
387 public pickerMonth: string;
388 public pickerYear: string;
390 public hourValue: number;
391 public minValue: number;
392 public secValue: number;
393 public meridianValue: string = 'AM';
394 private _userlocale: boolean = false;
395 private _locale: LocaleSettings = {
397 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
398 dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
399 //dayNamesShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
400 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
401 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
405 private _localeEn: LocaleSettings = {
407 dayNames: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
408 dayNamesShort: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
409 monthNames: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
410 monthNamesShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
414 private _localeZh: LocaleSettings = {
416 dayNames: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
417 dayNamesShort: ['日', '一', '二', '三', '四', '五', '六'],
418 monthNames: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
419 monthNamesShort: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
423 private dialogClick: boolean;
424 private documentClickListener: Function;
425 private valueIndex: number = 0;
426 private onModelChange: Function = () => {
429 private onModelTouched: Function = () => {
433 constructor(private renderer: Renderer2,
434 private numFixedLenPipe: NumberFixedLenPipe) {
436 private updateDate(newvalue : Date)
438 if(newvalue!==undefined&&newvalue!==null)
442 newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min);
446 newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max);
448 this.updateModel(newvalue);
449 this.updateCalendar(newvalue);
450 this.updateTimer(newvalue);
451 this.updateFormattedValue();
455 public onInputChange(event:any): void {
456 let newvalue = this.parseToDate(event.target.value);
457 if(newvalue!==undefined&&newvalue!==null)
461 newvalue = this._min.getTime()<=newvalue.getTime()?newvalue:new Date(this._min);
465 newvalue = this._max.getTime()>=newvalue.getTime()?newvalue:new Date(this._max);
467 this.updateModel(newvalue);
468 this.updateCalendar(newvalue);
469 this.updateTimer(newvalue);
470 this.updateFormattedValue();
473 this.updateModel(null);
474 this.updateCalendar(null);
475 this.updateTimer(null);
476 this.updateFormattedValue();
478 public ngOnInit(): void {
480 if ((!this._userlocale) || this.locale === null && this.locale === undefined) {
481 switch (this.localePrefab) {
484 this._locale = this._localeZh;
488 this._locale = this._localeEn;
493 this._locale = this._localeEn;
498 this.pickerMoment = new Date();
500 if (this.type === 'both' || this.type === 'calendar') {
501 this.generateWeekDays();
502 this.generateMonthList();
504 this.updateTimer(this.value);
507 public ngAfterViewInit(): void {
508 this.updateCalendar(this.value);
509 this.updateTimer(this.value);
512 public ngOnDestroy(): void {
513 this.unbindDocumentClickListener();
516 public writeValue(obj: any): void {
518 if (obj instanceof Array) {
521 let v = this.parseToDate(o);
524 this.updateCalendar(this.value[0]);
525 this.updateTimer(this.value[0]);
527 this.value = this.parseToDate(obj);
528 this.updateCalendar(this.value);
529 this.updateTimer(this.value);
531 this.updateFormattedValue();
534 public registerOnChange(fn: any): void {
535 this.onModelChange = fn;
538 public registerOnTouched(fn: any): void {
539 this.onModelTouched = fn;
542 public setDisabledState(isDisabled: boolean): void {
543 this.disabled = isDisabled;
547 private initflag = true;
549 * Handle click event on the text input
553 public onInputClick(event: any): void {
555 if (this.timepicker !== undefined && this.initflag) {
556 this.initflag = false;
557 if (this.value !== undefined && this.value !== null) {
558 this.timepicker.updateHour(this.value.getHours());
559 this.timepicker.updateMinute(this.value.getMinutes());
560 this.timepicker.updateSecond(this.value.getSeconds());
563 this.timepicker.updateHour(0);
564 this.timepicker.updateMinute(0);
565 this.timepicker.updateSecond(0);
566 this.updateModel(null);
567 this.updateFormattedValue();
571 event.preventDefault();
575 this.dialogClick = true;
576 if (!this.dialogVisible) {
579 event.preventDefault();
584 * Set the element on focus
588 public onInputFocus(event: any): void {
590 this.onFocus.emit(event);
591 event.preventDefault();
596 * Set the element on blur
600 public onInputBlur(event: any): void {
602 this.onModelTouched();
603 this.onBlur.emit(event);
604 event.preventDefault();
609 * Handle click event on the dialog
613 public onDialogClick(event: any): void {
614 this.dialogClick = true;
618 * Go to previous month
622 public prevMonth(event: any): void {
625 event.preventDefault();
629 this.pickerMoment = addMonths(this.pickerMoment, -1);
630 this.generateCalendar();
631 if(this.value!==undefined&&this.value!==null)
633 let nowvalue = new Date(this.value);
634 nowvalue.setMonth(this.pickerMoment.getMonth());
635 this.updateDate(nowvalue);
637 event.preventDefault();
646 public nextMonth(event: any): void {
649 event.preventDefault();
653 this.pickerMoment = addMonths(this.pickerMoment, 1);
654 this.generateCalendar();
655 if(this.value!==undefined&&this.value!==null)
657 let nowvalue = new Date(this.value);
658 nowvalue.setMonth(this.pickerMoment.getMonth());
659 this.updateDate(nowvalue);
661 event.preventDefault();
671 public selectDate(event: any, date: Date): void {
673 if (this.disabled || !date) {
674 event.preventDefault();
679 // check if the selected date is valid
680 if (this.isValidValue(date)) {
683 if (isSameDay(date, this._min)) {
684 temp = new Date(this._min);
685 } else if (isSameDay(date, this._max)) {
686 temp = new Date(this._max);
688 this.onInvalid.emit({ originalEvent: event, value: date });
692 if (this.minValue !== undefined) {
693 temp.setMinutes(this.minValue);
695 if (this.secValue !== undefined) {
696 temp.setSeconds(this.secValue);
698 if (this.hourValue !== undefined) {
699 temp.setHours(this.hourValue);
702 if (this.isSingleSelection()) {
703 if (!isSameDay(this.value, temp)) {
706 } else if (this.isRangeSelection()) {
707 if (this.value && this.value.length) {
708 let startDate = this.value[0];
709 let endDate = this.value[1];
711 if (!endDate && temp.getTime() > startDate.getTime()) {
719 selected = [startDate, endDate];
721 selected = [temp, null];
724 } else if (this.isMultiSelection()) {
726 // check if it exceeds the maxDateCount limit
727 if (this.maxDateCount && this.value &&
728 this.value.length && this.value.length >= this.maxDateCount) {
729 this.onInvalid.emit({ originalEvent: event, value: 'Exceed max date count.' });
733 if (this.isSelectedDay(temp)) {
734 selected = this.value.filter((d: Date) => {
735 return !isSameDay(d, temp);
738 selected = this.value ? [...this.value, temp] : [temp];
739 this.valueIndex = selected.length - 1;
743 this.updateModel(selected);
744 if (this.value instanceof Array) {
745 this.updateCalendar(this.value[this.valueIndex]);
746 this.updateTimer(this.value[this.valueIndex]);
748 this.updateCalendar(this.value);
749 this.updateTimer(this.value);
751 this.updateFormattedValue();
756 * Set a pickerMoment's month
757 * @param {Number} monthNum
760 public selectMonth(monthNum: number): void {
761 this.pickerMoment = setMonth(this.pickerMoment, monthNum);
762 this.generateCalendar();
763 if(this.value!==undefined&&this.value!==null)
765 let nowvalue = new Date(this.value);
766 nowvalue.setMonth(monthNum);
767 this.updateDate(nowvalue);
769 this.changeDialogType(DialogType.Month);
773 * Set a pickerMoment's year
774 * @param {Number} yearNum
777 public selectYear(yearNum: number): void {
778 this.pickerMoment = setYear(this.pickerMoment, yearNum);
779 this.generateCalendar();
780 if(this.value!==undefined&&this.value!==null)
782 let nowvalue = new Date(this.value);
783 nowvalue.setFullYear(yearNum);
784 this.updateDate(nowvalue);
786 this.changeDialogType(DialogType.Year);
790 * Set the selected moment's meridian
794 public toggleMeridian(event: any): void {
796 let value = this.value ? (this.value.length ? this.value[this.valueIndex] : this.value) : null;
799 event.preventDefault();
804 this.meridianValue = this.meridianValue === 'AM' ? 'PM' : 'AM';
808 let hours = getHours(value);
809 if (this.meridianValue === 'AM') {
811 } else if (this.meridianValue === 'PM') {
815 let selectedTime = setHours(value, hours);
816 this.setSelectedTime(selectedTime);
817 event.preventDefault();
822 * Set the selected moment's hour
824 * @param {'increase' | 'decrease' | number} val
825 * 'increase' -- increase hour value by 1
826 * 'decrease' -- decrease hour value by 1
827 * number -- set hour value to val
828 * @param {HTMLInputElement} input -- optional
831 public setHours(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
835 if (this.value.length) {
836 value = this.value[this.valueIndex];
841 if (this.type === 'timer') {
848 if (this.disabled || !value) {
849 event.preventDefault();
853 let hours = getHours(value);
854 if (val === 'increase') {
856 } else if (val === 'decrease') {
864 } else if (hours < 0) {
868 let selectedTime = setHours(value, hours);
869 let done = this.setSelectedTime(selectedTime);
871 // Focus the input and select its value when model updated
878 event.preventDefault();
883 * Set the selected moment's minute
885 * @param {'increase' | 'decrease' | number} val
886 * 'increase' -- increase minute value by 1
887 * 'decrease' -- decrease minute value by 1
888 * number -- set minute value to val
889 * @param {HTMLInputElement} input -- optional
892 public setMinutes(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
896 if (this.value.length) {
897 value = this.value[this.valueIndex];
902 if (this.type === 'timer') {
909 if (this.disabled || !value) {
910 event.preventDefault();
914 let minutes = getMinutes(value);
915 if (val === 'increase') {
917 } else if (val === 'decrease') {
925 } else if (minutes < 0) {
929 let selectedTime = setMinutes(value, minutes);
930 let done = this.setSelectedTime(selectedTime);
932 // Focus the input and select its value when model updated
939 event.preventDefault();
944 * Set the selected moment's second
946 * @param {'increase' | 'decrease' | number} val
947 * 'increase' -- increase second value by 1
948 * 'decrease' -- decrease second value by 1
949 * number -- set second value to val
950 * @param {HTMLInputElement} input -- optional
953 public setSeconds(event: any, val: 'increase' | 'decrease' | number, input?: HTMLInputElement): boolean {
957 if (this.value.length) {
958 value = this.value[this.valueIndex];
963 if (this.type === 'timer') {
970 if (this.disabled || !value) {
971 event.preventDefault();
975 let seconds = getSeconds(value);
976 if (val === 'increase') {
977 seconds = this.secValue + 1;
978 } else if (val === 'decrease') {
979 seconds = this.secValue - 1;
986 } else if (seconds < 0) {
990 let selectedTime = setSeconds(value, seconds);
991 let done = this.setSelectedTime(selectedTime);
993 // Focus the input and select its value when model updated
1000 event.preventDefault();
1005 * Check if the date is selected
1006 * @param {Date} date
1009 public isSelectedDay(date: Date): boolean {
1010 if (this.isSingleSelection()) {
1011 return this.value && isSameDay(this.value, date);
1012 } else if (this.isRangeSelection() && this.value && this.value.length) {
1013 if (this.value[1]) {
1014 return (isSameDay(this.value[0], date) || isSameDay(this.value[1], date) ||
1015 this.isDayBetween(this.value[0], this.value[1], date)) && this.isValidDay(date);
1017 return isSameDay(this.value[0], date);
1019 } else if (this.isMultiSelection() && this.value && this.value.length) {
1021 for (let d of this.value) {
1022 selected = isSameDay(d, date);
1033 * Check if a day is between two specific days
1034 * @param {Date} start
1039 public isDayBetween(start: Date, end: Date, day: Date): boolean {
1041 return isAfter(day, start) && isBefore(day, end);
1048 * Check if the calendar day is a valid day
1049 * @param {Date} date
1052 public isValidDay(date: Date): boolean {
1054 if (this.disabledDates && this.disabledDates.length) {
1055 for (let disabledDate of this.disabledDates) {
1056 if (isSameDay(disabledDate, date)) {
1062 if (isValid && this.disabledDays && this.disabledDays.length) {
1063 let weekdayNum = getDay(date);
1064 isValid = this.disabledDays.indexOf(weekdayNum) === -1;
1067 if (isValid && this.min) {
1068 isValid = isValid && !isBefore(date, startOfDay(this.min));
1071 if (isValid && this.max) {
1072 isValid = isValid && !isAfter(date, endOfDay(this.max));
1078 * Check if the month is current pickerMoment's month
1079 * @param {Number} monthNum
1082 public isCurrentMonth(monthNum: number): boolean {
1083 return getMonth(this.pickerMoment) === monthNum;
1087 * Check if the year is current pickerMoment's year
1088 * @param {Number} yearNum
1091 public isCurrentYear(yearNum: any): boolean {
1092 return getYear(this.pickerMoment) === yearNum||(getYear(this.pickerMoment)+"") === yearNum;
1096 * Change the dialog type
1097 * @param {DialogType} type
1100 public changeDialogType(type: DialogType): void {
1101 if (this.dialogType === type) {
1102 this.dialogType = DialogType.Date;
1105 this.dialogType = type;
1108 if (this.dialogType === DialogType.Year) {
1109 this.generateYearList();
1114 * Handle blur event on timer input
1115 * @param {any} event
1116 * @param {HTMLInputElement} input
1117 * @param {string} type
1118 * @param {number} modelValue
1121 public onTimerInputBlur(event: any, input: HTMLInputElement, type: string, modelValue: number): void {
1122 let val = +input.value;
1124 if (this.disabled || val === modelValue) {
1125 event.preventDefault();
1133 if (this.hourFormat === '24' &&
1134 val >= 0 && val <= 23) {
1135 done = this.setHours(event, val);
1136 } else if (this.hourFormat === '12'
1137 && val >= 1 && val <= 12) {
1138 if (this.meridianValue === 'AM' && val === 12) {
1140 } else if (this.meridianValue === 'PM' && val < 12) {
1143 done = this.setHours(event, val);
1147 if (val >= 0 && val <= 59) {
1148 done = this.setMinutes(event, val);
1152 if (val >= 0 && val <= 59) {
1153 done = this.setSeconds(event, val);
1160 input.value = this.numFixedLenPipe.transform(modelValue, 2);
1164 event.preventDefault();
1170 * @param {any} event
1173 public clearValue(event: any): void {
1174 this.dialogClick = true;
1175 this.updateModel(null);
1176 this.updateTimer(this.value);
1177 if (this.timepicker!==undefined) {
1178 this.timepicker.settime(undefined);
1180 this.updateFormattedValue();
1181 if(this.value!==null)
1183 event.date=new Date(this.value);
1185 this.onConfirm.emit(event);
1186 event.preventDefault();
1193 private show(): void {
1195 this.dialogVisible = true;
1196 this.dialogType = DialogType.Date;
1197 this.bindDocumentClickListener();
1200 private nextNav(event : any):void {
1201 if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month)
1203 this.nextMonth(event);
1205 else if(this.dialogType===DialogType.Year){
1206 this.generateYearList('next');
1209 private prevNav(event : any):void {
1210 if( this.dialogType===DialogType.Date|| this.dialogType===DialogType.Month)
1212 this.prevMonth(event);
1214 else if(this.dialogType===DialogType.Year){
1215 this.generateYearList('prev');
1222 private hide(): void {
1223 this.dialogVisible = false;
1224 this.timepicker ? this.timepicker.closeProp() : 0;
1225 this.unbindDocumentClickListener();
1226 if(this.value!==null)
1228 event["date"]=new Date(this.value);
1230 this.onConfirm.emit(event);
1235 * Set the dialog position
1238 private alignDialog(): void {
1239 let element = this.dialogElm.nativeElement;
1240 let target = this.containerElm.nativeElement;
1241 let elementDimensions = element.offsetParent ? {
1242 width: element.offsetWidth,
1243 height: element.offsetHeight
1244 } : this.getHiddenElementDimensions(element);
1245 let targetHeight = target.offsetHeight;
1246 let targetWidth = target.offsetWidth;
1247 let targetOffset = target.getBoundingClientRect();
1248 let viewport = this.getViewport();
1251 if ((targetOffset.top + targetHeight + elementDimensions.height) > viewport.height) {
1252 top = -1 * (elementDimensions.height);
1253 if (targetOffset.top + top < 0) {
1261 if ((targetOffset.left + elementDimensions.width) > viewport.width) {
1262 left = targetWidth - elementDimensions.width;
1267 element.style.top = top + 'px';
1268 element.style.left = left + 'px';
1272 * Bind click event on document
1275 private bindDocumentClickListener(): void {
1276 let firstClick = true;
1277 if (!this.documentClickListener) {
1278 this.documentClickListener = this.renderer.listen('document', 'click', () => {
1279 if (!firstClick && !this.dialogClick) {
1284 this.dialogClick = false;
1291 * Unbind click event on document
1294 private unbindDocumentClickListener(): void {
1295 if (this.documentClickListener) {
1296 this.documentClickListener();
1297 this.documentClickListener = null;
1303 * Parse a object to Date object
1307 private parseToDate(val: any): Date {
1313 if (typeof val === 'string') {
1314 parsedVal = parse(val);
1319 return isValid(parsedVal) ? parsedVal : null;
1323 * Generate the calendar days array
1326 private generateCalendar(): void {
1328 if (!this.pickerMoment) {
1332 this.calendarDays = [];
1333 let startDateOfMonth = startOfMonth(this.pickerMoment);
1334 let startWeekdayOfMonth = getDay(startDateOfMonth);
1336 let dayDiff = 0 - (startWeekdayOfMonth + (7 - this.locale.firstDayOfWeek)) % 7;
1338 for (let i = 1; i < 7; i++) {
1340 for (let j = 0; j < 7; j++) {
1341 let date = addDays(startDateOfMonth, dayDiff);
1342 let inOtherMonth = !isSameMonth(date, this.pickerMoment);
1346 today: isToday(date),
1347 otherMonth: inOtherMonth,
1348 hide: !this.showOtherMonths && inOtherMonth,
1352 this.calendarDays.push(week);
1355 this.pickerMonth = this.locale.monthNames[getMonth(this.pickerMoment)];
1356 this.pickerYear = getYear(this.pickerMoment).toString();
1360 * Generate the calendar weekdays array
1362 private generateWeekDays(): void {
1363 this.calendarWeekdays = [];
1364 let dayIndex = this.locale.firstDayOfWeek;
1365 for (let i = 0; i < 7; i++) {
1366 this.calendarWeekdays.push(this.locale.dayNamesShort[dayIndex]);
1367 dayIndex = (dayIndex === 6) ? 0 : ++dayIndex;
1372 * Generate the calendar month array
1375 private generateMonthList(): void {
1376 this.calendarMonths = [];
1378 for (let i = 0; i < 4; i++) {
1380 for (let j = 0; j < 3; j++) {
1381 months.push(this.locale.monthNamesShort[monthIndex]);
1384 this.calendarMonths.push(months);
1389 * Generate the calendar year array
1392 public generateYearList(dir?: string): void {
1394 if (!this.pickerMoment) {
1399 if (dir === 'prev') {
1400 start = +this.calendarYears[0][0] - 12;
1405 } else if (dir === 'next') {
1406 start = +this.calendarYears[3][2] + 1;
1408 start = getYear(addYears(this.pickerMoment, -4));
1411 for (let i = 0; i < 4; i++) {
1413 for (let j = 0; j < 3; j++) {
1414 let year = (start + i * 3 + j).toString();
1417 this.calendarYears[i] = years;
1423 * Update the calendar
1424 * @param {Date} value
1427 private updateCalendar(value: Date): void {
1429 // if the dateTime picker is only the timer,
1430 // no need to update the update Calendar.
1431 if (this.type === 'timer') {
1435 if (value && (!this.calendarDays || !isSameMonth(value, this.pickerMoment))) {
1436 this.pickerMoment = setMonth(this.pickerMoment, getMonth(value));
1437 this.pickerMoment = setYear(this.pickerMoment, getYear(value));
1438 this.generateCalendar();
1439 } else if (!value && !this.calendarDays) {
1440 this.generateCalendar();
1447 * @param {Date} value
1450 private updateTimer(value: Date): boolean {
1452 // if the dateTime picker is only the calendar,
1453 // no need to update the timer
1454 if (this.type === 'calendar') {
1459 this.hourValue = null;
1460 this.minValue = null;
1461 this.secValue = null;
1462 this.mtime.hour = 0;
1463 this.mtime.minute = 0;
1464 this.mtime.second = 0;
1467 this.mtime.hour = value.getHours();
1468 this.mtime.minute = value.getMinutes();
1469 this.mtime.second = value.getSeconds();;
1472 let hours = getHours(time);
1473 if (this.hourFormat === '12') {
1474 if (hours < 12 && hours > 0) {
1475 this.hourValue = hours;
1476 this.meridianValue = 'AM';
1477 } else if (hours > 12) {
1478 this.hourValue = hours - 12;
1479 this.meridianValue = 'PM';
1480 } else if (hours === 12) {
1481 this.hourValue = 12;
1482 this.meridianValue = 'PM';
1483 } else if (hours === 0) {
1484 this.hourValue = 12;
1485 this.meridianValue = 'AM';
1487 } else if (this.hourFormat === '24') {
1488 this.hourValue = hours;
1491 this.minValue = getMinutes(time);
1492 this.secValue = getSeconds(time);
1493 if(this.value!==undefined&&this.timepicker!==undefined)
1495 this.timepicker.settime(new Date(this.value));
1502 * @param {Date} value
1505 private updateModel(value: Date | Date[]): boolean {
1507 if (this.dataType === 'date') {
1508 this.onModelChange(this.value);
1509 } else if (this.dataType === 'string') {
1510 if (this.value && this.value.length) {
1512 for (let v of this.value) {
1514 formatted.push(format(v, this.dateFormat, { locale: this.locale.dateFns }));
1516 formatted.push(null);
1519 this.onModelChange(formatted);
1521 this.onModelChange(format(this.value, this.dateFormat, { locale: this.locale.dateFns }));
1528 * Update variable formattedValue
1531 private updateFormattedValue(): void {
1532 let formattedValue = '';
1535 if (this.isSingleSelection()) {
1536 formattedValue = format(this.value, this.dateFormat, { locale: this.locale.dateFns });
1537 } else if (this.isRangeSelection()) {
1538 let startDate = this.value[0];
1539 let endDate = this.value[1];
1541 formattedValue = format(startDate, this.dateFormat, { locale: this.locale.dateFns });
1544 formattedValue += ' - ' + format(endDate, this.dateFormat, { locale: this.locale.dateFns });
1546 formattedValue += ' - ' + this.dateFormat;
1548 } else if (this.isMultiSelection()) {
1549 for (let i = 0; i < this.value.length; i++) {
1550 let dateAsString = format(this.value[i], this.dateFormat, { locale: this.locale.dateFns });
1551 formattedValue += dateAsString;
1552 if (i !== (this.value.length - 1)) {
1553 formattedValue += ', ';
1559 this.formattedValue = formattedValue;
1569 public setSelectedTime(val: Date): boolean {
1571 if (this.isValidValue(val)) {
1572 if (this.value instanceof Array) {
1573 this.value[this.valueIndex] = val;
1574 done = this.updateModel(this.value);
1575 done = done && this.updateTimer(this.value[this.valueIndex]);
1577 done = this.updateModel(val);
1578 done = done && this.updateTimer(this.value);
1580 this.updateFormattedValue();
1582 this.onInvalid.emit({ originalEvent: event, value: val });
1588 private isValidValue(value: Date): boolean {
1591 if (this.disabledDates && this.disabledDates.length) {
1592 for (let disabledDate of this.disabledDates) {
1593 if (isSameDay(disabledDate, value)) {
1599 if (isValid && this.disabledDays && this.disabledDays.length) {
1600 let weekdayNum = getDay(value);
1601 isValid = this.disabledDays.indexOf(weekdayNum) === -1;
1604 if (isValid && this.min) {
1605 isValid = isValid && !isBefore(value, this.min);
1608 if (isValid && this.max) {
1609 isValid = isValid && !isAfter(value, this.max);
1616 * Check if the selection mode is 'single'
1619 private isSingleSelection(): boolean {
1620 return this.selectionMode === 'single';
1624 * Check if the selection mode is 'range'
1627 private isRangeSelection(): boolean {
1628 return this.selectionMode === 'range';
1632 * Check if the selection mode is 'multiple'
1635 private isMultiSelection(): boolean {
1636 return this.selectionMode === 'multiple';
1639 private getHiddenElementDimensions(element: any): any {
1640 let dimensions: any = {};
1641 element.style.visibility = 'hidden';
1642 element.style.display = 'block';
1643 dimensions.width = element.offsetWidth;
1644 dimensions.height = element.offsetHeight;
1645 element.style.display = 'none';
1646 element.style.visibility = 'visible';
1651 private getViewport(): any {
1654 e = d.documentElement,
1655 g = d.getElementsByTagName('body')[0],
1656 w = win.innerWidth || e.clientWidth || g.clientWidth,
1657 h = win.innerHeight || e.clientHeight || g.clientHeight;
1659 return { width: w, height: h };
1664 public seconds = false;
1665 public mtime: any = { hour: 0, minute: 0, second: 0 };
1666 public TimerChange(time: any) {
1669 if (this.value.length) {
1670 value = this.value[this.valueIndex];
1675 if (this.type === 'timer') {
1682 if (this.disabled || !value) {
1683 event.preventDefault();
1687 let minute = time.minute;
1688 let hour = time.hour;
1689 let second = time.second;
1690 this.minValue = minute;
1691 this.hourValue = hour;
1692 this.secValue = second;
1693 let selectedTime = setMinutes(value, minute);
1694 selectedTime = setHours(selectedTime, hour);
1695 selectedTime = setSeconds(selectedTime, second);
1696 let done = this.setSelectedTime(selectedTime);
1698 // Focus the input and select its value when model updated
1700 event.preventDefault();
1703 private mouseIn :boolean = false;
1704 private Mouseout(event:any)
1706 this.mouseIn = false;
1708 private Mouseover(event:any)
1710 this.mouseIn = true;