260 lines
7.7 KiB
JavaScript
260 lines
7.7 KiB
JavaScript
/* global window, document, fetch, URLSearchParams */
|
|
|
|
(function () {
|
|
'use strict';
|
|
|
|
var Luxtools = window.Luxtools || (window.Luxtools = {});
|
|
|
|
function findCalendarRoot(target) {
|
|
var el = target;
|
|
while (el && el !== document) {
|
|
if (el.classList && el.classList.contains('luxtools-calendar') && el.getAttribute('data-luxtools-calendar') === '1') {
|
|
return el;
|
|
}
|
|
el = el.parentNode;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function getNextMonth(year, month, direction) {
|
|
var cursor = new Date(year, month - 1, 1);
|
|
cursor.setMonth(cursor.getMonth() + direction);
|
|
return {
|
|
year: cursor.getFullYear(),
|
|
month: cursor.getMonth() + 1
|
|
};
|
|
}
|
|
|
|
function parseCalendarFromHtml(html) {
|
|
if (!html) return null;
|
|
|
|
var wrapper = document.createElement('div');
|
|
wrapper.innerHTML = html;
|
|
|
|
return wrapper.querySelector('div.luxtools-calendar[data-luxtools-calendar="1"]');
|
|
}
|
|
|
|
function setCalendarBusy(calendar, busy) {
|
|
if (!calendar) return;
|
|
|
|
if (busy) {
|
|
calendar.setAttribute('data-luxtools-loading', '1');
|
|
} else {
|
|
calendar.removeAttribute('data-luxtools-loading');
|
|
}
|
|
|
|
var buttons = calendar.querySelectorAll('button.luxtools-calendar-nav-button, button.luxtools-calendar-today-button');
|
|
for (var i = 0; i < buttons.length; i++) {
|
|
buttons[i].disabled = !!busy;
|
|
}
|
|
}
|
|
|
|
function getCalendarStateKey(calendar) {
|
|
var baseNs = calendar.getAttribute('data-base-ns') || 'chronological';
|
|
return 'luxtools.calendar.month.' + baseNs;
|
|
}
|
|
|
|
function readSavedCalendarMonth(calendar) {
|
|
if (!window.localStorage) return null;
|
|
|
|
try {
|
|
var raw = window.localStorage.getItem(getCalendarStateKey(calendar));
|
|
if (!raw) return null;
|
|
|
|
var parsed = JSON.parse(raw);
|
|
var year = parseInt(parsed && parsed.year, 10);
|
|
var month = parseInt(parsed && parsed.month, 10);
|
|
if (!year || month < 1 || month > 12) return null;
|
|
|
|
return { year: year, month: month };
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function saveCalendarMonth(calendar) {
|
|
if (!window.localStorage) return;
|
|
|
|
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
|
|
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
|
|
if (!year || month < 1 || month > 12) return;
|
|
|
|
try {
|
|
window.localStorage.setItem(getCalendarStateKey(calendar), JSON.stringify({
|
|
year: year,
|
|
month: month
|
|
}));
|
|
} catch (e) {
|
|
// ignore storage failures
|
|
}
|
|
}
|
|
|
|
function clearSavedCalendarMonth(calendar) {
|
|
if (!window.localStorage) return;
|
|
|
|
try {
|
|
window.localStorage.removeItem(getCalendarStateKey(calendar));
|
|
} catch (e) {
|
|
// ignore storage failures
|
|
}
|
|
}
|
|
|
|
function fetchCalendarMonth(calendar, year, month) {
|
|
var ajaxUrl = calendar.getAttribute('data-luxtools-ajax-url') || '';
|
|
if (!ajaxUrl) return Promise.reject(new Error('Missing calendar ajax url'));
|
|
|
|
var baseNs = calendar.getAttribute('data-base-ns') || 'chronological';
|
|
var params = new URLSearchParams({
|
|
call: 'luxtools_calendar_month',
|
|
year: String(year),
|
|
month: String(month),
|
|
base: baseNs
|
|
});
|
|
|
|
var url = ajaxUrl + (ajaxUrl.indexOf('?') >= 0 ? '&' : '?') + params.toString();
|
|
|
|
return fetch(url, {
|
|
method: 'GET',
|
|
credentials: 'same-origin',
|
|
headers: {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
}
|
|
}).then(function (response) {
|
|
if (!response.ok) {
|
|
throw new Error('Calendar request failed: ' + response.status);
|
|
}
|
|
return response.text();
|
|
});
|
|
}
|
|
|
|
function pad2(num) {
|
|
return num < 10 ? '0' + num : String(num);
|
|
}
|
|
|
|
function syncCalendarToday(calendar) {
|
|
if (!calendar) return;
|
|
|
|
var todayCells = calendar.querySelectorAll('td.luxtools-calendar-day-today');
|
|
for (var i = 0; i < todayCells.length; i++) {
|
|
todayCells[i].classList.remove('luxtools-calendar-day-today');
|
|
}
|
|
|
|
var now = new Date();
|
|
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
|
|
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
|
|
if (!year || !month) return;
|
|
|
|
var currentYear = now.getFullYear();
|
|
var currentMonth = now.getMonth() + 1;
|
|
if (year !== currentYear || month !== currentMonth) return;
|
|
|
|
var dateIso = String(currentYear) + '-' + pad2(currentMonth) + '-' + pad2(now.getDate());
|
|
var todayCell = calendar.querySelector('td.luxtools-calendar-day[data-luxtools-date="' + dateIso + '"]');
|
|
if (todayCell) {
|
|
todayCell.classList.add('luxtools-calendar-day-today');
|
|
}
|
|
}
|
|
|
|
function replaceCalendar(calendar, replacement, persistState) {
|
|
if (persistState !== false) {
|
|
saveCalendarMonth(replacement);
|
|
}
|
|
syncCalendarToday(replacement);
|
|
calendar.replaceWith(replacement);
|
|
}
|
|
|
|
function loadCalendarMonth(calendar, targetYear, targetMonth, persistState) {
|
|
setCalendarBusy(calendar, true);
|
|
|
|
fetchCalendarMonth(calendar, targetYear, targetMonth)
|
|
.then(function (html) {
|
|
var replacement = parseCalendarFromHtml(html);
|
|
if (!replacement) {
|
|
throw new Error('Calendar markup missing in response');
|
|
}
|
|
|
|
replaceCalendar(calendar, replacement, persistState);
|
|
})
|
|
.catch(function () {
|
|
window.location.reload();
|
|
})
|
|
.finally(function () {
|
|
setCalendarBusy(calendar, false);
|
|
});
|
|
}
|
|
|
|
function navigateCalendarMonth(calendar, direction, persistState) {
|
|
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
|
|
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
|
|
if (!year || !month) return;
|
|
|
|
var next = getNextMonth(year, month, direction);
|
|
loadCalendarMonth(calendar, next.year, next.month, persistState);
|
|
}
|
|
|
|
function navigateCalendarToday(calendar) {
|
|
clearSavedCalendarMonth(calendar);
|
|
|
|
var now = new Date();
|
|
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
|
|
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
|
|
if (!year || !month) return;
|
|
|
|
var targetYear = now.getFullYear();
|
|
var targetMonth = now.getMonth() + 1;
|
|
var direction = ((targetYear - year) * 12) + (targetMonth - month);
|
|
if (!direction) {
|
|
syncCalendarToday(calendar);
|
|
return;
|
|
}
|
|
|
|
navigateCalendarMonth(calendar, direction, false);
|
|
}
|
|
|
|
function restoreCalendarMonth(calendar) {
|
|
var saved = readSavedCalendarMonth(calendar);
|
|
if (!saved) return;
|
|
|
|
var year = parseInt(calendar.getAttribute('data-current-year') || '', 10);
|
|
var month = parseInt(calendar.getAttribute('data-current-month') || '', 10);
|
|
if (saved.year === year && saved.month === month) return;
|
|
|
|
loadCalendarMonth(calendar, saved.year, saved.month, true);
|
|
}
|
|
|
|
function onCalendarClick(event) {
|
|
var target = event.target;
|
|
if (!target || !target.classList) return;
|
|
if (!target.classList.contains('luxtools-calendar-nav-button') && !target.classList.contains('luxtools-calendar-today-button')) return;
|
|
|
|
var calendar = findCalendarRoot(target);
|
|
if (!calendar) return;
|
|
|
|
event.preventDefault();
|
|
|
|
if (target.getAttribute('data-luxtools-today') === '1') {
|
|
navigateCalendarToday(calendar);
|
|
return;
|
|
}
|
|
|
|
var direction = parseInt(target.getAttribute('data-luxtools-dir') || '0', 10);
|
|
if (!direction) return;
|
|
|
|
navigateCalendarMonth(calendar, direction, true);
|
|
}
|
|
|
|
function initCalendarWidgets() {
|
|
var calendars = document.querySelectorAll('div.luxtools-calendar[data-luxtools-calendar="1"]');
|
|
for (var i = 0; i < calendars.length; i++) {
|
|
syncCalendarToday(calendars[i]);
|
|
restoreCalendarMonth(calendars[i]);
|
|
}
|
|
|
|
document.addEventListener('click', onCalendarClick, false);
|
|
}
|
|
|
|
Luxtools.CalendarWidget = {
|
|
init: initCalendarWidgets
|
|
};
|
|
})();
|