+Фильтр для поля для ввода корректных дат
This commit is contained in:
		
							
								
								
									
										997
									
								
								es6/smart-date.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										997
									
								
								es6/smart-date.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,997 @@
 | 
			
		||||
 | 
			
		||||
class AbstractSmartDatePart
 | 
			
		||||
{
 | 
			
		||||
    constructor(placeholder, isStatic)
 | 
			
		||||
    {
 | 
			
		||||
        this._state = AbstractSmartDatePart.STATE_NONE;
 | 
			
		||||
        this._cleanedValue = '';
 | 
			
		||||
        this._parsedValue = '';
 | 
			
		||||
        
 | 
			
		||||
        this._scanned = '';
 | 
			
		||||
        this._placeholder = placeholder;
 | 
			
		||||
        this._static = isStatic;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    state()
 | 
			
		||||
    {
 | 
			
		||||
        return this._state;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isNoState()
 | 
			
		||||
    {
 | 
			
		||||
        return this._state === AbstractSmartDatePart.STATE_NONE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isIncomplete()
 | 
			
		||||
    {
 | 
			
		||||
        return this._state === AbstractSmartDatePart.STATE_INCOMPLETE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isComplete()
 | 
			
		||||
    {
 | 
			
		||||
        return this._state === AbstractSmartDatePart.STATE_COMPLETE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    resetState()
 | 
			
		||||
    {
 | 
			
		||||
        this._state = AbstractSmartDatePart.STATE_NONE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    cleanedValue()
 | 
			
		||||
    {
 | 
			
		||||
        return this._cleanedValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    parsedValue()
 | 
			
		||||
    {
 | 
			
		||||
        return this._parsedValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    placeHolder()
 | 
			
		||||
    {
 | 
			
		||||
        return this._placeholder;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isStatic()
 | 
			
		||||
    {
 | 
			
		||||
        return this._static;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _isNumericSymbol(symbol)
 | 
			
		||||
    {
 | 
			
		||||
        return '0' <= symbol && symbol <= '9';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     * @returns {Boolean}
 | 
			
		||||
     */
 | 
			
		||||
    check(state)
 | 
			
		||||
    {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
AbstractSmartDatePart.STATE_NONE = 0;
 | 
			
		||||
AbstractSmartDatePart.STATE_INCOMPLETE = 1;
 | 
			
		||||
AbstractSmartDatePart.STATE_COMPLETE = 2;
 | 
			
		||||
 | 
			
		||||
class SmartDateStaticPart extends AbstractSmartDatePart
 | 
			
		||||
{
 | 
			
		||||
    constructor(placeholder)
 | 
			
		||||
    {
 | 
			
		||||
        super(placeholder, true);
 | 
			
		||||
        this._cleanedValue = placeholder;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     * @returns {Boolean}
 | 
			
		||||
     */
 | 
			
		||||
    parse(state)
 | 
			
		||||
    {
 | 
			
		||||
        let value = state.value();
 | 
			
		||||
        let index = state.valueIndex();
 | 
			
		||||
        
 | 
			
		||||
        while (index < value.length && !this._isNumericSymbol(value[index])) {
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._parsedValue = value.substring(state.valueIndex(), index);
 | 
			
		||||
        this._state = AbstractSmartDatePart.STATE_COMPLETE;
 | 
			
		||||
 | 
			
		||||
        state.setValueIndex(index);
 | 
			
		||||
        
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AbstractSmartDateNumericPart extends AbstractSmartDatePart
 | 
			
		||||
{
 | 
			
		||||
    constructor(placeholder, size)
 | 
			
		||||
    {
 | 
			
		||||
        super(placeholder, false);
 | 
			
		||||
        this._numericValue = 0;
 | 
			
		||||
        this._size = size;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    numericValue()
 | 
			
		||||
    {
 | 
			
		||||
        return this._numericValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Date} date
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    formatDate(date)
 | 
			
		||||
    {
 | 
			
		||||
        return '';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    formatValue(value)
 | 
			
		||||
    {
 | 
			
		||||
        let stringValue = String(this._correctValue(value));
 | 
			
		||||
        
 | 
			
		||||
        if (stringValue === '0') {
 | 
			
		||||
            return stringValue;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (stringValue.length > this._size) {
 | 
			
		||||
            return stringValue.substring(0, this._size);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (stringValue.length < this._size) {
 | 
			
		||||
            return stringValue.padStart(this._size, '0');
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return stringValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _correctValue(value)
 | 
			
		||||
    {
 | 
			
		||||
        return Math.round(value);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    scanStringNumber(state)
 | 
			
		||||
    {
 | 
			
		||||
        let index = state.valueIndex();
 | 
			
		||||
        let value = state.value();
 | 
			
		||||
        
 | 
			
		||||
        while (index < value.length && this._isNumericSymbol(value[index])) {
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let stringValue = value.substring(state.valueIndex(), index);
 | 
			
		||||
        
 | 
			
		||||
        state.setValueIndex(index);
 | 
			
		||||
        
 | 
			
		||||
        return stringValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    calculateNumericValue(stringValue, maximalNumber)
 | 
			
		||||
    {
 | 
			
		||||
        var startIndex = 0;
 | 
			
		||||
        var endIndex = 0;
 | 
			
		||||
        var numericValue = 0;
 | 
			
		||||
        
 | 
			
		||||
        while (startIndex < stringValue.length && stringValue[startIndex] === 0) {
 | 
			
		||||
            startIndex++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        endIndex = startIndex;
 | 
			
		||||
        
 | 
			
		||||
        while (endIndex < stringValue.length && numericValue < maximalNumber) {
 | 
			
		||||
            numericValue = numericValue * 10 + Number(stringValue[endIndex]);
 | 
			
		||||
            endIndex++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (numericValue > maximalNumber) {
 | 
			
		||||
            endIndex--;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return Number(stringValue.substring(startIndex, endIndex));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     * @param {Number} maximalValue
 | 
			
		||||
     */
 | 
			
		||||
    scanValue(state, maximalValue)
 | 
			
		||||
    {
 | 
			
		||||
        let index = state.valueIndex();
 | 
			
		||||
        let value = state.value();
 | 
			
		||||
        
 | 
			
		||||
        let numericValue = 0;
 | 
			
		||||
        
 | 
			
		||||
        while (index < value.length && this._isNumericSymbol(value[index]) && numericValue < maximalValue) {
 | 
			
		||||
            numericValue = numericValue * 10 + Number(value[index]);
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (numericValue > maximalValue) {
 | 
			
		||||
            index--;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let stringValue = value.substring(state.valueIndex(), index);
 | 
			
		||||
        
 | 
			
		||||
        state.setValueIndex(index);
 | 
			
		||||
        
 | 
			
		||||
        return stringValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _defineParsingState(maximalValue, isAtEnd)
 | 
			
		||||
    {
 | 
			
		||||
        if (this._numericValue > 0 && (this._parsedValue.length >= this._size || !isAtEnd)) {
 | 
			
		||||
            this._state = AbstractSmartDatePart.STATE_COMPLETE;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (this._numericValue * 10 > maximalValue) {
 | 
			
		||||
            this._state = AbstractSmartDatePart.STATE_COMPLETE;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._state = AbstractSmartDatePart.STATE_INCOMPLETE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _defineCleanValue()
 | 
			
		||||
    {
 | 
			
		||||
        this._cleanedValue = '';
 | 
			
		||||
        
 | 
			
		||||
        if (this.isNoState()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this.isComplete()) {
 | 
			
		||||
            this._cleanedValue = this.formatValue(this._numericValue);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this._parsedValue.length > 0) {
 | 
			
		||||
            this._cleanedValue = String(this._numericValue);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SmartDateDayPart extends AbstractSmartDateNumericPart
 | 
			
		||||
{
 | 
			
		||||
    constructor()
 | 
			
		||||
    {
 | 
			
		||||
        super(SmartDateFormat.DAY_PLACEHOLDER, 2);
 | 
			
		||||
        this.maximalValue = SmartDateDayPart.MAXIMAL_VALUE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    resetState()
 | 
			
		||||
    {
 | 
			
		||||
        super.resetState();
 | 
			
		||||
        this.maximalValue = SmartDateDayPart.MAXIMAL_VALUE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    setMaximalValue(value)
 | 
			
		||||
    {
 | 
			
		||||
        if (value != value /* isNaN */ || value < SmartDateDayPart.MAXIMAL_VALUE_LOW_LIMIT || SmartDateDayPart.MAXIMAL_VALUE_HIGH_LIMIT < value) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this.maximalValue = value;
 | 
			
		||||
        
 | 
			
		||||
        if (this._numericValue <= this.maximalValue) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._numericValue = this.maximalValue;
 | 
			
		||||
        this._defineCleanValue();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    resetMaximalValue()
 | 
			
		||||
    {
 | 
			
		||||
        this.maximalValue = SmartDateDayPart.MAXIMAL_VALUE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Date} date
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    formatDate(date)
 | 
			
		||||
    {
 | 
			
		||||
        return this.formatValue(date.getDate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     */
 | 
			
		||||
    parse(state)
 | 
			
		||||
    {
 | 
			
		||||
        this._parsedValue = this.scanStringNumber(state);
 | 
			
		||||
        this._numericValue = this.calculateNumericValue(this._parsedValue, this.maximalValue);
 | 
			
		||||
        
 | 
			
		||||
        this._defineParsingState(this.maximalValue, state.isOver());
 | 
			
		||||
        this._defineCleanValue();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartDateDayPart.MAXIMAL_VALUE_LOW_LIMIT = 28;
 | 
			
		||||
SmartDateDayPart.MAXIMAL_VALUE_HIGH_LIMIT = 31;
 | 
			
		||||
 | 
			
		||||
SmartDateDayPart.MAXIMAL_VALUE = SmartDateDayPart.MAXIMAL_VALUE_HIGH_LIMIT;
 | 
			
		||||
 | 
			
		||||
class SmartDateMonthPart extends AbstractSmartDateNumericPart
 | 
			
		||||
{
 | 
			
		||||
    constructor()
 | 
			
		||||
    {
 | 
			
		||||
        super(SmartDateFormat.MONTH_PLACEHOLDER, 2);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Date} date
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    formatDate(date)
 | 
			
		||||
    {
 | 
			
		||||
        return this.formatValue(date.getMonth() + 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     */
 | 
			
		||||
    parse(state)
 | 
			
		||||
    {
 | 
			
		||||
        this._parsedValue = this.scanStringNumber(state);
 | 
			
		||||
        this._numericValue = this.calculateNumericValue(this._parsedValue, SmartDateMonthPart.MAXIMAL_VALUE);
 | 
			
		||||
        
 | 
			
		||||
        this._defineParsingState(SmartDateMonthPart.MAXIMAL_VALUE, state.isOver());
 | 
			
		||||
        this._defineCleanValue();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartDateMonthPart.MAXIMAL_VALUE = 12;
 | 
			
		||||
 | 
			
		||||
class SmartDateYearPart extends AbstractSmartDateNumericPart
 | 
			
		||||
{
 | 
			
		||||
    constructor()
 | 
			
		||||
    {
 | 
			
		||||
        super(SmartDateFormat.YEAR_PLACEHOLDER, 4);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Date} date
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    formatDate(date)
 | 
			
		||||
    {
 | 
			
		||||
        return this.formatValue(date.getFullYear());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {SmartDateCheckState} state
 | 
			
		||||
     * @returns {Boolean}
 | 
			
		||||
     */
 | 
			
		||||
    parse(state)
 | 
			
		||||
    {
 | 
			
		||||
        this._parsedValue = this.scanStringNumber(state);
 | 
			
		||||
        this._numericValue = Number(this._parsedValue);
 | 
			
		||||
        
 | 
			
		||||
        this._defineParsingState();
 | 
			
		||||
        
 | 
			
		||||
        if (this._numericValue > SmartDateYearPart.MAXIMAL_VALUE) {
 | 
			
		||||
            this._numericValue = SmartDateYearPart.MAXIMAL_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this.isComplete() && this._numericValue < SmartDateYearPart.MINIMAL_VALUE) {
 | 
			
		||||
            this._numericValue = SmartDateYearPart.MINIMAL_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._defineCleanValue();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _defineParsingState()
 | 
			
		||||
    {
 | 
			
		||||
        if (this._numericValue > 0 && this._parsedValue.length >= this._size) {
 | 
			
		||||
            this._state = AbstractSmartDatePart.STATE_COMPLETE;
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._state = AbstractSmartDatePart.STATE_INCOMPLETE;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _defineCleanValue()
 | 
			
		||||
    {
 | 
			
		||||
        if (this._numericValue > 0) {
 | 
			
		||||
            this._cleanedValue = String(this._numericValue);
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            this._cleanedValue = '';
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartDateYearPart.MINIMAL_VALUE = 1000;
 | 
			
		||||
SmartDateYearPart.MAXIMAL_VALUE = 9999;
 | 
			
		||||
 | 
			
		||||
class SmartDateParseState
 | 
			
		||||
{
 | 
			
		||||
    constructor()
 | 
			
		||||
    {
 | 
			
		||||
        this._value = '';
 | 
			
		||||
        this._valueIndex = 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    start(value)
 | 
			
		||||
    {
 | 
			
		||||
        this._value = value;
 | 
			
		||||
        this._valueIndex = 0;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    current()
 | 
			
		||||
    {
 | 
			
		||||
        return this.isOver() ? '' : this._value[this._valueIndex];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    next()
 | 
			
		||||
    {
 | 
			
		||||
        if (this.isOver()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._valueIndex++;
 | 
			
		||||
        
 | 
			
		||||
        return !this.isOver();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isOver()
 | 
			
		||||
    {
 | 
			
		||||
        return this._valueIndex >= this._value.length;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    value()
 | 
			
		||||
    {
 | 
			
		||||
        return this._value;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    valueIndex()
 | 
			
		||||
    {
 | 
			
		||||
        return this._valueIndex;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    setValueIndex(index)
 | 
			
		||||
    {
 | 
			
		||||
        if (this._valueIndex < index) {
 | 
			
		||||
            this._valueIndex = index;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SmartDateFormat
 | 
			
		||||
{
 | 
			
		||||
    constructor(format)
 | 
			
		||||
    {
 | 
			
		||||
        this._day = null;
 | 
			
		||||
        this._month = null;
 | 
			
		||||
        this._year = null;
 | 
			
		||||
        
 | 
			
		||||
        this.loadFormat(format);
 | 
			
		||||
        
 | 
			
		||||
        this._parseState = new SmartDateParseState();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    loadFormat(format)
 | 
			
		||||
    {
 | 
			
		||||
        let index = 0;
 | 
			
		||||
        let lowFormat = format.toLowerCase();
 | 
			
		||||
        
 | 
			
		||||
        this._format = format;
 | 
			
		||||
        this._parts = [];
 | 
			
		||||
        
 | 
			
		||||
        while (index < format.length) {
 | 
			
		||||
            index = this._scanPart(index, format, lowFormat);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _scanPart(index, format, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        switch (lowFormat[index]) {
 | 
			
		||||
            case SmartDateFormat.DAY_ANCHOR:
 | 
			
		||||
                return this._scanDay(index, lowFormat);
 | 
			
		||||
                
 | 
			
		||||
            case SmartDateFormat.MONTH_ANCHOR:
 | 
			
		||||
                return this._scanMonth(index, lowFormat);
 | 
			
		||||
                
 | 
			
		||||
            case SmartDateFormat.YEAR_ANCHOR:
 | 
			
		||||
                return this._scanYear(index, lowFormat);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return this._scanStatic(index, format, lowFormat);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _scanDay(startIndex, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        this._checkLastIsStatic();
 | 
			
		||||
        
 | 
			
		||||
        this._day = new SmartDateDayPart();
 | 
			
		||||
        
 | 
			
		||||
        this._parts.push(this._day);
 | 
			
		||||
        
 | 
			
		||||
        return this._avoidAnchor(SmartDateFormat.DAY_ANCHOR, startIndex, lowFormat);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _scanMonth(startIndex, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        this._checkLastIsStatic();
 | 
			
		||||
        
 | 
			
		||||
        this._month = new SmartDateMonthPart();
 | 
			
		||||
        
 | 
			
		||||
        this._parts.push(this._month);
 | 
			
		||||
        
 | 
			
		||||
        return this._avoidAnchor(SmartDateFormat.MONTH_ANCHOR, startIndex, lowFormat);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _scanYear(startIndex, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        this._checkLastIsStatic();
 | 
			
		||||
        
 | 
			
		||||
        this._year = new SmartDateYearPart();
 | 
			
		||||
        
 | 
			
		||||
        this._parts.push(this._year);
 | 
			
		||||
        
 | 
			
		||||
        return this._avoidAnchor(SmartDateFormat.YEAR_ANCHOR, startIndex, lowFormat);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _avoidAnchor(anchor, startIndex, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        let endingIndex = startIndex;
 | 
			
		||||
        
 | 
			
		||||
        while (endingIndex < lowFormat.length && lowFormat[endingIndex] === anchor) {
 | 
			
		||||
            endingIndex++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return endingIndex;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _checkLastIsStatic()
 | 
			
		||||
    {
 | 
			
		||||
        if (this._parts.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!this._parts[this._parts.length - 1].isStatic()) {
 | 
			
		||||
            throw new Error('Dynamic date parts must be separated by static substrings');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _scanStatic(startIndex, format, lowFormat)
 | 
			
		||||
    {
 | 
			
		||||
        let endingIndex = startIndex;
 | 
			
		||||
        
 | 
			
		||||
        while (endingIndex < format.length && this._isStaticAnchorSymbol(lowFormat[endingIndex])) {
 | 
			
		||||
            endingIndex++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._parts.push(new SmartDateStaticPart(format.substring(startIndex, endingIndex)));
 | 
			
		||||
        
 | 
			
		||||
        return endingIndex;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _isStaticAnchorSymbol(symbol)
 | 
			
		||||
    {
 | 
			
		||||
        return symbol !== SmartDateFormat.DAY_ANCHOR
 | 
			
		||||
            && symbol !== SmartDateFormat.MONTH_ANCHOR
 | 
			
		||||
            && symbol !== SmartDateFormat.YEAR_ANCHOR;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    getPlaceHolder()
 | 
			
		||||
    {
 | 
			
		||||
        let placeHolder = '';
 | 
			
		||||
        
 | 
			
		||||
        for (let i = 0; i < this._parts.length; i++) {
 | 
			
		||||
            placeHolder += this._parts[i].placeHolder();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return placeHolder;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * @param {Date} date
 | 
			
		||||
     * @returns {String}
 | 
			
		||||
     */
 | 
			
		||||
    formatDate(date)
 | 
			
		||||
    {
 | 
			
		||||
        var formattedDate = '';
 | 
			
		||||
        
 | 
			
		||||
        for (let i = 0; i < this._parts.length; i++) {
 | 
			
		||||
            if (this._parts[i].isStatic()) {
 | 
			
		||||
                formattedDate += this._parts[i].placeHolder();
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                formattedDate += this._parts[i].formatDate(date);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return formattedDate;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    parse(value, autocomplete)
 | 
			
		||||
    {
 | 
			
		||||
        this._resetParseState();
 | 
			
		||||
        
 | 
			
		||||
        this._parseState.start(value);
 | 
			
		||||
        
 | 
			
		||||
        this._parseParts(autocomplete);
 | 
			
		||||
        
 | 
			
		||||
        this._correctDate();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _resetParseState()
 | 
			
		||||
    {
 | 
			
		||||
        for (let i = 0; i < this._parts.length; i++) {
 | 
			
		||||
            this._parts[i].resetState();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _parseParts(autocomplete)
 | 
			
		||||
    {
 | 
			
		||||
        let index = 0;
 | 
			
		||||
        
 | 
			
		||||
        while (index < this._parts.length && (autocomplete || !this._parseState.isOver())) {
 | 
			
		||||
            this._parts[index].parse(this._parseState);
 | 
			
		||||
            
 | 
			
		||||
            if (!this._parts[index].isComplete()) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _correctDate()
 | 
			
		||||
    {
 | 
			
		||||
        if (!this._day.isComplete() || !this._month.isComplete()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._day.setMaximalValue(this._getMonthSize(this._month.numericValue() - 1, !this._year.isComplete() || this._isLeaPYear(this._year.numericValue())));
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    getParsedDate()
 | 
			
		||||
    {
 | 
			
		||||
        if (!this.isComplete()) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return new Date(this._year.numericValue(), this._month.numericValue() - 1, this._day.numericValue());
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _getMonthSize(monthIndex, isLeapYear)
 | 
			
		||||
    {
 | 
			
		||||
        if (monthIndex < 0 || monthIndex >= SmartDateMonthPart.MAXIMAL_VALUE) {
 | 
			
		||||
            return SmartDateMonthPart.MAXIMAL_VALUE;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (monthIndex === 1 && isLeapYear) { // February
 | 
			
		||||
            return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex] + 1;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return SmartDateFormat.BASIC_MONTH_SIZES[monthIndex];
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _isLeaPYear(year)
 | 
			
		||||
    {
 | 
			
		||||
        return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    getCorrectedValue()
 | 
			
		||||
    {
 | 
			
		||||
        let index = 0;
 | 
			
		||||
        let value = '';
 | 
			
		||||
        
 | 
			
		||||
        while (index < this._parts.length && !this._parts[index].isNoState()) {
 | 
			
		||||
            value += this._parts[index].cleanedValue();
 | 
			
		||||
            index++;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return value;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    isComplete()
 | 
			
		||||
    {
 | 
			
		||||
        return this._parts[this._parts.length - 1].isComplete();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartDateFormat.DAY_ANCHOR = 'd';
 | 
			
		||||
SmartDateFormat.DAY_PLACEHOLDER = 'ДД';
 | 
			
		||||
 | 
			
		||||
SmartDateFormat.MONTH_ANCHOR = 'm';
 | 
			
		||||
SmartDateFormat.MONTH_PLACEHOLDER = 'ММ';
 | 
			
		||||
 | 
			
		||||
SmartDateFormat.YEAR_ANCHOR = 'y';
 | 
			
		||||
SmartDateFormat.YEAR_PLACEHOLDER = 'ГГГГ';
 | 
			
		||||
 | 
			
		||||
SmartDateFormat.BASIC_MONTH_SIZES = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
 | 
			
		||||
class SmartDateField
 | 
			
		||||
{
 | 
			
		||||
    constructor(field, options)
 | 
			
		||||
    {
 | 
			
		||||
        this._dateYmdParser = new RegExp('^([0-9]{4})[,\._\/\-]+([0-9]{1,2})[,\._\/\-]+([0-9]{1,2})$');
 | 
			
		||||
        this._dateDmyParser = new RegExp('^([0-9]{1,2})[,\._\/\-]+([0-9]{1,2})[,\._\/\-]+([0-9]{4})$');
 | 
			
		||||
                
 | 
			
		||||
        this._loadOptions(options);
 | 
			
		||||
        
 | 
			
		||||
        this._formatter = new SmartDateFormat(this._format, this._minimalDate, this._maximalDate);
 | 
			
		||||
        
 | 
			
		||||
        this._lastCorrectValue = (this._defaultDate === null ? null : this._formatter.formatDate(this._defaultDate));
 | 
			
		||||
 | 
			
		||||
        this._initField(field);
 | 
			
		||||
 | 
			
		||||
        this._subscribeEvents();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    
 | 
			
		||||
    _loadDefaultOptions()
 | 
			
		||||
    {
 | 
			
		||||
        this._format = 'd.m.y';
 | 
			
		||||
        this._minimalDate = new Date(1900, 0, 1);
 | 
			
		||||
        this._maximalDate = new Date(2099, 11, 31);
 | 
			
		||||
        this._defaultDate = null;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _loadOptions(options)
 | 
			
		||||
    {
 | 
			
		||||
        this._loadDefaultOptions();
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(options) !== 'object' || options == null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(options.format) === 'string'
 | 
			
		||||
            && options.format.indexOf('d') >= 0
 | 
			
		||||
            && options.format.indexOf('m') >= 0
 | 
			
		||||
            && options.format.indexOf('y') >= 0) {
 | 
			
		||||
            this._format = options.format;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(options.minimal) !== 'undefined') {
 | 
			
		||||
            let date = this._parseDateValue(options.minimal);
 | 
			
		||||
            this._minimalDate = date;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(options.maximal) !== 'undefined') {
 | 
			
		||||
            let date = this._parseDateValue(options.maximal);
 | 
			
		||||
            this._maximalDate = date;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(options.default) !== 'undefined') {
 | 
			
		||||
            let date = this._parseDateValue(options.default);
 | 
			
		||||
            this._defaultDate = date;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._correctDateOptions();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _correctDateOptions()
 | 
			
		||||
    {
 | 
			
		||||
        if (this._minimalDate.valueOf() > this._maximalDate.valueOf()) {
 | 
			
		||||
            let temporaryDate = this._minimalDate;
 | 
			
		||||
            this._minimalDate = this._maximalDate;
 | 
			
		||||
            this._maximalDate = temporaryDate;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this._defaultDate === null) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this._defaultDate.valueOf() < this._minimalDate.valueOf()) {
 | 
			
		||||
            this._defaultDate = this._minimalDate;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this._defaultDate.valueOf() > this._maximalDate.valueOf()) {
 | 
			
		||||
            this._defaultDate = this._maximalDate;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _parseDateValue(date)
 | 
			
		||||
    {
 | 
			
		||||
        if (typeof(date) === 'object' && date !== null && date instanceof Date) {
 | 
			
		||||
            return date;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (typeof(date) !== 'string' || date.trim() === '') {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let parsed = this._dateYmdParser.exec(date);
 | 
			
		||||
        
 | 
			
		||||
        if (parsed !== null) {
 | 
			
		||||
            return new Date(Number(parsed[1]), Number(parsed[2]) - 1, Number(parsed[3]));
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        parsed = this._dateDmyParser.exec(date);
 | 
			
		||||
        
 | 
			
		||||
        if (parsed === null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return new Date(Number(parsed[3]), Number(parsed[2]) - 1, Number(parsed[1]));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
    _initField(field)
 | 
			
		||||
    {
 | 
			
		||||
        this._field = field;
 | 
			
		||||
        this._field.placeholder = this._formatter.getPlaceHolder();
 | 
			
		||||
        
 | 
			
		||||
        if (this._isEmptyValue(this._field.value)) {
 | 
			
		||||
            this._field.value = (this._defaultDate === null ? '' : this._formatter.formatDate(this._defaultDate));
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            this.correctValue(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _subscribeEvents()
 | 
			
		||||
    {
 | 
			
		||||
        let _this = this;
 | 
			
		||||
        
 | 
			
		||||
        let correct = function (event) {
 | 
			
		||||
            if (event === null || event.type !== 'keyup') {
 | 
			
		||||
                _this.correctValue(true);
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (_this._isServiceCode(event.keyCode)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (event.keyCode === 27) { // Escape
 | 
			
		||||
                _this.returnLastCorrectValue();
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            _this.correctValue(true);
 | 
			
		||||
        };
 | 
			
		||||
        
 | 
			
		||||
        this._field.addEventListener('change', correct);
 | 
			
		||||
        this._field.addEventListener('keyup', correct);
 | 
			
		||||
        
 | 
			
		||||
        this._field.addEventListener('blur', function () {
 | 
			
		||||
            _this.correctValue(true);
 | 
			
		||||
            _this.returnLastCorrectValue();
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _isServiceCode(code) {
 | 
			
		||||
        return code === 8 /* backspace */
 | 
			
		||||
            || code === 46 /* delete */
 | 
			
		||||
            || code === 16 /* shift */
 | 
			
		||||
            || code === 17 /* shift */
 | 
			
		||||
            || code === 18 /* shift */
 | 
			
		||||
            || code === 20 /* caps lock */
 | 
			
		||||
            || code === 35 /* end */
 | 
			
		||||
            || code === 36 /* home */
 | 
			
		||||
            || code === 37 /* left */
 | 
			
		||||
            || code === 39 /* right */
 | 
			
		||||
            || code === 46 /* delete */;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _isEmptyValue(value)
 | 
			
		||||
    {
 | 
			
		||||
        return value.trim() === '';
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    getDefaultValue()
 | 
			
		||||
    {
 | 
			
		||||
        return this._defaultDate === null ? '' : this._formatter.formatDate(this._defaultDate);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    correctValue(autocomplete)
 | 
			
		||||
    {
 | 
			
		||||
        this._formatter.parse(this._field.value, autocomplete);
 | 
			
		||||
        
 | 
			
		||||
        let parsedValue = this._getLimitedParsedValue();
 | 
			
		||||
        
 | 
			
		||||
        if (this._formatter.isComplete()) {
 | 
			
		||||
            this._lastCorrectValue = parsedValue;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (this._field.value === parsedValue) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._setValueToFieled(parsedValue);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    setValue(value)
 | 
			
		||||
    {
 | 
			
		||||
        let date = this._parseDateValue(value);
 | 
			
		||||
        
 | 
			
		||||
        if (date === null || date.valueOf() < this._minimalDate.valueOf() || date.valueOf() > this._maximalDate.valueOf()) {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        this._lastCorrectValue = this._formatter.formatDate(date);
 | 
			
		||||
        this._field.value = this._lastCorrectValue;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _getLimitedParsedValue()
 | 
			
		||||
    {
 | 
			
		||||
        if (!this._formatter.isComplete()) {
 | 
			
		||||
            return this._formatter.getCorrectedValue();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        let date = this._formatter.getParsedDate();
 | 
			
		||||
            
 | 
			
		||||
        if (date.valueOf() < this._minimalDate.valueOf()) {
 | 
			
		||||
            return this._formatter.formatDate(this._minimalDate);
 | 
			
		||||
        }
 | 
			
		||||
        else if (date.valueOf() > this._maximalDate.valueOf()) {
 | 
			
		||||
            return this._formatter.formatDate(this._maximalDate);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        return this._formatter.getCorrectedValue();
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    _setValueToFieled(newValue)
 | 
			
		||||
    {
 | 
			
		||||
        let position = this._field.selectionStart;
 | 
			
		||||
        let isAtEnd = (position === this._field.value.length);
 | 
			
		||||
        
 | 
			
		||||
        this._field.value = newValue;
 | 
			
		||||
        
 | 
			
		||||
        if (!isAtEnd && position < this._field.value.length) {
 | 
			
		||||
            this._field.selectionStart = position;
 | 
			
		||||
            this._field.selectionEnd = position;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    returnLastCorrectValue()
 | 
			
		||||
    {
 | 
			
		||||
        if (this._lastCorrectValue !== null) {
 | 
			
		||||
            this._field.value = this._lastCorrectValue;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartDateField.initBy = function (id) {
 | 
			
		||||
    return SmartDateField.initFor(document.getElementById(id));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SmartDateField.initFor = function (field, options) {
 | 
			
		||||
    if (typeof(field) !== 'object'
 | 
			
		||||
            || field === null
 | 
			
		||||
            || field.tagName.toLowerCase() !== 'input'
 | 
			
		||||
            || field.type.toLowerCase() !== 'text') {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (typeof(field._smartDate) === 'object' && field._smartDate !== null && field._smartDate instanceof SmartDateField) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    field._smartDate = new SmartDateField(field, options);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
jQuery.fn.smartdate = function (options) {
 | 
			
		||||
    let collection = [];
 | 
			
		||||
    let field = null;
 | 
			
		||||
 | 
			
		||||
    for (let i = 0; i < this.length; i++) {
 | 
			
		||||
        field = SmartDateField.initFor(this[i], options);
 | 
			
		||||
        
 | 
			
		||||
        if (field !== null) {
 | 
			
		||||
            collection.push(field);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return jQuery(this);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
jQuery.fn.setDateValue = function (value) {
 | 
			
		||||
    for (let i = 0; i < this.length; i++) {
 | 
			
		||||
        if (typeof(this[i]._smartDate) === 'object' && this[i]._smartDate !== null && this[i]._smartDate instanceof SmartDateField) {
 | 
			
		||||
            this[i]._smartDate.setValue(value);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return jQuery(this);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										1
									
								
								es6/smart-date.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								es6/smart-date.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
		Reference in New Issue
	
	Block a user