You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1001 lines
24 KiB
JavaScript

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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);
if (!_this._isEmptyValue(_this._field.value)) {
_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._setValueToField(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();
}
_setValueToField(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);
};