141 lines
5.4 KiB
JavaScript
141 lines
5.4 KiB
JavaScript
// Shared date-range handling for Browse Events and both maps — remembered
|
|
// across pages the way location.js remembers the place, validated in one
|
|
// spot, and bound to date pickers shown in the page language (format and
|
|
// calendar names from i18n.js / the flatpickr locale). Pages still
|
|
// own what a range *does* (a server-side filter on Browse, a client-side
|
|
// one on the maps); this only owns the range itself.
|
|
(function (window) {
|
|
var KEY = 'vindn:dateRange';
|
|
var DAY_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
|
|
// "Y-m-d" for today in Germany — the site's audience and data are
|
|
// German-local, so "today" must not flip at UTC midnight. sv-SE happens
|
|
// to format dates as ISO, which is the only reason it's used here.
|
|
function today() {
|
|
return new Date().toLocaleDateString('sv-SE', { timeZone: 'Europe/Berlin' });
|
|
}
|
|
|
|
// Valid "Y-m-d" strings or null for each end, and reversed ends swapped.
|
|
function normalize(from, to) {
|
|
from = DAY_RE.test(from || '') ? from : null;
|
|
to = DAY_RE.test(to || '') ? to : null;
|
|
if (from && to && from > to) {
|
|
var swap = from;
|
|
from = to;
|
|
to = swap;
|
|
}
|
|
return { from: from, to: to };
|
|
}
|
|
|
|
// The remembered range, or null. A range that already ended is dropped
|
|
// (nothing it could match is shown anymore), and a start in the past is
|
|
// moved up to today so the pickers never show a date they can't select.
|
|
function load() {
|
|
try {
|
|
var raw = window.localStorage && localStorage.getItem(KEY);
|
|
if (!raw) return null;
|
|
var parsed = JSON.parse(raw);
|
|
var range = normalize(parsed.from, parsed.to);
|
|
if (!range.from && !range.to) return null;
|
|
var now = today();
|
|
if (range.to && range.to < now) {
|
|
clear();
|
|
return null;
|
|
}
|
|
if (range.from && range.from < now) range.from = now;
|
|
return range;
|
|
} catch (e) { /* localStorage disabled/unavailable, or corrupt value — just skip it */ }
|
|
return null;
|
|
}
|
|
|
|
function save(from, to) {
|
|
var range = normalize(from, to);
|
|
if (!range.from && !range.to) {
|
|
clear();
|
|
return;
|
|
}
|
|
try {
|
|
if (window.localStorage) localStorage.setItem(KEY, JSON.stringify(range));
|
|
} catch (e) { /* localStorage disabled/unavailable — nothing to persist to */ }
|
|
}
|
|
|
|
function clear() {
|
|
try {
|
|
if (window.localStorage) localStorage.removeItem(KEY);
|
|
} catch (e) { /* localStorage disabled/unavailable */ }
|
|
}
|
|
|
|
// Turns a pair of <input type="date"> into pickers in the page language
|
|
// when flatpickr is loaded. A native date input displays in the *browser's*
|
|
// locale and can't be forced to the site's format, so the visible field is
|
|
// flatpickr's alt input (e.g. d.m.Y with a German calendar) while the
|
|
// original input keeps holding the machine-readable "Y-m-d" value
|
|
// everything else reads.
|
|
// Without flatpickr (e.g. its CDN is unreachable) the native inputs keep
|
|
// working as they are.
|
|
//
|
|
// onChange(from, to) fires when the *user* edits either field — never for
|
|
// updates made through the returned setter, so applying a remembered
|
|
// range doesn't trigger the page's own change handling.
|
|
function bindInputs(fromEl, toEl, onChange) {
|
|
var pickers = [];
|
|
if (window.flatpickr) {
|
|
var options = {
|
|
// flatpickr ships a file per language (loaded by
|
|
// _datepicker_scripts.html.twig); English is built in.
|
|
locale: (window.flatpickr.l10ns && window.flatpickr.l10ns[window.VindnI18n.locale]) ? window.VindnI18n.locale : 'default',
|
|
dateFormat: 'Y-m-d',
|
|
altInput: true,
|
|
altFormat: window.VindnI18n.dateFormat,
|
|
allowInput: true,
|
|
disableMobile: true, // the native mobile picker would bring the device locale back
|
|
static: true, // rendered inside the card, so it also shows in a fullscreen map
|
|
minDate: 'today',
|
|
// The popup renders inside its card/dialog; in a scrolling container
|
|
// (the Browse filter popup) it can open partly out of view.
|
|
onOpen: function (selected, str, instance) {
|
|
setTimeout(function () {
|
|
instance.calendarContainer.scrollIntoView({ block: 'nearest' });
|
|
}, 0);
|
|
}
|
|
};
|
|
pickers = [fromEl, toEl].map(function (el) {
|
|
var picker = window.flatpickr(el, options);
|
|
// The original input turns into a hidden one, so the visible alt
|
|
// input has to take over its id and label — otherwise the <label for>
|
|
// points at something that can't be focused or clicked.
|
|
var alt = picker.altInput;
|
|
var label = el.id && document.querySelector('label[for="' + el.id + '"]');
|
|
alt.id = el.id + 'Display';
|
|
if (label) label.setAttribute('for', alt.id);
|
|
alt.setAttribute('aria-label', el.getAttribute('aria-label') || '');
|
|
alt.setAttribute('placeholder', window.VindnI18n.t('dates.placeholder'));
|
|
return picker;
|
|
});
|
|
}
|
|
|
|
function handle() { onChange(fromEl.value, toEl.value); }
|
|
fromEl.addEventListener('change', handle);
|
|
toEl.addEventListener('change', handle);
|
|
|
|
return function set(from, to) {
|
|
if (pickers.length) {
|
|
pickers[0].setDate(from || null, false);
|
|
pickers[1].setDate(to || null, false);
|
|
} else {
|
|
fromEl.value = from || '';
|
|
toEl.value = to || '';
|
|
}
|
|
};
|
|
}
|
|
|
|
window.VindnDates = {
|
|
today: today,
|
|
normalize: normalize,
|
|
load: load,
|
|
save: save,
|
|
clear: clear,
|
|
bindInputs: bindInputs
|
|
};
|
|
})(window);
|