aboutsummaryrefslogtreecommitdiffstats
path: root/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts
diff options
context:
space:
mode:
Diffstat (limited to 'sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts')
-rw-r--r--sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts1712
1 files changed, 1712 insertions, 0 deletions
diff --git a/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts
new file mode 100644
index 00000000..493e0cb2
--- /dev/null
+++ b/sdc-workflow-designer-ui/src/app/paletx/plx-datepicker/picker.component.ts
@@ -0,0 +1,1712 @@
+/**
+ * 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<any>();
+
+ /**
+ * Callback to invoke when dropdown gets focus.
+ * */
+ @Output() onConfirm: any = new EventEmitter<any>();
+
+ /**
+ * Callback to invoke when dropdown loses focus.
+ * */
+ @Output() onBlur: any = new EventEmitter<any>();
+
+ /**
+ * Callback to invoke when a invalid date is selected.
+ * */
+ @Output() onInvalid: any = new EventEmitter<any>();
+
+
+
+ @ViewChild('container') containerElm: ElementRef;
+ @ViewChild('textInput') textInputElm: ElementRef;
+ @ViewChild('dialog') dialogElm: ElementRef;
+
+ public calendarDays: Array<any[]>;
+ public calendarWeekdays: string[];
+ public calendarMonths: Array<string[]>;
+ public calendarYears: Array<string[]> = [];
+ 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;
+ }
+}