PageSourceSearch

https://www.hwg-lu.de/_assets/5671348b12af99bd02d50cb604ea8fc0/JavaScript/academic-study-plan.js?1789461350

js hwg-lu.de collected 2026-10-02 16:47:26 UTC 6,922 bytes, 238 lines download raw bytes

1(function () {
2  const MOBILE_BREAKPOINT = 768;
3
4  class StudyPlan {
5    constructor(container) {
6      this.container = container;
7      this.modules = this.container.querySelectorAll('.module');
8      this.headers = this.container.querySelectorAll('.header');
9
10      this.init();
11    }
12
13    init() {
14      this.buildCategoryFilter();
15      this.initCategoryFilter();
16      this.initModuleClicks();
17      this.initModal();
18      this.handleResize();
19      this.initHeaderClicks();
20    }
21
22    buildCategoryFilter() {
23      const filterList = this.container.querySelector('.filter');
24      const filterTemplate = this.container.querySelector('.filter li')?.outerHTML;
25
26      if (!filterList || !filterTemplate) return;
27
28      filterList.innerHTML = '';
29
30      const categoriesMap = new Map();
31
32      this.container.querySelectorAll('.module[data-categories]').forEach(module => {
33        const categoriesData = module.dataset.categories;
34        if (!categoriesData || categoriesData === '[]') return;
35
36        try {
37          const categories = JSON.parse(categoriesData);
38          categories.forEach(cat => {
39            if (cat.uid && !categoriesMap.has(cat.uid)) {
40              categoriesMap.set(cat.uid, {
41                uid: cat.uid,
42                label: cat.label,
43                colour: cat.colour
44              });
45            }
46          });
47        } catch (e) { }
48      });
49
50      if (categoriesMap.size === 0) {
51        return;
52      }
53
54      Array.from(categoriesMap.values()).forEach(category => {
55        let template = filterTemplate;
56
57        template = template.replace(/category-id-placeholder/g, `${category.uid}`);
58        template = template.replace(/category-color-placeholder/g, `${category.colour}`);
59        template = template.replace(/category-label-placeholder/g, `${category.label}`);
60
61        let temp = document.createElement('li');
62        temp.innerHTML = template;
63
64        filterList.appendChild(temp.firstElementChild);
65      });
66    }
67
68    getModuleCategoryIds(module) {
69      const categoriesData = module.dataset.categories;
70      if (!categoriesData || categoriesData === '[]') return [];
71
72      try {
73        const categories = JSON.parse(categoriesData);
74        return categories.map(cat => String(cat.uid));
75      } catch (e) {
76        return [];
77      }
78    }
79
80    initCategoryFilter() {
81      this.container.querySelectorAll('.filter button').forEach(button => {
82        button.addEventListener('click', (e) => {
83          if (button.classList.contains('highlighted')) {
84            this.clearHighlights();
85          } else {
86            this.highlightCategory(button.dataset.categoryId, button.dataset.categoryColor);
87            button.classList.add('highlighted');
88          }
89        });
90      });
91    }
92
93    highlightCategory(categoryId, color) {
94      this.clearHighlights();
95      this.container.classList.add('highlighted');
96
97      this.modules.forEach(module => {
98        const categoryIds = this.getModuleCategoryIds(module);
99
100        if (categoryIds.includes(categoryId)) {
101          module.classList.add('highlighted');
102          module.closest('.col').classList.add('highlighted', 'open');
103          this.container.style.setProperty('--highlight-color', this.hexToRgba(color, 0.25));
104        }
105      });
106    }
107
108    clearHighlights() {
109      this.container.querySelectorAll('.highlighted').forEach(highlighted => {
110        highlighted.classList.remove('highlighted', 'open');
111      });
112      this.container.classList.remove('highlighted');
113    }
114
115    initHeaderClicks() {
116      this.headers.forEach(header => {
117        header.addEventListener('click', () => {
118          if (window.innerWidth <= MOBILE_BREAKPOINT) {
119            header.closest('.col').classList.toggle('open');
120            header.setAttribute('aria-expanded', header.closest('.col').classList.contains('open'));
121          }
122        });
123      });
124    }
125
126    enableMobile() {
127      this.headers.forEach(header => {
128        header.style.height = '';
129        header.setAttribute('aria-expanded', 'false');
130        header.setAttribute('tabindex', '0');
131        header.setAttribute('role', 'button');
132        header.setAttribute('aria-hidden', 'false');
133        header.removeAttribute('inert');
134      });
135
136      this.modules.forEach(module => {
137        module.style.height = '';
138      });
139    }
140
141    disableMobile() {
142      let headerHeight = 0;
143      let moduleHeight = 0;
144
145      this.headers.forEach(header => {
146        headerHeight = Math.max(headerHeight, header.offsetHeight);
147      });
148
149      this.headers.forEach(header => {
150        header.style.height = `${headerHeight}px`;
151        header.setAttribute('tabindex', '-1');
152        header.setAttribute('role', '');
153        header.setAttribute('aria-hidden', 'true');
154        header.setAttribute('inert', '');
155      });
156
157      this.modules.forEach(module => {
158        moduleHeight = Math.max(moduleHeight, module.offsetHeight);
159      });
160
161      this.modules.forEach(module => {
162        module.style.height = `${moduleHeight}px`;
163      });
164    }
165
166    initModuleClicks() {
167      this.container.querySelectorAll('.modal-trigger').forEach(module => {
168        module.addEventListener('click', () => document.getElementById(module.dataset.dialogId)?.showModal());
169      });
170    }
171
172    initModal() {
173      document.querySelectorAll('.module dialog').forEach(dialog => {
174        const button = dialog.querySelector('button');
175
176        button.addEventListener('click', (e) => {
177          this.closeModal(dialog)
178          e.stopPropagation();
179        });
180      });
181    }
182
183    closeModal(dialog) {
184      const audioElements = dialog.querySelectorAll('audio');
185
186      audioElements.forEach(audio => {
187        audio.pause();
188        audio.currentTime = 0;
189      });
190
191      dialog.close();
192    }
193
194    hexToRgba(hex, alpha) {
195      if (!hex || hex.length < 7) return `rgba(0, 0, 0, ${alpha})`;
196      const r = parseInt(hex.slice(1, 3), 16);
197      const g = parseInt(hex.slice(3, 5), 16);
198      const b = parseInt(hex.slice(5, 7), 16);
199      return `rgba(${r}, ${g}, ${b}, ${alpha})`;
200    }
201
202    handleResize() {
203      if (window.innerWidth > MOBILE_BREAKPOINT) {
204        this.disableMobile();
205      } else {
206        this.enableMobile();
207      }
208    }
209  }
210
211  const studyPlanInstances = new Map();
212
213  function init() {
214    document.querySelectorAll('.academic-study-plan').forEach(container => {
215      const id = container.dataset.studyPlan;
216
217      if (!studyPlanInstances.has(id)) {
218        studyPlanInstances.set(id, new StudyPlan(container));
219      }
220    });
221  }
222
223  function handleResize() {
224    studyPlanInstances.forEach(instance => instance.handleResize());
225  }
226
227  if (document.readyState === 'loading') {
228    document.addEventListener('DOMContentLoaded', init);
229  } else {
230    init();
231  }
232
233  let resizeTimeout;
234  window.addEventListener('resize', () => {
235    clearTimeout(resizeTimeout);
236    resizeTimeout = setTimeout(handleResize, 150);
237  });
238})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.