Source: datepicker.js

/**
 * @class ClassDatepicker
 * @type {{_pickers: {}, _getNextState: _getNextState, _checkNextStateAvalibility: _checkNextStateAvalibility, changeView: changeView, _flagSelfManipulate: boolean, selectTime: selectTime, selectDate: selectDate, selectMonth: selectMonth, selectYear: selectYear, _formatDigit: _formatDigit, destroy: destroy, _syncWithImportData: _syncWithImportData, _attachEvents: _attachEvents, _updateInputElement: _updateInputElement, _defineOnInitState: _defineOnInitState, events: {}, _viewed: boolean, init: init}}
 */
var ClassDatepicker = {
    /**
     *
     * @desc list of picker obejcts like dayPicker,monthPicker,yearPicker
     * @private
     */
    _pickers: {},


    /**
     * @desc save current visibility state of plugin
     * @private
     */
    _viewed: false,


    /**
     * @desc if plugin selector detect as a div or any element exsept inpout set as true
     * @private
     */
    _inlineView: false,


    /**
     * @desc define next state of {@link ClassDatepicker._pickers}
     * @param {string} action . Acceptable Value : 'day','month',year
     * @returns {*}
     * @private
     */
    _getNextState: function (action) {
        var currentState = this.currentView;
        var nextState = this.currentView;
        if (action === 'next') {
            if (currentState === 'month' && this.dayPicker) {
                nextState = 'day';
            }
            if (currentState === 'year') {
                if (this.monthPicker) {
                    nextState = 'month';
                } else {
                    if (this.dayPicker) {
                        nextState = 'day';
                    }
                }
            }
        }
        else if (action === 'prev') {
            if (currentState === 'month' && this.yearPicker) {
                nextState = 'year';
            }
            if (currentState === 'day') {
                if (this.monthPicker) {
                    nextState = 'month';
                } else {
                    if (this.yearPicker) {
                        nextState = 'year';
                    }
                }
            }
        }
        return this._checkNextStateAvalibility(nextState);
    },


    /**
     * @desc check next state is available in {@link ClassDatepicker._pickers}
     * @param {string} state Accepptable Value: 'day','month','year'
     * @returns {*}
     * @private
     */
    _checkNextStateAvalibility: function (state) {
        if (!this._pickers[state]) {
            this.element.main.hide();
            return false;
            $.error(state + "Picker Set as {enabled:false} and dos not exist!! Set viewMode to Enabled view Check Configuration");
        }
        return state;
    },


    /**
     * @desc update {@link ClassNavigator} object switch text
     * @param switchStr
     * @public
     * @returns {ClassDatepicker}
     */
    updateNavigator: function (switchStr) {
        if (this.navigator) {
            this.navigator.updateSwitchBtn(this._formatDigit(switchStr));
        }
        return this;
    },


    /**
     * @desc update {@link ClassNavigator} relaion state
     * @param switchStr
     * @public
     * @returns {ClassDatepicker}
     */
    switchNavigatorRelation: function (newState) {
        if (this.navigator) {
            this.navigator.switchRelation(newState);
        }
        return this;
    },


    /**
     * @desc change {@link _pickers} visibility
     * @param state
     * @param action
     * @returns {ClassDatepicker}
     */
    changeView: function (state, action) {
        var self = this;
        var newState;
        if (!action) {
            newState = this._checkNextStateAvalibility(state);
        } else {
            newState = this._getNextState(action);
        }
        if (newState) {
            self.publishInDic(self._pickers, 'hide');
            self._pickers[newState].show();
            self.switchNavigatorRelation(newState);
            self.currentView = newState;
        }
        return this;
    },


    /**
     * @desc used in {@link ClassDatepicker._attachEvents}
     * @private
     */
    _flagSelfManipulate: true,


    /**
     * @desc only called by {@link ClassTimepicker}
     * @param key
     * @param val
     * @event
     */
    selectTime: function (key, val) {
        this.state.setTime(key, val);
        this._updateInputElement();
        this.onSelect(key, this);
    },


    /**
     * @desc only called by {@link ClassDaypicker}
     * @param key
     * @param unixDate
     * @returns {ClassDatepicker}
     * @event
     */
    selectDate: function (unixDate) {
        var self = this;
        self.state.setSelected('unix', unixDate);
        this.state.syncViewWithelected();
        switch (self.currentView) {
            case ('month'):
                self.monthPicker.selectMonth();
                break;
            case ('year'):
                self.yearPicker.selectYear();
                break;
            case ('day'):
                self.dayPicker.selectDay();
                break;
        }
        self._updateInputElement();
        self.onSelect(unixDate, this);
        if (self.autoClose) {
            self.element.main.hide();
        }
        return this;
    },


    /**
     * @desc only called by {@link ClassDaypicker}
     * @param key
     * @param unixDate
     * @returns {ClassDatepicker}
     * @event
     */
    selectDateTime: function (unixDate) {
        var self = this;
        self.state.setSelectedDateTime('unix', unixDate);
        this.state.syncViewWithelected();
        switch (self.currentView) {
            case ('month'):
                self.monthPicker.selectMonth();
                break
            case ('year'):
                self.yearPicker.selectYear();
                break
            case ('day'):
                self.dayPicker.selectDay();
                break
        }
        self._updateInputElement();
        self.onSelect(unixDate, this);
        if (self.autoClose) {
            self.element.main.hide();
        }
        return this;
    },


    /**
     * @desc only called by {@link ClassMonthPicker}
     * @param monthNum
     * @returns {ClassDatepicker}
     */
    selectMonth: function (monthNum) {
        var self = this;
        if (this.justSelectOnDate) {
            self.state.setView('month', monthNum);
        } else {
            self.state.setSelected('month', monthNum);
            self.state.setSelected('year', self.state.view.year);
            self.state.syncViewWithelected();
        }
        self._updateInputElement();
        self.changeView(self.currentView, 'next');
        return this;
    },


    /**
     * @desc only called by {@link ClassYearPicker}
     * @param yearNum
     * @returns {ClassDatepicker}
     */
    selectYear: function (yearNum) {
        var self = this;
        if (this.justSelectOnDate) {
            self.state.setView('year', yearNum);
        } else {
            self.state.setSelected('year', yearNum);
            self.state.syncViewWithelected();
        }
        self._updateInputElement();
        self.changeView(self.currentView, 'next');
        return this;
    },


    /**
     * @desc check {@link ClassDatepicker.persianDigit} and if set true all digit convert to persian
     * @param digit
     * @returns {*}
     * @private
     */
    _formatDigit: function (digit) {
        if (this.persianDigit && digit) {
            return digit.toString().toPersianDigit();
        }
        else {
            return digit;
        }
    },


    /**
     * @desc use in {@link ClassDatepicker._attachEvents}
     * @param pasted
     * @returns {ClassDatepicker}
     * @private
     */
    _syncWithImportData: function (pasted) {
        if (pasted) {
            var self = this;
            if (jQuery.isNumeric(pasted)) {
                var newPersainDate = new persianDate(pasted);
                self.state.setSelected('unix', newPersainDate);
                self._updateInputElement();
            } else {
                var persianDateArray = self.validatePersianDateString(pasted);
                if (persianDateArray != null) {
                    delay(function () {

                        var newPersainDate = new persianDate(persianDateArray);
                        self.selectDate(newPersainDate.valueOf());
                    }, self.inputDelay)
                }
            }
        }
        return this;
    },


    /**
     * @desc  Attach all dom events to rendered element.
     * @returns {ClassDatepicker}
     * @private
     */
    _attachEvents: function () {
        var self = this;
        $(window).resize(function () {
            self.view.fixPosition(self);
        });
        if (self.observer) {
            /////////////////   Manipulate by Copy And paste
            self.inputElem.bind('paste', function (e) {
                delay(function () {
                    self._syncWithImportData(e.target.value);
                }, 60);
            });
            /////////////////   Manipulate by alt changes
            $(self.altField).bind("change", function () {
                if (!self._flagSelfManipulate) {
                    var newDate = new Date($(this).val());
                    if (newDate !== "Invalid Date") {
                        var newPersainDate = new persianDate(newDate);
                        self.selectDate(newPersainDate.valueOf());
                    }
                }
            });
            /////////////////   Manipulate by keyboard
            var ctrlDown = false;
            var ctrlKey = [17, 91], vKey = 86, cKey = 67;
            $(document).keydown(function (e) {
                if ($.inArray(e.keyCode, ctrlKey) > 0)
                    ctrlDown = true;
            }).keyup(function (e) {
                if ($.inArray(e.keyCode, ctrlKey) > 0)
                    ctrlDown = false;
            });
            self.inputElem.bind("keyup", function (e) {
                var $self = $(this);
                if (!self._flagSelfManipulate) {
                    var trueKey = false;
                    if (e.keyCode === 8 || e.keyCode < 105 && e.keyCode > 96 || e.keyCode < 58 && e.keyCode > 47 || (ctrlDown && (e.keyCode == vKey || $.inArray(e.keyCode, ctrlKey) > 0  ))) {
                        trueKey = true;
                    }
                    if (trueKey) {
                        self._syncWithImportData($self.val());
                    }
                }
            });
        }

        self.inputElem.focus(function () {
            self.show();
        });
        self.inputElem.click(function (e) {
            e.stopPropagation();
            return false;
        });
        self.inputElem.blur(function () {
            if (!$.browser.msie) {
                self.hide();
            }
        });
        $(document).not(".datepicker-plot-area,.datepicker-plot-area > *").click(function (e) {
            self.inputElem.blur();
            self.hide();

        });
        $(self.element.main).mousedown(function (e) {
            e.stopPropagation();
            return false;
        });
        return this;
    },


    /**
     * @desc update input and altField input elemet value
     * @returns {ClassDatepicker}
     * @private
     */
    _updateInputElement: function () {
        var self = this;
        self._flagSelfManipulate = true;
        // Update Alt Field
        self.altField.val(self.altFieldFormatter(self.state.selected.unixDate)).trigger('change');
        ;
        // Update Display Field
        self.inputElem.val(self.formatter(self.state.selected.unixDate)).trigger('change');
        ;
        self._flagSelfManipulate = false;
        return self;
    },


    /**
     * @desc bootstrap method of {@link ClassDatepicker}
     * @returns {ClassDatepicker}
     * @private
     */
    _defineOnInitState: function () {
        if ($(this.$container)[0].nodeName == 'INPUT') {
            var garegurianDate = new Date(this.inputElem.val()).valueOf();
            this.$container = $('body');
        }
        else {
            var garegurianDate = new Date($(this.$container).data('date')).valueOf();
            this._inlineView = true;
        }
        if (garegurianDate && garegurianDate != 'undefined') {
            this.state.unixDate = garegurianDate;
        }
        else {
            this.state.unixDate = new Date().valueOf();
        }
        this.altField = $(this.altField);
        this.state.setSelectedDateTime('unix', this.state.unixDate);
        this.state.setTime('unix', this.state.unixDate);
        this.state.setView('unix', this.state.unixDate);
        return this;
    },


    /**
     * @desc set time of timepicker
     */
    setTime: function () {
        this.timePicker.setTime(this.state.selected.unixDate);
    },


    /**
     * @desc set date of datepicker
     */
    setDate: function (p) {
        var date = new persianDate(p);
        this.selectDateTime(date.valueOf());
        if (this.timePicker.enabled)
            this.setTime();
        return this;
    },


    /**
     * @desc initilize {@link ClassDatepicker}
     * @returns {ClassDatepicker}
     */
    init: function () {
        var self = this;
        this.state = new State({datepicker: self});
        this.compatConfig();
        this._defineOnInitState();
        this._updateInputElement();
        this.view = this.views['default'];
        this.view.render(this);
        this.inputElem.data("datepicker", this);
        this.inputElem.addClass(self.cssClass);
        this._attachEvents();
        return this;
    }
};

var Datepicker = function (mainElem, options) {
    return inherit(this, [ClassSprite, ClassCompat, ClassDatepicker, ViewsDatePicker, ClassConfig, options, {
        $container: mainElem,
        inputElem: $(mainElem)
    }]);
};