Skip to content

Instantly share code, notes, and snippets.

@JordanReiter
Last active January 25, 2023 08:55
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save JordanReiter/2367756a5f93c25d5b8412ed5eb11eed to your computer and use it in GitHub Desktop.
Save JordanReiter/2367756a5f93c25d5b8412ed5eb11eed to your computer and use it in GitHub Desktop.
Polyfill for input type="month" with no dependencies.
(function() {
var monthInputs = document.querySelectorAll('input[type="month"]'),
checkDateInput = document.createElement('input'),
dateSupported = false,
months = [],
lang = document.documentElement.lang || navigator.language,
DEFAULT_SPAN = 5;
if (monthInputs[0].type === 'month') {
// browser supports month input; no need for polyfill
return true;
}
checkDateInput.setAttribute('type', 'date')
dateSupported = 'date' === checkDateInput.type;
function makeRangeArray(from, to) {
var result = [], idx;
if (!to) {
to=from;
from=0;
}
for (idx=from; idx < to; idx += 1) {
result.push(idx);
}
return result;
}
if (!dateSupported) {
// initialize months
makeRangeArray(12).forEach( // generate size 12 array and loop over
function (_, idx) {
var dt = new Date(1900, idx, 15);
months[idx] = dt.toLocaleString(
lang,
{'month': "long"}
)
}
);
}
function createMonthDropdown(selected) {
var monthDropdown = document.createElement('select');
selected = selected || new Date().getUTCMonth();
selected = selected.getUTCMonth ? selected.getUTCMonth() : selected;
months.forEach(function (monthName, monthIdx) {
var month = monthIdx + 1,
opt = document.createElement('option');
opt.value = month;
opt.text = monthName;
if (monthIdx === selected) {
opt.setAttribute('selected', 'selected');
}
monthDropdown.appendChild(opt);
});
return monthDropdown;
}
function createYearDropdown(selected, dropdownYears) {
var yearDropdown = document.createElement('select');
selected = selected || new Date().getUTCFullYear();
selected = selected.getUTCFullYear ? selected.getUTCFullYear() : selected;
dropdownYears.forEach(function (year) {
var opt = document.createElement('option');
opt.value = year;
opt.text = year;
if (year === selected) {
opt.setAttribute('selected', 'selected');
}
yearDropdown.appendChild(opt);
});
return yearDropdown;
}
function getYears(startYear, endYear, yearSpan) {
var years;
yearSpan = yearSpan || DEFAULT_SPAN // by default, get 10 years before & after
if (!startYear && !endYear) {
startYear = new Date().getUTCFullYear() - yearSpan;
endYear = new Date().getUTCFullYear() + yearSpan;
} else {
startYear = startYear || endYear - (yearSpan * 2);
endYear = endYear || startYear + (yearSpan * 2);
}
if (startYear > endYear) {
throw new Error("Invalid inputs; end year must be less than start year");
}
years = makeRangeArray(startYear, endYear + 1);
return years;
}
function getLastDayOfMonth(dt) {
dt = dt instanceof Date ? dt : new Date(Date.parse(dt));
dt.setUTCMonth(dt.getUTCMonth() + 1)
dt = new Date(dt - 1);
return dt.getUTCDate();
}
var YEAR_MONTH_PATT = /^\d\d\d\d-\d\d$/;
monthInputs.forEach(function (monthInput) {
var initial = monthInput.value,
yearEl, monthEl, dateEl,
yearSpan = parseInt(monthInput.dataset.yearSpan),
yearDropdown = monthInput.dataset.yearDropdown,
minMonth = monthInput.min,
maxMonth = monthInput.max,
startYear, endYear, years;
if (minMonth) {
startYear = parseInt(minMonth.split('-')[0], 10);
}
if (maxMonth) {
endYear = parseInt(maxMonth.split('-')[0], 10);
}
if (typeof yearDropdown !== 'undefined') {
yearDropdown = !(
yearDropdown === false ||
yearDropdown.toLowerCase() === 'false'
)
}
if (initial && YEAR_MONTH_PATT.test(initial)) {
initial += '-01';
}
initial = initial && new Date(Date.parse(initial));
initial = (initial && initial.getTime && !isNaN(initial.getTime())) ? initial : new Date();
monthInput.dataset.year = initial.getUTCFullYear();
monthInput.dataset.month = initial.getUTCMonth() + 1;
years = getYears(startYear, endYear, yearSpan);
if (dateSupported) {
dateEl = document.createElement('input');
dateEl.setAttribute('type', 'date');
dateEl.monthInputTarget = monthInput;
dateEl.monthInputType = 'date';
if (monthInput.min) {
if (monthInput.min.split('-').length >= 2) {
dateEl.setAttribute(
'min',
monthInput.min + '-01'
);
} else {
dateEl.setAttribute('min', monthInput.min + '-01-01');
}
}
if (monthInput.max) {
if (monthInput.max.split('-').length >= 2) {
dateEl.setAttribute(
'max',
monthInput.max + '-' + getLastDayOfMonth(monthInput.max)
);
} else {
dateEl.setAttribute('max', monthInput.max + '-12-31');
}
}
if (monthInput.value) {
dateEl.value = (
monthInput.value + '-01'
);
}
monthInput.parentNode.insertBefore(dateEl, monthInput);
} else {
if (yearDropdown === false) {
yearEl = document.createElement('input');
yearEl.setAttribute('type', 'number');
if (startYear) {
yearEl.setAttribute('min', startYear);
}
if (endYear) {
yearEl.setAttribute('max', endYear);
}
yearEl.setAttribute('step', 1);
if (initial) {
yearEl.value = initial.getUTCFullYear()
}
} else {
yearEl = createYearDropdown(initial, years);
}
yearEl.monthInputTarget = monthInput;
yearEl.monthInputType = 'year';
monthEl = createMonthDropdown(initial);
monthEl.monthInputTarget = monthInput;
monthEl.monthInputType = 'month';
monthInput.parentNode.insertBefore(monthEl, monthInput);
monthInput.parentNode.insertBefore(
document.createTextNode(' '),
monthInput
);
monthInput.parentNode.insertBefore(yearEl, monthInput);
}
// monthInput.style.display = 'none';
});
function updateValue(input) {
var date, newValue, monthDisplay;
if (input.dataset.date) {
date = new Date(Date.parse(input.dataset.date));
newValue = (
date.getUTCFullYear() +
'-' +
(date.getUTCMonth() + 1).toString().padStart(2, 0)
);
} else {
newValue = input.dataset.year + '-' + input.dataset.month.toString().padStart(2,'0');
}
if (input.max && newValue > input.max) {
monthDisplay = months[parseInt(input.max.split('-')[1])];
if (monthDisplay) {
alert(
"You cannot choose a date later than " +
monthDisplay + ", " +
input.max.split('-')[0]
)
} else {
alert(
"You cannot choose a year greater than " + input.max.split('-')[0]
)
}
return false;
} else if (input.min && newValue < input.min) {
alert("Value cannot be less than " + input.min);
return false;
}
input.value = newValue;
return true;
}
document.addEventListener('focus', function (event) {
var focused = event.target;
if (focused.monthInputTarget) {
focused.dataset.storedValue = focused.value;
if (typeof focused.selectedIndex !== 'undefined') {
focused.dataset.storedIndex = focused.selectedIndex;
}
}
}, true);
document.addEventListener('change', function (event) {
var changed = event.target;
if (changed.monthInputTarget && changed.monthInputType) {
changed.monthInputTarget.dataset[changed.monthInputType] = changed.value;
if (!updateValue(changed.monthInputTarget)) {
if (changed.dataset.storedIndex) {
changed.selectedIndex = changed.dataset.storedIndex;
} else if (changed.dataset.storedValue) {
changed.value = changed.dataset.storedValue;
}
changed.monthInputTarget.dataset[changed.monthInputType] = changed.dataset.storedValue;
changed.focus();
} else {
changed.dataset.storedValue = changed.value;
if (typeof changed.selectedIndex !== 'undefined') {
changed.dataset.storedIndex = changed.selectedIndex;
}
}
}
return true;
}, true);
}())
@garak
Copy link

garak commented Mar 23, 2018

Tried with Firefox 59, the result is: I'm getting an additional input date field, when I select a day from such input the input month is updated with matching month. Not exactly what I expected...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment