Array.prototype.forEach.call(document.body.querySelectorAll("*[data-mask]"), applyDataMask);
function applyDataMask(field) {
var mask = field.dataset.mask.split('');
// For now, this just strips everything that's not a number
function stripMask(maskedData) {
function isDigit(char) {
return /\d/.test(char);
}
return maskedData.split('').filter(isDigit);
}
// Replace `_` characters with characters from `data`
function applyMask(data) {
return mask.map(function(char) {
if (char != '_') return char;
if (data.length == 0) return char;
return data.shift();
}).join('')
}
function reapplyMask(data) {
return applyMask(stripMask(data));
}
function changed() {
var oldStart = field.selectionStart;
var oldEnd = field.selectionEnd;
field.value = reapplyMask(field.value);
field.selectionStart = oldStart;
field.selectionEnd = oldEnd;
}
field.addEventListener('click', changed)
field.addEventListener('keyup', changed)
}
Array.prototype.forEach.call(document.body.querySelectorAll("*[data-mask]"), applyDataMask);
function applyDataMask(field) {
var mask = field.dataset.mask.split('');
// For now, this just strips everything that's not a number
function stripMask(maskedData) {
function isDigit(char) {
return /\d/.test(char);
}
return maskedData.split('').filter(isDigit);
}
// Replace `_` characters with characters from `data`
function applyMask(data) {
return mask.map(function(char) {
if (char != '_') return char;
if (data.length == 0) return char;
return data.shift();
}).join('')
}
function reapplyMask(data) {
return applyMask(stripMask(data));
}
function changed() {
var oldStart = field.selectionStart;
var oldEnd = field.selectionEnd;
field.value = reapplyMask(field.value);
field.selectionStart = oldStart;
field.selectionEnd = oldEnd;
}
field.addEventListener('click', changed)
field.addEventListener('keyup', changed)
}
const patternFreeChar = "_";
const dayValidator = [/^[0-3]$/, /^0[1-9]|[12]\d|3[01]$/];
const monthValidator = [/^[01]$/, /^0[1-9]|1[012]$/];
const yearValidator = [/^[12]$/, /^19|20$/, /^(19|20)\d$/, /^(19|20)\d\d$/];
/**
* Validate a date as your type.
* @param {string} date The date in the provided format as a string representation.
* @param {string} format The format to use.
* @throws {Error} When the date is invalid.
*/
function validateStartTypingDate(date, format='DDMMYYYY') {
if ( !date ) return "";
date = date.substr(0, 8);
if ( !/^\d+$/.test(date) )
throw new Error("Please type numbers only");
const formatAsArray = format.split('');
const dayIndex = formatAsArray.findIndex(c => c == 'D');
const monthIndex = formatAsArray.findIndex(c => c == 'M');
const yearIndex = formatAsArray.findIndex(c => c == 'Y');
const dayStr = date.substr(dayIndex,2);
const monthStr = date.substr(monthIndex,2);
const yearStr = date.substr(yearIndex,4);
if ( dayStr && !dayValidator[dayStr.length-1].test(dayStr) ) {
switch (dayStr.length) {
case 1:
throw new Error("Day in month can start only with 0, 1, 2 or 3");
case 2:
throw new Error("Day in month must be in a range between 01 and 31");
}
}
if ( monthStr && !monthValidator[monthStr.length-1].test(monthStr) ) {
switch (monthStr.length) {
case 1:
throw new Error("Month can start only with 0 or 1");
case 2:
throw new Error("Month number must be in a range between 01 and 12");
}
}
if ( yearStr && !yearValidator[yearStr.length-1].test(yearStr) ) {
switch (yearStr.length) {
case 1:
throw new Error("We support only years between 1900 and 2099, so the full year can start only with 1 or 2");
default:
throw new Error("We support only years between 1900 and 2099, so the full year can start only with 19 or 20");
}
}
const day = parseInt(dayStr);
const month = parseInt(monthStr);
const year = parseInt(yearStr);
const monthName = new Date(0,month-1).toLocaleString('en-us',{month:'long'});
if ( day > 30 && [4,6,9,11].includes(month) )
throw new Error(`${monthName} have maximum 30 days`);
if ( day > 29 && month === 2 )
throw new Error(`${monthName} have maximum 29 days`);
if ( date.length === 8 ) {
if ( !isLeap(year) && month === 2 && day === 29 )
throw new Error(`The year you are trying to enter (${year}) is not a leap year. Thus, in this year, ${monthName} can have maximum 28 days`);
}
return date;
}
/**
* Check whether the given year is a leap year.
*/
function isLeap(year) {
return new Date(year, 1, 29).getDate() === 29;
}
/**
* Move cursor to the end of the provided input element.
*/
function moveCursorToEnd(el) {
if (typeof el.selectionStart == "number") {
el.selectionStart = el.selectionEnd = el.value.length;
} else if (typeof el.createTextRange != "undefined") {
el.focus();
var range = el.createTextRange();
range.collapse(false);
range.select();
}
}
/**
* Move cursor to the end of the self input element.
*/
function selfMoveCursorToEnd() {
return moveCursorToEnd(this);
}
const inputs = document.querySelectorAll("input");
inputs.forEach(input => {
const { format, pattern } = input.dataset;
input.addEventListener("keydown", function(event){
event.preventDefault();
document.getElementById("date-error-msg").innerText = "";
// On digit pressed
let inputMemory = this.dataset.inputMemory || "";
if ( event.key.length === 1 ) {
try {
inputMemory = validateStartTypingDate(inputMemory + event.key, format);
} catch (err) {
document.getElementById("date-error-msg").innerText = err.message;
}
}
// On backspace pressed
if ( event.code === "Backspace" ) {
inputMemory = inputMemory.slice(0, -1);
}
// Build an output using a pattern
if ( this.dataset.inputMemory !== inputMemory ) {
let output = pattern;
for ( let i=0, digit; i<inputMemory.length, digit=inputMemory[i]; i++ ) {
output = output.replace(patternFreeChar, digit);
}
this.dataset.inputMemory = inputMemory;
this.value = output;
}
// Clean the value if the memory is empty
if ( inputMemory === "" ) {
this.value = "";
}
}, false);
input.addEventListener('select', selfMoveCursorToEnd, false);
input.addEventListener('mousedown', selfMoveCursorToEnd, false);
input.addEventListener('mouseup', selfMoveCursorToEnd, false);
input.addEventListener('click', selfMoveCursorToEnd, false);
});