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

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);
};