document.addEventListener('DOMContentLoaded', () => { // We instantiate the class when the DOM is ready new PartnerExpoSearch(); }); class PartnerExpoSearch { constructor() { this.els = { shell: document.getElementById("pexpo-core-shell"), topbar: document.getElementById("pexpo-core-topbar"), filterWrap: document.getElementById("pexpo-core-filterWrap"), filterBtn: document.getElementById("pexpo-core-filterBtn"), filterDrawer: document.getElementById("pexpo-core-filterDrawer"), drawerBackdrop: document.getElementById("pexpo-core-drawerBackdrop"), filterFTag: document.getElementById("pexpo-core-tag-toggle"), filterTags: document.getElementById("pexpo-core-tags"), qInput: document.getElementById("pexpo-core-q"), masonry: document.getElementById("pexpo-core-masonry"), measure: document.getElementById("pexpo-core-measure"), emptyResult: document.getElementById("pexpo-core-emptyResult"), count: document.getElementById("pexpo-core-count"), layoutMeta: document.getElementById("pexpo-core-layoutMeta"), sortSelect: document.getElementById("pexpo-core-sortSelect"), filterApply: document.getElementById("pexpo-core-filterApply"), pageNum: document.getElementById("pexpo-core-page-number"), pagePrev: document.getElementById("pexpo-core-page-prev"), pageNext: document.getElementById("pexpo-core-page-next"), }; this.state = { filters: { q: "", sort: "relevance", force_tags: false, tags: [], resultsPerPage: 20, page: 1, }, activeSortKey: "relevance", total: '', pages: 1, results: [], layoutQueued: false }; this.SORTS = { relevance: "Relevánsság (legjobb → legrosszabb)", date_desc: "Dátum (új → régi)", date_asc: "Dátum (régi → új)", title_asc: "Cím (A → Z)", title_desc: "Cím (Z → A)", }; if (this.els.shell) { this.init(); } } init() { this.initMultiSelect(); this.initSortDropdown(); this.bindEvents(); this.syncTopbarHeight(); this.initResizeObserver(); // Initial Fetch this.fetchData(); } // ---------- Initialization Helpers ---------- initMultiSelect() { if (typeof MultiSelect !== 'undefined') { new MultiSelect(this.els.filterTags, { placeholder: 'Címkék kiválasztása', search: true, selectAll: false, onSelect: (value) => { if (!this.state.filters.tags.includes(value)) { this.state.filters.tags.push(value); } }, onUnselect: (value) => { this.state.filters.tags = this.state.filters.tags.filter(tag => tag !== value); } }); } else { console.warn("MultiSelect library not found."); } } initSortDropdown() { if (!this.els.sortSelect) return; this.els.sortSelect.innerHTML = Object.entries(this.SORTS) .map(([k, label]) => ``) .join(""); this.els.sortSelect.value = this.state.activeSortKey; } initResizeObserver() { const ro = new ResizeObserver(() => { this.syncTopbarHeight(); this.requestLayout(); }); ro.observe(this.els.shell); ro.observe(this.els.masonry); } // ---------- Event Binding ---------- bindEvents() { // Sort if (this.els.sortSelect) { this.els.sortSelect.addEventListener("change", () => { this.state.activeSortKey = this.els.sortSelect.value; this.state.filters.sort = this.state.activeSortKey; this.fetchData(); }); } // Search this.els.qInput.addEventListener("keyup", (e) => { if (e.key === "Enter") { this.state.filters.q = this.els.qInput.value.trim(); this.fetchData(); } }); // Tag Toggle this.els.filterFTag.addEventListener("change", () => { this.state.filters.force_tags = this.els.filterFTag.checked; }); // Drawer Toggle this.els.filterBtn.addEventListener("click", () => { const isOpen = this.els.filterDrawer.classList.contains("pexpo-core-open"); this.setDrawerOpen(!isOpen); }); this.els.filterApply.addEventListener("click", () => { this.fetchData(); this.setDrawerOpen(false); }); this.els.drawerBackdrop.addEventListener("click", () => this.setDrawerOpen(false)); document.addEventListener("keydown", (e) => { if (e.key === "Escape" && this.els.filterDrawer.classList.contains("pexpo-core-open")) { this.setDrawerOpen(false); } }); this.els.pageNext.addEventListener("click", () => { if (this.state.filters.page < this.state.pages) { this.state.filters.page += 1; this.fetchData(); } }); this.els.pagePrev.addEventListener("click", () => { if (this.state.filters.page > 1) { this.state.filters.page -= 1; this.fetchData(); } }); } // ---------- Fetch ---------- async fetchData() { this.els.masonry.style.opacity = "0.5"; try { const queryString = this.encodeDataToURL(this.state.filters); const response = await fetch(`/wp-json/pexpo/v1/query?${queryString}`); const data = await response.json(); this.state.results = data['results'] || []; this.state.total = data['found'] || 0; this.state.pages = data['pages'] || 1; console.log("Fetched data:", data['pages']); this.requestLayout(); } catch (error) { console.error("Search failed:", error); this.els.masonry.innerHTML = `
Hiba történt a keresés során.
`; } finally { this.els.masonry.style.opacity = "1"; } } // ---------- UI & Layout Methods ---------- setDrawerOpen(open) { this.els.filterWrap.classList.toggle("pexpo-core-open", open); this.els.filterBtn.setAttribute("aria-expanded", String(open)); this.els.filterDrawer.classList.toggle("pexpo-core-open", open); this.els.filterDrawer.setAttribute("aria-hidden", String(!open)); this.els.drawerBackdrop.classList.toggle("pexpo-core-show", open); this.els.shell.classList.toggle("pexpo-core-drawerOpen", open); this.syncTopbarHeight(); } syncTopbarHeight() { const h = this.els.topbar.offsetHeight; document.documentElement.style.setProperty("--topbarH", h + "px"); } // ---------- Masonry Engine ---------- minCard() { return Number(getComputedStyle(document.documentElement).getPropertyValue("--cardMin")) || 240; } gap() { return Number(getComputedStyle(document.documentElement).getPropertyValue("--gap")) || 12; } requestLayout() { if (this.state.layoutQueued) return; this.state.layoutQueued = true; requestAnimationFrame(() => { this.state.layoutQueued = false; this.applyMasonry(); }); } getColumnCount() { const w = this.els.masonry.clientWidth || this.els.masonry.getBoundingClientRect().width || 1; const mc = this.minCard(); const g = this.gap(); return Math.max(1, Math.floor((w + g) / (mc + g))); } measureCardHeights(cols, list) { const w = this.els.masonry.clientWidth || 1; const g = this.gap(); const colW = Math.floor((w - (cols - 1) * g) / cols); this.els.measure.style.width = colW + "px"; this.els.measure.innerHTML = ""; const measured = list.map((r, idx) => { const el = this.createCardElement(r); this.els.measure.appendChild(el); const h = el.offsetHeight; this.els.measure.removeChild(el); return { r, idx, h }; }); return { measured, colW }; } packStandard(measured, cols) { // 1. Create an array to track the current height of each column const colHeights = new Array(cols).fill(0); // 2. Prepare the array to store where each item goes const placements = []; // 3. Loop through every item strictly in order for (const item of measured) { // 4. Find the column that is currently the shortest let bestCol = 0; for (let c = 1; c < cols; c++) { if (colHeights[c] < colHeights[bestCol]) { bestCol = c; } } // 5. Place the item there placements.push({ item: item, col: bestCol }); // 6. Update that column's height colHeights[bestCol] += item.h + this.gap(); } return placements; } // packRankAware(measured, cols, K = 6) { // const colHeights = new Array(cols).fill(0); // const placements = []; // const queue = measured.slice(); // while (queue.length) { // // Find shortest column // let bestCol = 0; // for (let c = 1; c < cols; c++) { // if (colHeights[c] < colHeights[bestCol]) bestCol = c; // } // const lookN = Math.min(K, queue.length); // let pick = 0; // for (let i = 1; i < lookN; i++) { // if (queue[i].h > queue[pick].h) pick = i; // } // const picked = queue.splice(pick, 1)[0]; // placements.push({ item: picked, col: bestCol }); // colHeights[bestCol] += picked.h + this.gap(); // } // return placements; // } applyMasonry() { const n = this.state.results.length || 0; const page = this.state.filters.page || 1; const pages = this.state.pages; this.els.count.textContent = this.state.total ? this.state.total : ''; this.els.pageNum.textContent = page; this.els.masonry.innerHTML = ""; if (page >= pages) { this.els.pageNext.setAttribute("disabled", "disabled"); } else { this.els.pageNext.removeAttribute("disabled"); } if (page <= 1) { this.els.pagePrev.setAttribute("disabled", "disabled"); } else { this.els.pagePrev.removeAttribute("disabled"); } if (n === 0) { if(this.els.emptyResult) { this.els.count.textContent = this.state.total ? this.state.total : 0; let emptyClone = this.els.emptyResult.cloneNode(true); emptyClone.style.display = "block"; this.els.masonry.appendChild(emptyClone); } else { this.els.masonry.innerHTML = `
Nincs találat.
`; } return; } const cols = this.getColumnCount(); const colEls = []; // Create Columns for (let c = 0; c < cols; c++) { const col = document.createElement("div"); col.className = "pexpo-core-mCol"; this.els.masonry.appendChild(col); colEls.push(col); } const sliced = this.state.results.slice(); const { measured } = this.measureCardHeights(cols, sliced); // const placements = this.packRankAware(measured, cols, 6); const placements = this.packStandard(measured, cols); for (const p of placements) { colEls[p.col].appendChild(this.createCardElement(p.item.r)); } } createCardElement(r) { const div = document.createElement("div"); div.className = "pexpo-core-card"; div.innerHTML = `
${this.escapeHtml(r.title)}
${this.escapeHtml(r.title)}
${this.escapeHtml(r.excerpt)}
${ Array.isArray(r.tag) ? r.tag.map(element => `${this.escapeHtml(element)}`).join('') : '' }
${this.escapeHtml(r.date)}
`; return div; } // ---------- Utilities ---------- escapeHtml(str) { return String(str).replace(/[&<>"']/g, s => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[s])); } encodeDataToURL(data) { return Object .keys(data) .map(value => `${value}=${encodeURIComponent(data[value])}`) .join('&'); } }