Widget:Event calendar
<script>
function attachDateSelectEvents() {
document.querySelectorAll('.has-event').forEach(td => {
td.addEventListener('click', () => {
const eventDate = td.getAttribute('data-eventdate');
document.querySelectorAll('.has-event.active').forEach(activeTd => {
activeTd.classList.remove('active');
});
const activeRow = document.querySelector('.calendar tr.active');
if (activeRow) {
activeRow.classList.remove('active');
}
const activeMonth = document.querySelector('.calendar tbody.active');
if (activeMonth) {
activeMonth.classList.remove('active');
}
const calendarLabel = document.querySelector('.calendar-label');
if (calendarLabel) {
calendarLabel.classList.remove('bg-primary');
}
// Add .active to the clicked one
td.classList.add('active');
// Call the Lua module via MediaWiki API
fetch(mw.util.wikiScript('api'), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'parse',
format: 'json',
text: `Lua error in mw.lua at line 143: field 'day' missing in date table.}`,
})
})
.then(response => response.json())
.then(data => {
if (data.parse ) {
if ( data.parse.text) {
document.querySelector('#calendar-results').innerHTML = data.parse.text['*'];
}
}
updateBg();
})
.catch(err => console.error('Error fetching events:', err));
});
});
}
function attachWeekSelectEvents() {
const monthBody = document.querySelector('table.calendar tbody');
const monthLabel = document.querySelector('.calendar-label');
document.querySelectorAll('.week-number').forEach(td => {
td.addEventListener('click', () => {
const weekStart = td.getAttribute('data-week-start');
const weekEnd = td.getAttribute('data-week-end');
monthBody.classList.remove('active');
monthLabel.classList.remove('bg-primary');
document.querySelectorAll('[data-week-start]').forEach(activeTd => {
activeTd.parentElement.classList.remove('active');
});
// Add .active to the clicked one
td.parentElement.classList.add('active');
// Call the Lua module via MediaWiki API
fetch(mw.util.wikiScript('api'), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'parse',
format: 'json',
text: `Lua error in mw.lua at line 143: field 'day' missing in date table.}`,
})
})
.then(response => response.json())
.then(data => {
if (data.parse ) {
if ( data.parse.text) {
document.querySelector('#calendar-results').innerHTML = data.parse.text['*'];
}
}
updateBg();
})
.catch(err => console.error('Error fetching events:', err));
});
});
}
function updateBg() {
const elements = document.querySelectorAll('[data-bg]');
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const backgroundImage = element.dataset.bg;
element.style.backgroundImage = `url("${backgroundImage}")`;
element.style.backgroundColor = 'transparent';
}
const items = document.querySelectorAll('.event-item');
if (items.length) buildEventFilters();
}
function monthSelectAuto(){
const fullMonthSelect = document.querySelector('.calendar-header .calendar-label'); if (!fullMonthSelect) { return; } fullMonthSelect.click();
}
function attachMonthChangeEvents() {
document.querySelectorAll('.change-month').forEach(td => {
td.addEventListener('click', () => {
const eventDate = td.getAttribute('data-eventdate');
/* ---- Call the Lua module via MediaWiki API ------------------ */
fetch(mw.util.wikiScript('api'), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'parse',
format: 'json',
text: `Lua error in mw.lua at line 143: field 'day' missing in date table.}`
})
})
.then(r => r.json())
.then(data => {
if (data.parse && data.parse.text) {
/* ---- Replace the calendar wrapper with new markup --------- */
const wrapper = document.querySelector('.calendar-wrapper').parentElement;
wrapper.parentElement.innerHTML = data.parse.text['*'];
attachMonthChangeEvents();
attachDateSelectEvents();
attachWeekSelectEvents();
// document.querySelector('.current-month.has-event')?.click();
const headerLabel = wrapper.querySelector('.calendar-header .calendar-label');
const monthChangers = wrapper.querySelectorAll('.change-month');
if (monthChangers.length && td.closest('.calendar-header')) {
headerLabel.removeEventListener('click', monthSelectAuto);
headerLabel.addEventListener('click', monthSelectAuto);
headerLabel.click();
}
}
})
.catch(err => console.error('Error reloading calendar:', err));
});
});
}
attachMonthChangeEvents(); attachDateSelectEvents(); attachWeekSelectEvents();
const heading = document.getElementById('firstHeading');
const selectedArea = localStorage.getItem('selectedEventArea');
if (selectedArea) {
if (selectedArea !== "All Events") {
heading.textContent += ' - ' + selectedArea;
}
}
document.addEventListener('click', function (e) {
const label = e.target.closest('.calendar-label');
if (!label) return;
const wrapper = label.closest('.calendar-wrapper');
if (!wrapper) return;
const calendar = wrapper.querySelector('table.calendar');
if (!calendar) return;
const tbody = calendar.querySelector('tbody');
if (!tbody) return;
const trow = calendar.querySelector('tr.active');
if (trow) {
trow.classList.remove('active');
}
tbody.classList.add('active');
label.classList.add('bg-primary');
const dateAfter = label.getAttribute('data-dateafter');
const dateBefore = label.getAttribute('data-datebefore');
// Call the Lua module via MediaWiki API
fetch(mw.util.wikiScript('api'), {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'parse',
format: 'json',
text: `Lua error in mw.lua at line 143: field 'day' missing in date table.}`,
})
})
.then(response => response.json())
.then(data => {
if (data.parse ) {
if ( data.parse.text) {
document.querySelector('#calendar-results').innerHTML = data.parse.text['*'];
}
}
updateBg();
});
});
// SETTINGS & HELPERS
const FILTER_STORAGE_KEY = 'myEventFilters';
function loadFilterState() {
const raw = localStorage.getItem(FILTER_STORAGE_KEY);
return raw ? JSON.parse(raw) : {};
}
function saveFilterState(state) {
localStorage.setItem(FILTER_STORAGE_KEY, JSON.stringify(state));
}
// BUILD FILTER UI
function buildEventFilters() {
const eventItems = document.querySelectorAll('.event-item');
/* 1️⃣ Build a temporary map of every value that appears in *any* event */ const allTypes = new Set(); const allCats = new Set();
eventItems.forEach(el => {
const t = el.dataset.eventType;
if (t) allTypes.add(t);
const cats = (el.dataset.eventCategory || )
.split(';').map(c=>c.trim()).filter(Boolean);
cats.forEach(c=>allCats.add(c));
});
/* 2️⃣ Build the markup – “All” is always first */
const container = document.querySelector('.filters-container')
|| createFiltersContainer();
const html =
`
Type of Event
Category
`;
container.innerHTML = html;
/* 3️⃣ Paint buttons according to stored state */
const stored = loadFilterState();
document.querySelectorAll('.filter-btn').forEach(btn=>{
const type = btn.dataset.filterType;
const val = btn.dataset.filterValue;
if (stored[type]?.[val]) btn.classList.add('active');
});
/* 4️⃣ Wire click handlers */
document.querySelectorAll('.filter-btn')
.forEach(b=>b.onclick = () => toggleFilter(b));
/* 5️⃣ Apply the filters right away */ applyAllFilters(eventItems);
}
// BUTTON HELPERS
function buildButton(filterType, filterValue, label) {
return `<button class="filter-btn"
data-filter-type="${filterType}"
data-filter-value="${filterValue}">
${label}
</button>`;
}
function createFiltersContainer() {
const cont = document.createElement('div');
cont.className = 'filters-container';
const calendar = document.querySelector('table.calendar');
if (calendar) {
calendar.insertAdjacentElement('afterend', cont);
} else {
document.body.appendChild(cont);
}
return cont;
}
// TOGGLE LOGIC
function toggleFilter(btn) {
const type = btn.dataset.filterType; const val = btn.dataset.filterValue;
const state = loadFilterState();
if (!state[type]) state[type] = {};
if (val === 'all') {
// remove any existing individual flags
Object.keys(state[type]).forEach(k=>delete state[type][k]);
state[type]['all'] = true; // special flag
/* Paint UI: All active, others inactive */
document.querySelectorAll(`[data-filter-type="${type}"]`)
.forEach(b=>{
b.classList.toggle('active', b.dataset.filterValue==='all');
});
} else {
/* ---------- Toggle a single value ----------
* 1) Remove “all” flag if it existed
*/
delete state[type]['all'];
/* 2) Flip the current value */
state[type][val] = !state[type][val];
/* 3) If all values are now true → collapse back to “All” */
const allValues = getAllValuesForType(type);
const allOn = allValues.every(v=>state[type][v]);
if (allOn) {
// set the special flag and paint UI
Object.keys(state[type]).forEach(k=>delete state[type][k]);
state[type]['all'] = true;
document.querySelectorAll(`[data-filter-type="${type}"]`)
.forEach(b=>{
b.classList.toggle('active', b.dataset.filterValue==='all');
});
} else {
// paint UI: this value active, All inactive
document.querySelectorAll(`[data-filter-type="${type}"]`)
.forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
}
}
/* Persist the new state */ saveFilterState(state);
/* Re‑apply all filters (intersection) */
const eventItems = document.querySelectorAll('.event-item');
applyAllFilters(eventItems);
}
/* Helper: return an array of *all* values for a given filter type
(used when toggling individual values). */
function getAllValuesForType(type) {
const allSet = new Set();
document.querySelectorAll(`[data-filter-type="${type}"]`)
.forEach(b=>{
if (b.dataset.filterValue !== 'all')
allSet.add(b.dataset.filterValue);
});
return [...allSet];
}
// APPLY ALL FILTERS
function applyAllFilters(allEvents) {
const state = loadFilterState();
/* Build the list of active values for each type
– if “all” flag is present, we treat it as *no filter* for that set.
*/
const activeTypes = Object.entries(state['event-type'] || {})
.filter(([k,v])=>v && k!=='all')
.map(([k])=>k);
const activeCats = Object.entries(state['event-category']||{})
.filter(([k,v])=>v && k!=='all')
.map(([k])=>k);
/* If no values are selected → treat as “All” */ const useAllTypes = activeTypes.length===0; const useAllCats = activeCats.length===0;
/* ----- Show / hide events ----- */
const visibleEvents = [];
allEvents.forEach(el=>{
let show = true;
// Event‑type filter (AND)
if (!useAllTypes) {
const t = el.dataset.eventType;
show &&= activeTypes.includes(t);
}
// Category filter – event is shown if *any* of its categories match
if (show && !useAllCats) {
const cats = (el.dataset.eventCategory||)
.split(';').map(c=>c.trim()).filter(Boolean);
show &&= cats.some(c=>activeCats.includes(c));
}
el.style.display = show ? : 'none';
if (show) visibleEvents.push(el);
});
/* ----- Keep only the values that are still present in the filtered set ----- */ pruneFilterState(visibleEvents);
/* ----- Re‑build the filter UI so that it contains *only* applicable options ----- */ rebuildFilterUI(visibleEvents);
}
// PRUNE STATE
function pruneFilterState(visibleEvents) {
const state = loadFilterState();
// Build a set of values that still exist const presentTypes = new Set(); const presentCats = new Set();
visibleEvents.forEach(el=>{
const t = el.dataset.eventType;
if (t) presentTypes.add(t);
const cats = (el.dataset.eventCategory||)
.split(';').map(c=>c.trim()).filter(Boolean);
cats.forEach(cat => presentCats.add(cat));
});
// Remove any stored value that no longer exists
['event-type','event-category'].forEach(type=>{
Object.keys(state[type] || {}).forEach(val=>{
const stillPresent = type==='event-type'
? presentTypes.has(val)
: presentCats.has(val);
if (!stillPresent) delete state[type][val];
});
});
// Persist the cleaned state saveFilterState(state);
}
// REBUILD FILTER UI
function rebuildFilterUI(visibleEvents) {
const container = document.querySelector('.filters-container');
/* Re‑collect all unique values that appear in the visible set */ const types = new Set(); const cats = new Set();
visibleEvents.forEach(el=>{
const t = el.dataset.eventType;
if (t) types.add(t);
const cs = (el.dataset.eventCategory||)
.split(';').map(c=>c.trim()).filter(Boolean);
cs.forEach(c=>cats.add(c));
});
/* Build new markup – “All” first */ const html =
`
Type of Event
Category
`;
container.innerHTML = html;
/* Paint according to stored state */
const stored = loadFilterState();
document.querySelectorAll('.filter-btn').forEach(btn=>{
const type = btn.dataset.filterType;
const val = btn.dataset.filterValue;
if (stored[type]?.[val]) btn.classList.add('active');
});
/* Wire click handlers again */
document.querySelectorAll('.filter-btn')
.forEach(b=>b.onclick = () => toggleFilter(b));
} const style = document.createElement('style');
style.textContent = `
.filters-container {
margin-bottom: 20px;
}
.filter-section {
margin-bottom: 15px;
}
.filter-options {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.filter-btn {
padding: 4px 12px;
background-color: #e9ecef;
border: 1px solid #ced4da;
border-radius: 2px;
cursor: pointer;
font-size: 14px;
transition: all 0.2s ease;
}
.filter-btn:hover {
background-color: #dee2e6;
}
.filter-btn.active {
background-color: #007bff;
color: white;
border-color: #007bff;
}
@media (max-width: 768px) {
.filter-options {
flex-direction: column;
}
.filter-btn {
width: 100%;
text-align: left;
}
}
`;
document.head.appendChild(style);
</script>