Widget:Event calendar

From The Observatory
Jump to navigation Jump to search

<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
               ${buildButton('event-type','all','All')}
               ${[...allTypes].sort().map(t=>buildButton('event-type',t,t)).join()}
Category
               ${buildButton('event-category','all','All')}
               ${[...allCats].sort().map(c=>buildButton('event-category',c,c)).join()}

`;

   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
               ${buildButton('event-type','all','All')}
               ${[...types].sort().map(t=>buildButton('event-type',t,t)).join()}
Category
               ${buildButton('event-category','all','All')}
               ${[...cats].sort().map(c=>buildButton('event-category',c,c)).join()}

`;

   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>