1document.addEventListener('DOMContentLoaded', () => { 2 const root = document.querySelector('[data-component="cpm-menu3"]'); 3 if (!root || !window.CPM_MENU3) return; 4 5 const itemsArr = CPM_MENU3.items || []; 6 const childrenMap = CPM_MENU3.children || {}; 7 const PLUS = CPM_MENU3.plusSvg || ''; 8 9 const byId = new Map(itemsArr.map(it => [String(it.id), it])); 10 11 const col1 = root.querySelector('.cpm-menu3__list[data-level="1"]'); 12 const col2 = root.querySelector('.cpm-menu3__list[data-level="2"]'); 13 const col3 = root.querySelector('.cpm-menu3__list[data-level="3"]'); 14 15 const clear = (ul) => { if (ul) ul.innerHTML = ''; }; 16 17 const clean = (s) => (s || '').replace(/\s+/g, ' ').trim(); 18 const mergeClasses = (base, extra) => clean(`${clean(base)} ${clean(extra)}`); 19 20 const ensureRelForBlank = (target, rel) => { 21 if (target !== '_blank') return rel || ''; 22 const parts = new Set(clean(rel).split(' ').filter(Boolean)); 23 parts.add('noopener'); 24 parts.add('noreferrer'); 25 return Array.from(parts).join(' '); 26 }; 27 28 const filterWpClasses = (className) => { 29 const deny = [ 30 /^menu-item/, /^current-/, /^page-item/, /^wpml-/, 31 /^mega-/, /^elementor-/, /^nav-/, /^sub-menu/, 32 ]; 33 return clean(className) 34 .split(' ') 35 .filter(c => c && !deny.some(rx => rx.test(c))) 36 .join(' '); 37 }; 38 39 const hydrateFromWpMenu = (scopeEl) => { 40 (scopeEl || root).querySelectorAll('.cpm-menu3__item[data-id]').forEach((li) => { 41 const id = li.dataset.id; 42 if (!id) return; 43 44 const wpLi = document.querySelector(`#menu-item-${CSS.escape(id)}`); 45 if (!wpLi) return; 46 47 const wpLiClasses = filterWpClasses(wpLi.className || ''); 48 if (wpLiClasses) li.className = mergeClasses(li.className, wpLiClasses); 49 50 const wpA = wpLi.querySelector(':scope > a'); 51 const target = wpA?.getAttribute('target') || ''; 52 const rel = wpA?.getAttribute('rel') || ''; 53 54 const a = li.querySelector(':scope > a.cpm-menu3__link'); 55 if (a) { 56 if (target) a.setAttribute('target', target); 57 const relFixed = ensureRelForBlank(target, rel); 58 if (relFixed) a.setAttribute('rel', relFixed); 59 } 60 }); 61 }; 62 63 const dimSiblings = (ul, activeLi) => { 64 ul.querySelectorAll('.cpm-menu3__item').forEach(li => { 65 li.classList.toggle('is-dim', li !== activeLi); 66 li.classList.toggle('is-active', li === activeLi); 67 }); 68 }; 69 70 const renderLevel = (ul, ids) => { 71 clear(ul); 72 73 (ids || []).forEach((id) => { 74 const it = byId.get(String(id)); 75 if (!it) return; 76 77 const kids = childrenMap[String(it.id)] || childrenMap[it.id] || []; 78 const hasChildren = kids.length > 0; 79 80 const li = document.createElement('li'); 81 li.className = 'cpm-menu3__item'; 82 li.dataset.id = String(it.id); 83 li.dataset.hasChildren = hasChildren ? '1' : '0'; 84 85 if (hasChildren) { 86 li.innerHTML = ` 87 <button type="button" class="cpm-menu3__btn"> 88 <span class="cpm-menu3__title"></span> 89 ${PLUS} 90 </button> 91 `; 92 li.querySelector('.cpm-menu3__title').textContent = it.title; 93 } else { 94 li.innerHTML = ` 95 <a class="cpm-menu3__link" href="${it.url}"> 96 <span class="cpm-menu3__title"></span> 97 </a> 98 `; 99 li.querySelector('.cpm-menu3__title').textContent = it.title; 100 } 101 102 ul.appendChild(li); 103 }); 104 105 hydrateFromWpMenu(ul); 106 }; 107 108 // â AUTO OPEN (URL -> currentId -> parentMap -> render col2/col3) 109const normalizeKey = (u) => { 110 try { 111 const x = new URL(u, window.location.origin); 112 const host = (x.host || '').replace(/^www\./, '').toLowerCase(); 113 let path = (x.pathname || '/').toLowerCase().replace(/\/+$/, ''); 114 if (path === '') path = '/'; 115 return host + path; 116 } catch (e) { return ''; } 117}; 118 119const buildParentMap = () => { 120 const parent = new Map(); // childId -> parentId 121 Object.keys(childrenMap || {}).forEach(pid => { 122 (childrenMap[pid] || []).forEach(cid => parent.set(String(cid), String(pid))); 123 }); 124 return parent; 125}; 126 127const findCurrentIdByUrl = () => { 128 // â NON aprire nulla in homepage 129 const path = (new URL(window.location.href)).pathname.replace(/\/+$/, '') || '/'; 130 if (path === '/') return null; 131 132 const currentKey = normalizeKey(window.location.href); 133 if (!currentKey) return null; 134 135 const exact = itemsArr.find(it => normalizeKey(it.url) === currentKey); 136 return exact ? String(exact.id) : null; 137}; 138 139 140const findLi = (ul, id) => 141 ul?.querySelector(`.cpm-menu3__item[data-id="${CSS.escape(String(id))}"]`) || null; 142 143const autoOpenFromCurrentPage = () => { 144 const currentId = findCurrentIdByUrl(); 145 if (!currentId) return; 146 147 const parentMap = buildParentMap(); 148 149 // trail risalendo dai parent (es: 6586 -> 6582 -> 228) 150 const trail = []; 151 let cur = String(currentId); 152 while (cur) { 153 trail.push(cur); 154 cur = parentMap.get(cur); 155 } 156 trail.reverse(); // [228, 6582, 6586] 157 158 // â Se il trail inizia con un "root" (0 o id non presente in col1), lo scarto 159 const col1Ids = new Set( 160 Array.from(col1.querySelectorAll('.cpm-menu3__item[data-id]')) 161 .map(li => String(li.dataset.id)) 162 ); 163 164 while (trail.length && !col1Ids.has(String(trail[0]))) { 165 trail.shift(); 166 } 167 168 169 // ricostruisco colonne in modo pulito 170 clear(col2); 171 clear(col3); 172 173 // L1 174 const lvl1Id = trail[0]; 175 if (!lvl1Id) return; 176 177 const li1 = findLi(col1, lvl1Id); 178 if (li1) dimSiblings(col1, li1); 179 180 const kids1 = (childrenMap[lvl1Id] || childrenMap[Number(lvl1Id)] || []).map(String); 181 if (!kids1.length) return; 182 renderLevel(col2, kids1); 183 184 // L2 185 const lvl2Id = trail[1]; 186 if (!lvl2Id) return; 187 188 const li2 = findLi(col2, lvl2Id); 189 if (li2) dimSiblings(col2, li2); 190 191 const kids2 = (childrenMap[lvl2Id] || childrenMap[Number(lvl2Id)] || []).map(String); 192 if (!kids2.length) return; 193 renderLevel(col3, kids2); 194 195 // L3 (solo evidenzio) 196 const lvl3Id = trail[2]; 197 if (!lvl3Id) return; 198 199 const li3 = findLi(col3, lvl3Id); 200 if (li3) dimSiblings(col3, li3); 201}; 202 203 204 // Stato iniziale 205 clear(col2); 206 clear(col3); 207 208 hydrateFromWpMenu(col1); 209 210 autoOpenFromCurrentPage(); 211 212 213 // Click delegation 214 root.addEventListener('click', (e) => { 215 const btn = e.target.closest('.cpm-menu3__btn'); 216 if (!btn) return; 217 218 const li = btn.closest('.cpm-menu3__item'); 219 const ul = li?.closest('.cpm-menu3__list'); 220 if (!li || !ul) return; 221 222 const level = ul.dataset.level; 223 const id = li.dataset.id; 224 225 dimSiblings(ul, li); 226
227 const kids = childrenMap[id] || childrenMap[Number(id)] || []; 228 const hasKids = kids.length > 0; 229 230 if (level === '1') { 231 clear(col3); 232 if (hasKids) { 233 renderLevel(col2, kids); 234 } else { 235 clear(col2); 236 } 237 } 238 239 if (level === '2') { 240 if (hasKids) { 241 renderLevel(col3, kids); 242 } else { 243 clear(col3); 244 } 245 } 246 }); 247 248 //GESTIONE BACKGROUND 249const overlayBg = document.getElementById('headermenu_mobile'); 250const BG_IMAGE = 'url(https://www.cpm.it/wp-content/uploads/2026/01/CPM_fondo-desktop_1920x1080.jpg)'; 251 252const setOverlayBg = (active) => { 253 if (!overlayBg) return; 254 overlayBg.style.backgroundImage = active ? BG_IMAGE : ''; 255}; 256 257// click su voce menu -> sfondo attivo 258root.addEventListener('click', (e) => { 259 const btn = e.target.closest('.cpm-menu3__btn'); 260 if (!btn) return; 261 setOverlayBg(true); 262}); 263 264// bottone "Scegli il tuo corso" desktop -> sfondo attivo 265const chooseBtnDesk = document.getElementById('choose_course_btn_desk'); 266if (chooseBtnDesk) { 267 chooseBtnDesk.addEventListener('click', () => setOverlayBg(true)); 268} 269 270// se c'è già una voce attiva all'apertura â sfondo nostro 271autoOpenFromCurrentPage(); 272if (col1.querySelector('.is-active')) setOverlayBg(true); 273 274// reset sfondo solo quando overlay si chiude 275let bgLastVisible = false; 276setInterval(() => { 277 if (!overlayBg) return; 278 const st = window.getComputedStyle(overlayBg); 279 const isVisible = 280 st.display !== 'none' && 281 st.visibility !== 'hidden' && 282 st.opacity !== '0'; 283 284 if (isVisible && !bgLastVisible) { 285 const anyActive = col1.querySelector('.is-active'); 286 setOverlayBg(!!anyActive); 287 } 288 289 if (!isVisible && bgLastVisible) { 290 setOverlayBg(false); 291 } 292 293 bgLastVisible = isVisible; 294}, 250); 295 296 297});
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.