PageSourceSearch

https://www.dm.gov.ae/wp-content/themes/dubaimunicipality/assets/scripts/search.js

js dm.gov.ae collected 2026-09-24 08:37:35 UTC 30,981 bytes, 809 lines download raw bytes

1jQuery(function ($) {
2  "use strict";
3
4  /* ===========================
5   * UTILITIES
6   * =========================== */
7  const isRTL   = document.documentElement.lang.slice(0, 2) === "ar";
8  const locale  = isRTL ? "ar-AE" : "en-US";
9
10  const i18n = $.extend({
11    noTitle: "No title available",
12    noDesc: "No description available",
13    resultsFound: "Results Found",
14    resultsFoundPrefix: "",
15    servicesHeader: "Services",
16    contentHeader: "Content",
17    filesHeader: "Files",
18    noResults: "No results found. Please try a different term",
19    enterTerm: "Enter a search term above to see results",
20    startSug: "Start searching to see suggestions",
21    noSug: "No suggestions found",
22    micPerm:
23      'Permission Access to microphone denied, to use this feature please allow microphone access to this site, for help  <a href="https://support.google.com/chrome/answer/2693767?co=GENIE.Platform%3DDesktop" target="_blank" title="Google Chrome Help">click here</a>',
24    micNoSpeech:
25      "No audio detected, please check your microphone settings and try again",
26    open: "Open",
27    share: "Share",
28    download: "Download",
29    fileText: "FILE",
30    documentText: "Document",
31    lastModified: "Last modified",
32  }, isRTL ? {
33    enterTerm: "أدخل عبارة أعلاه ليتم عرض النتائج",
34    noSug: "لم يتم العثور على اي اقتراحات",
35    filesHeader: "المستندات ",
36    contentHeader: "المحتوى",
37    servicesHeader: "الخدمات",
38    startSug: "ابدأ البحث لعرض الاقتراحات",
39    resultsFound: "تم العثور على ",
40    resultsFoundPrefix: "نتائج",
41    noResults: "لم يتم العثور على اي نتائج. يرجى تجربة مصطلح مختلف",
42    micPerm:
43      '<a href="https://support.google.com/chrome/answer/2693767?co=GENIE.Platform%3DDesktop" target="_blank" title="Google Chrome Help">تم رفض إذن الوصول إلى الميكروفون، لاستخدام هذه الميزة، يرجى تفعيل استخدام الميكروفون للوصول إلى هذا الموقع ، للمساعدة انقر هنا</a>',
44    noTitle: "لا يوجد عنوان متاح",
45    noDesc: "لا يوجد وصف متاح",
46    open: "فتح",
47    share: "شارك",
48    download: "تحميل",
49    fileText: "ملف",
50    documentText: "وثيقة",
51    lastModified: "تاريخ اخر تعديل",
52    micNoSpeech:
53      "لم يتم اكتشاف صوت ، يرجى التحقق من إعدادات الميكروفون وإعادة المحاولة",
54  } : {});
55
56  const serviceImageUrl           = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg";
57  const serviceScreenCaptureEnUrl = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg";
58  const serviceScreenCaptureArUrl = "https://www.dm.gov.ae/wp-content/uploads/2020/06/services_screen.jpg";
59
60  const esc = (s) => String(s ?? "")
61    .replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
62
63  const fmtDate = (ts) => {
64    try { return new Date(ts).toLocaleDateString(locale); }
65    catch { return "-"; }
66  };
67
68  // Simple highlighter without raw regex injection
69  const highlight = (text, q) => {
70    if (!text || !q) return text || "";
71    const norm = (v) => v.toString().toLowerCase();
72    const t = text.toString();
73    const idx = norm(t).indexOf(norm(q));
74    if (idx === -1) return t;
75    return t.slice(0, idx) + "<em>" + t.slice(idx, idx + q.length) + "</em>" + t.slice(idx + q.length);
76  };
77
78  const detectBrowser = () => {
79    const ua = navigator.userAgent;
80    if ((ua.indexOf("Opera") !== -1 && ua.indexOf("OPR") !== -1)) return "Opera";
81    if (ua.indexOf("Chrome")  !== -1) return "Chrome";
82    if (ua.indexOf("Safari")  !== -1) return "Safari";
83    if (ua.indexOf("Firefox") !== -1) return "Firefox";
84    if (ua.indexOf("MSIE")    !== -1 || !!document.documentMode) return "IE";
85    return "Unknown";
86  };
87
88  // tiny debounce
89  const debounce = (fn, ms=300) => {
90    let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn.apply(null,args), ms); };
91  };
92
93  /* ===========================
94   * CACHED SELECTORS
95   * =========================== */
96  const $resultsPanel  = $(".search-results-results");
97  const $pagingPanel   = $(".search-paging-pages");
98  const $pagingInfo   = $(".search-results-pageinfo");
99  const $sugPanel      = $(".search-results-suggestions-list");
100  const $errorPanel    = $(".search-error-panel");
101  const $termEcho      = $(".search-results-term-keyword");
102  const $searchInfo    = $(".search-results-info");
103  const $searchCount   = $(".search-results-count-no");
104  const $searchInput   = $("#search_page_input");
105
106  const $filter     = $("#search_filter_input");
107  const $perPage       = $("#search-paging-perpage-input");
108  const $sort       = $("#search_sort_input");
109
110  let viewAll = false;
111
112  /* ===========================
113   * BASIC UI (existing small pieces)
114   * =========================== */
115  $(".dubai-more-box").on("click", function () {
116    const cls = $(this).data("div");
117    $(".dubai-more-box").not(this).removeClass("active");
118    $(this).addClass("active");
119    $(".dubaimore_content").hide().filter("." + cls).show();
120  });
121
122  // enable Fancybox when user tabs into right column
123  $(".search-results-right").on("focusin", function () {
124    $("a.ajaxFancyBox").fancybox({ padding: 0 });
125  });
126
127  if (isRTL) {
128    $('.rtl #search-top-input-input').attr("placeholder", "البحث عن");
129    $('.rtl .filterby').text("ترتيب النتائج :");
130    $('.rtl .sortby').text("ترتيب حسب :");
131    $('.rtl .perpage').text("لكل صفحة");
132    $('.rtl .showpaging').text("عرض");
133    $('.rtl .search-filter-services-lbl').text("الخدمات");
134    $('.rtl .search-filter-content-lbl').text("المحتوى");
135    $('.rtl .search-filter-documents-lbl').text("الملفات");
136    $('.rtl .search-filter-all-lbl').text("عرض الكل");
137    $('.rtl .searchsuggestionslabel').text("اقتراحات البحث :");
138    $('.rtl .search-sort-services-lbl').text("الأقدم");
139    $('.rtl .search-sort-content-lbl').text("الأحدث");
140    $('.rtl .search-results-term-text').text("نتائج البحث عن ");
141    $('.rtl .search-results-count-text').text("تم العثور على:");
142    $('.rtl .no-searchMsg').text("أدخل عبارة أعلاه ليتم عرض النتائج");
143    $('.rtl .search-results-suggestions-list li:nth-of-type(1)').text("ابدأ البحث لعرض الاقتراحات");
144    $('.rtl .search-header-area .search span').text("إجراء بحث");
145    $('.rtl .iconSerch').attr("href", "https://stgportal03.dm.gov.ae/search-dubai-municipality-arabic/?lang=ar");
146  }
147
148  /* ===========================
149   * ADDSEARCH CLIENTS
150   * =========================== */
151  const client = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6");
152  client.setThrottleTime(500);
153
154  // Header search UIs (desktop + mobile)
155  const conf = { searchResultsPageUrl: "/search", collectAnalytics: true };
156
157  const uiDesktop = new AddSearchUI(client, conf);
158  uiDesktop.searchField({
159    containerId: "searchcontainer-header",
160    placeholder: isRTL ? "ابحث" : "Search",
161    autofocus: false
162  });
163
164  const uiMobile = new AddSearchUI(client, conf);
165  uiMobile.searchField({
166    containerId: "searchcontainer-headermobile",
167    placeholder: isRTL ? "ابحث" : "Search",
168    autofocus: false
169  });
170
171  const acTpl = `
172    <div class="addsearch-autocomplete" style="position:relative;">
173      {{#gt searchResults.results.length 0}}
174        <ul>
175          {{#each ../searchResults.results}}
176            <li id="{{id}}" class="autocomplete-item search-item-suggestion"
177                data-title="{{title}}" data-url="{{url}}">
178              <span class="main-image {{document_type}} {{#unless images.main}}noimage{{/unless}}"
179                {{#if images.main}} style="background-image:url({{images.main}})"{{/if}}
180                {{#if custom_fields.is_service}} style="background-image:url('/wp-content/uploads/2020/06/services_screen.jpg')"{{/if}}>
181              </span>
182              <div><strong>{{title}}</strong><br />{{{highlight}}}</div>
183            </li>
184          {{/each}}
185        </ul>
186      {{/gt}}
187    </div>`;
188
189  const acClientDesktop = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6");
190  acClientDesktop.setPaging(1, 5, "relevance", "desc");
191  uiDesktop.autocomplete({
192    containerId: "autocomplete-header",
193    template: acTpl,
194    hideAutomatically: false,
195    infiniteScrollElement: document.querySelector("#scrollable-header"),
196    sources: [{ type: AddSearchUI.AUTOCOMPLETE_TYPE.SEARCH, client: acClientDesktop, collectSearchAnalytics: true, jsonKey: "results" }]
197  });
198  uiDesktop.start();
199
200  const acClientMobile = new AddSearchClient("b3b578f840aadc0f27cf47ff57bce2a6");
201  acClientMobile.setPaging(1, 5, "relevance", "desc");
202  acClientMobile.setThrottleTime(500);
203  uiMobile.autocomplete({
204    containerId: "autocomplete-headermobile",
205    template: acTpl,
206    hideAutomatically: false,
207    infiniteScrollElement: document.querySelector("#scrollable-headermobile"),
208    sources: [{ type: AddSearchUI.AUTOCOMPLETE_TYPE.SEARCH, client: acClientMobile, collectSearchAn
208alytics: true, jsonKey: "results" }]
209  });
210  uiMobile.start();
211
212  // Header “Search” buttons & Enter handling
213  $(document).on("click", ".searchNewBtn", function () {
214    const v = $("#searchcontainer-header").find("input").val();
215    localStorage.setItem("searchValue", v);
216  });
217  $(document).on("keyup", "#searchcontainer-header input", function (e) {
218    if (e.keyCode === 13) {
219      const v = $("#searchcontainer-header").find("input").val();
220      localStorage.setItem("searchValue", v);
221      window.location.href = $("body").hasClass("rtl")
222        ? "/search-dubai-municipality-arabic/?lang=ar" : "/search/";
223      e.preventDefault();
224    }
225  });
226  $(document).on("click", "#scrollable-header .search-item-suggestion", function () {
227    client.sendStatsEvent("click", $("#searchcontainer-header").find("input").val(),
228      { documentId: $(this).attr("id"), position: 1 });
229    window.location.href = $(this).data("url");
230  });
231
232  $(document).on("keyup", "#searchcontainer-headermobile input", function (e) {
233    if (e.keyCode === 13) {
234      const v = $("#searchcontainer-headermobile").find("input").val();
235      localStorage.setItem("searchValue", v);
236      window.location.href = $("body").hasClass("rtl")
237        ? "/search-dubai-municipality-arabic/?lang=ar" : "/search/";
238      e.preventDefault();
239    }
240  });
241  $(document).on("click", "#scrollable-headermobile .search-item-suggestion", function () {
242    client.sendStatsEvent("click", $("#searchcontainer-headermobile").find("input").val(),
243      { documentId: $(this).attr("id"), position: 1 });
244    window.location.href = $(this).data("url");
245  });
246
247  /* ===========================
248   * RENDER HELPERS
249   * =========================== */
250  const headerHTML = (label) => `
251    <div class="search-result-header fa fa-angle-down">
252      <span class="search-result-header-text">${esc(label)}</span>
253    </div>`;
254
255  const moreInfoHTML = (r) => {
256    if (r.category !== "Document") return "";
257    const canShare = !!navigator.share;
258    return `
259      <div class="search-result-document-more-info">
260        <div class="more-info-text">
261          <h3>${esc(r.titleNoEm || r.title || i18n.noTitle)}</h3>
262          <p class="more-info-file">${i18n.fileText}</p>
263          <p class="more-info-date">${i18n.lastModified}: <span>${esc(r.timestamp || "-")}</span></p>
264        </div>
265        <div class="more-info-actions">
266          <a href="${esc(r.url)}" title="${i18n.open}" class="more-info-open">
267            <i class="fas fa-external-link"></i>${i18n.open}
268          </a>
269          ${canShare ? `
270            <a href="#" data-url="${esc(r.url)}" data-title="${esc(r.titleNoEm || r.title || "")}"
271               class="more-info-share" title="${i18n.share}">
272              <i class="fas fa-share-alt"></i> ${i18n.share}
273            </a>` : ""}
274          <a href="${esc(r.url)}" download target="_blank" title="${i18n.download}" class="more-info-download">
275            <i class="fas fa-download"></i> ${i18n.download}
276          </a>
277        </div>
278      </div>`;
279  };
280
281  const imageHTML = (r) => {
282    if (!r.imageUrl) {
283      return `<div class="search-result-image-placeholder"><i class="far fa-image"></i></div>`;
284    }
285    return `<img src="${esc(r.imageUrl)}" title="${esc(r.titleNoEm || r.title || "")}" />`;
286  };
287
288  const capturePreviewLink = (r) => {
289    if (!r.captureUrl) return "";
290    const title = esc(r.titleNoEm || r.title || "");
291    return `
292      <a href="${esc(r.captureUrl)}" class="ajaxFancyBox" data-fancybox="${esc(r.id || "")}"
293         data-title="${title}" title="${title}" style="color:#800823;">
294        <i class="fa fa-info-circle"></i>
295      </a>`;
296  };
297
298  const resultHTML = (r) => `
299    <div class="search-results-result search-result-${esc(r.category)} thin-bottom-border"
300         data-capture-url="${esc(r.captureUrl || "")}">
301      <div class="search-result-result-info">
302        <div class="search-result-title">
303        <a href="${esc(r.url||"#")}" data-id="${esc(r.id||"")}" data-pos="${esc(r.pos||0)}"
304           class="search_result_link" title="${esc(r.titleNoEm||r.title||i18n.noTitle)}">
305          <h2 class="search-results-result-title">${r.title || i18n.noTitle}</h2>
306        </a>
307        <p class="search-results-result-desc">${r.desc || i18n.noDesc}</p>
308        </div>
309        <div class="search-results-result-cat">${esc(r.displayCat || "")}</div>
310      </div>
311    </div>`;
312
313  /* ===========================
314   * SEARCH RENDERING
315   * =========================== */
316  function renderResults(res) {
317
318    if (!res || !Array.isArray(res.hits)) {
319      $resultsPanel.html("");
320      $pagingPanel.html(" ");
321      return;
322    }
323
324    const q = $searchInput.val();
325    const docs = [], svcs = [], content = [];
326    let pos = 1;
327
328    res.hits.forEach((el) => {
329      const r = {
330        id: el.id || "",
331        pos: pos++,
332        imageUrl: "",
333        title: i18n.noTitle,
334        titleNoEm: i18n.noTitle,
335        desc: i18n.noDesc,
336        url: el.url || "#",
337        timestamp: el.ts ? fmtDate(el.ts) : "-",
338        captureUrl: "",
339        category: "",
340        displayCat: ""
341      };
342
343      if (el.images) {
344        r.imageUrl   = el.images.main || el.images.capture || "";
345        r.captureUrl = el.images.capture || "";
346      }
347
348      if (el.title) {
349        r.titleNoEm = el.title;
350        r.title     = highlight(esc(el.title), q);
351      } else if (el.highlight) {
352        r.title     = el.highlight;
353        r.titleNoEm = el.highlight.replace(/<[^>]+>/g, "");
354      }
355
356      if (el.highlight) r.desc = el.highlight;
357      else if (el.meta_description) r.desc = el.meta_description;
358
359      // Categorize
360      if (el.custom_fields && el.custom_fields.is_service === "1") {
361        r.category   = "Service";
362        r.displayCat = i18n.servicesHeader;
363        r.imageUrl   = serviceImageUrl;
364        r.captureUrl = isRTL ? serviceScreenCaptureArUrl : serviceScreenCaptureEnUrl;
365        if (el.custom_fields.service_desc) {
366          r.desc = el.custom_fields.service_desc.replace(/(<([^>]+)>)/gi, "")
367                                               .replace(/^(.{200}[^\s]*).*/, "$1");
368        }
369        svcs.push(r);
370      /*} else if (el.categories && el.categories.length > 2 && el.categories[1] === "1xwp-content") {*/
371      } else if (el.custom_fields && el.custom_fields.isDocument === "1") {
372        r.category   = "Document";
373        r.displayCat = i18n.documentText;
374        docs.push(r);
375      } else if (el.categories && el.categories.length > 2 && el.categories[1] === "1xwp-content") {
376      } else {
377        r.category   = "Content";
378        r.displayCat = i18n.contentHeader;
379        /*if (el.categories && el.categories.length > 1) {
380          r.displayCat = el.categories[1].substring(2).replace(/-/g, " ");
381        } else {
382          r.displayCat = i18n.contentHeader;
383        }*/
384        content.push(r);
385      }
386    });
387
388    let html = "";
389    if (svcs.length) {
390      //headerHTML(i18n.servicesHeader)
391      html += '<div class="service_block">';
392      svcs.forEach((r) => html += resultHTML(r));
393      html += "</div>";
394    }
395    if (content.length) {
396      html += '<div class="content_block">';
397      content.forEach((r) => html += resultHTML(r));
398      html += "</div>";
399    }
400    if (docs.length) {
401      html += '<div class="document_block">';
402      docs.forEach((r) => html += resultHTML(r));
403      html += "</div>";
404    }
405
406    $resultsPanel.html(html);
407
408    renderPaging(res);
409
410    // hoverintent document info
411    if (typeof hoverintent !== "undefined") {
412      $(".search-result-Document").each(function () {
413        const el = this;
414        hoverintent(
415          el,
416          function () {
417            const $popup = $(el).find(".search-result-document-more-info");
418            const pos = $(el).position();
419            $popup.css({ top: pos.top }).show();
420          },
421          function () { $(el).find(".search-result-document-more-info").hide(); }
422        );
423      });
424    }
425
426    // web share
427    $resultsPanel.off("click.share").on("click.share", ".more-info-share", function (e) {
428      e.preventDefault();
429      if (navigator.share) {
430        navigator.share({ url: $(this).data("url"), title: $(this).data("title") });
431      }
432    });
433
434    $searchInfo.show();
435    $searchCount.text((res.total_hits || 0).toLocaleString(locale));
436  }
437
438  function renderPaging(res) {
439  $pagingPanel.html("");
440
441  if (!res || !res.total_hits) {
442    $resultsPanel.html(`<p class="search-result-empty">${i18n.noResults}</p>`);
443    return;
444  }
445
446  // --- basic variables ---
447  let pageSize = parseInt($perPage.val(), 10) || 10;
448  if (viewAll) pageSize = 300;
449
450  const totalResults = res.total_hits || 0;
451  const totalPages   = Math.max(1, Math.ceil(totalResults / pageSize));
452  const currentPage  = Math.min(Math.max(res.page || 1, 1), totalPages);
453
454  // --- page info ---
455  const startIndex = (currentPage - 1) * pageSize + 1;
456  const endIndex   = Math.min(currentPage * pageSize, totalResults);
457  var pageInfoText = `Showing ${startIndex.toLocaleString(locale)} to ${endIndex.toLocaleString(locale)} of ${totalResults.toLocaleString(locale)} results`;
458  if(isRTL){
459    pageInfoText = `عرض ${startIndex.toLocaleString(locale)} إلى ${endIndex.toLocaleString(locale)} من أصل ${totalResults.toLocaleString(locale)} نتيجة`;
460    
461  }
462
463
464  $(".search-results-pageinfo-range").remove();
465  $pagingInfo.html(`<div class="search-results-pageinfo-range">${pageInfoText}</div>`);
466
467  // --- helper for ellipses window ---
468  function makePageWindow(total, current) {
469    const s = new Set([1, 2, total - 1, total, current - 1, current, current + 1]);
470    const nums = [...s].filter(n => n >= 1 && n <= total).sort((a, b) => a - b);
471    const out = [];
472    for (let i = 0; i < nums.length; i++) {
473      if (i > 0 && nums[i] - nums[i - 1] > 1) out.push("…");
474      out.push(nums[i]);
475    }
476    return out;
477  }
478
479  const windowPages = makePageWindow(totalPages, currentPage);
480
481  // --- build HTML ---
482  let html = `<div class="dm-pager">`;
483
484  // Prev arrow
485  html += `
486    <button class="dm-pager-arrow button_default button_square mr-1" data-page="${currentPage - 1}"
487            ${currentPage === 1 ? "disabled" : ""} aria-label="Previous page"><i class="fa fa-arrow-left"></i></button>`;
488
489  // Numbered buttons
490  windowPages.forEach(p => {
491    if (p === "…") {
492      html += `<span class="dm-pager-ellipsis mr-1">…</span>`;
493    } else {
494      const isCurrent = Number(p) === currentPage;
495      html += `<button class="dm-pager-btn ${isCurrent ? "active" : ""} button_default button_square mr-1" data-page="${p}"
496                  ${isCurrent ? 'aria-current="page"' : ""}>${p}</button>`;
497    }
498  });
499
500  // Next arrow
501  html += `
502    <button class="dm-pager-arrow  button_default button_square mr-1" data-page="${currentPage + 1}"
503            ${currentPage === totalPages ? "disabled" : ""} aria-label="Next page"><i class="fa fa-arrow-right"></i></button>`;
504
505  html += `</div>`; // end .dm-pager
506
507  $pagingPanel.html(html);
508
509  // --- events ---
510  $pagingPanel.off("click.page").on("click.page", "[data-page]", function () {
511    const p = parseInt($(this).data("page"), 10);
512    if (!Number.isFinite(p) || p < 1 || p > totalPages || p === currentPage) return;
513    startSearch($searchInput.val(), p);
514  });
515}
516
517
518
519
520  function renderSuggestions(sug) {
521    if (!sug || !Array.isArray(sug.suggestions) || !sug.suggestions.length) {
522      $sugPanel.html(`<li class="search-suggestion-empty">${i18n.noSug}</li>`);
523      return;
524    }
525    const html = sug.suggestions.map(s => `<li class="search-suggestion-result">${esc(s.value)}</li>`).join("");
526    $sugPanel.html(html);
527  }
528
529  /* ===========================
530   * SEARCH FLOW
531   * =========================== */
532  function applyFilters(page, pageSize) {
533    const isDoc  = ($filter.val()=="Document");
534    const isCont = ($filter.val()=="Content");
535    const isSvc  = ($filter.val()=="Services");
536    const asc    = ($sort.val()=="Oldest");
537    const desc   = ($sort.val()=="Latest");
538
539    if (isDoc) {
540      client.addCustomFieldFilter("isDocument", "1");
541      client.removeCustomFieldFilter("isContent");
542      client.removeCustomFieldFilter("is_service");
543    } else if (isCont) {
544      client.removeCustomFieldFilter("isDocument");
545      client.removeCustomFieldFilter("is_service");
546      client.addCustomFieldFilter("isContent", "1");
547    } else if (isSvc) {
548      client.removeCustomFieldFilter("isDocument");
549      client.removeCustomFieldFilter("isContent");
550      client.addCustomFieldFilter("is_service", "1");
551    } else {
552      client.removeCustomFieldFilter("isDocument");
553      client.removeCustomFieldFilter("isContent");
554      client.removeCustomFieldFilter("is_service");
555    }
556
557    if (asc)      client.setPaging(page, pageSize, "date", "asc");
558    else if (desc)client.setPaging(page, pageSize, "date", "desc");
559    else         client.setPaging(page, pageSize, "relevance", "desc");
560
561    client.setFuzzyMatch("retry");
562  }
563
564  function startSearch(term, page) {
565    $errorPanel.html(" ");
566    const q = (term || "").trim();
567    
568    if (q.length <= 1) {
569      $resultsPanel.html(`<p class="search-result-empty">${i18n.enterTerm}</p>`);
570      $pagingPanel.html(" ");
571      $termEcho.text("");
572      return;
573    }
574
575    localStorage.setItem("searchValue", q);
576    $(".search-paging-panel").show();
577
578    let pageSize = parseInt($perPage.val(), 10) || 10;
579    if (viewAll) pageSize = 300;
580
581    applyFilters(page, pageSize);
582
583    client.search(q, renderResults);
584    client.suggestions(q, renderSuggestions);
585    client.sendStatsEvent("search", q, { numberOfResults: $searchCount.text() });
586
587    // clickable suggestions
588    $sugPanel.off("click.pick").on("click.pick", ".search-suggestion-result", function () {
589      const text = $(this).text();
590      $searchInput.val(text);
591      startSearch(text, 1);
592    });
593
594    $termEcho.text(`"${q}"`);
595  }
596
597  // expose if needed
598  window.startSearch = startSearch;
599
600  // click tracking for result links
601  $(document).on("click", ".search_result_link", function () {
602    client.sendStatsEvent("click", $searchInput.val(), {
603      documentId: $(this).data("id"),
604      position: $(this).data("pos")
605    });
606  });
607
608  /* ===========================
609   * VOICE SEARCH
610   * =========================== */
611  const browser = detectBrowser();
612
613  // Show voice button when supported (Chrome webkitSpeechRecognition OR fallback to Azure)
614  if ("webkitSpeechRecognition" in window || ["Opera", "Firefox", "Safari"].includes(browser)) {
615    $(".search-voice").show();
616  }
617
618  // Chrome: webkitSpeechRecognition
619  if ("webkitSpeechRecognition" in window) {
620    const rec = new webkitSpeechRecognition();
621    rec.continuous = false;
622    rec.interimResults = false;
623    rec.lang = isRTL ? "ar-AE" : "en-US";
624
625    let listening = false;
626
627    $(".search-voice").on("click", function () {
628      $errorPanel.html("");
629      if (["Firefox","Opera","OPR","Safari"].includes(browser)) return; // handled by Azure below
630
631      if (listening) {
632        rec.abort();
633        listening = false;
634        $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
635      } else {
636        listening = true;
637        rec.start();
638        $(".search-voice").html('<i class="fal fa-microphone-alt-slash"></i>');
639      }
640    });
641
642    rec.onresult = function (e) {
643      const path = window.location.href;
644      const said = (e.results?.[0]?.[0]?.transcript) || "";
645      if (!said) return;
646
647      if (path.indexOf("/search") !== -1) {
648        startSearch(said, 1);
649        $searchInput.val(said);
650      } else {
651        localStorage.setItem("searchValue", said);
652        window.location.href = $("body").hasClass("rtl")
653          ? "/search-dubai-municipality-arabic/?lang=ar" : "/search";
654      }
655      listening = false;
656      $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
657    };
658
659    rec.onnomatch = rec.onend = function () {
660      $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
661    };
662
663    rec.onerror = function (e) {
664      if (e.error === "not-allowed") {
665        $errorPanel.html('<span class="voice-permission-denied">' + i18n.micPerm + "</span>");
666      } else if (e.error === "no-speech") {
667        $errorPanel.html('<span class="voice-permission-denied">' + i18n.micNoSpeech + "</span>");
668      }
669      $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
670    };
671  }
672
673  // Fallback (Firefox/Opera/Safari): Azure Speech Translation (client-side demo; avoid hardcoding keys)
674  if (["Opera","Firefox","Safari"].includes(browser)) {
675    $(".search-voice").on("click", function () {
676      $errorPanel.html("");
677      $(".search-voice").html('<i class="fal fa-microphone-alt-slash"></i>');
678
679      const SpeechSDK = window.SpeechSDK;
680      if (!SpeechSDK) {
681        console.warn("Azure Speech SDK missing");
682        $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
683        return;
684      }
685
686      const speechConfig = SpeechSDK.SpeechTranslationConfig.fromSubscription("YOUR_AZURE_SPEECH_KEY", "centralindia");
687      speechConfig.speechRecognitionLanguage = "en-US";
688      const lang = "en";
689      speechConfig.addTargetLanguage(lang);
690
691      const audioConfig = SpeechSDK.AudioConfig.fromDefaultMicrophoneInput();
692      const recognizer  = new SpeechSDK.TranslationRecognizer(speechConfig, audioConfig);
693
694      recognizer.recognizeOnceAsync(function (result) {
695        if (result.reason === SpeechSDK.ResultReason.TranslatedSpeech) {
696          let translation = result.translations.get(lang) || "";
697          if (translation.endsWith(".")) translation = translation.slice(0, -1);
698
699          const path = window.location.href;
700          if (path.indexOf("/search") !== -1) {
701            if (translation) {
702              startSearch(translation, 1);
703              $searchInput.val(translation);
704            }
705          } else {
706            if (translation) {
707              localStorage.setItem("searchValue", translation);
708              window.location.href = $("body").hasClass("rtl")
709                ? "/search-dubai-municipality-arabic/?lang=ar" : "/search";
710            }
711          }
712        }
713        $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
714        recognizer.close();
715      }, function (err) {
716        console.error(err);
717        $(".search-voice").html('<i class="fal fa-microphone-alt"></i>');
718        recognizer.close();
719      });
720    });
721  }
722
723  /* ===========================
724   * SEARCH PANEL CONTROLS
725   * =========================== */
726  $("#search_filter_input, #search_sort_input").on("change", function () {
727    startSearch($searchInput.val(), 1);
728  });
729
730  $/*(".search-open-search, .small-side-menu-container .search").on("click", function () {
731    $("body").toggleClass("dynamic-search-open");
732    $(".search-results-info").hide();
733    $("#dynamic-search-area").show();
734    $searchInput.trigger("focus");
735  });*/
736
737  $("#search-top-close-btn").on("click", function () {
738    $(".search-results-results").html("<p>" + i18n.enterTerm + "</p>");
739    $searchInput.val("");
740    $errorPanel.html(" ");
741    $(".search-results-term-keyword").html("");
742    $(".search-results-count-no").html("0");
743    $(".search-results-info").hide();
744    $(".search-filter").prop("checked", false);
745    $("#search-filter-all").prop("checked", true);
746    $pagingPanel.html("");
747    $sugPanel.html("<li>" + i18n.startSug + "</li>");
748  });
749
750    // Typing → debounced search
751    $searchInput.on("input", debounce(function () {
752        viewAll = false;
753        startSearch($searchInput.val(), 1); // use the cached selector
754    }, 350));
755    
756    var passValue = localStorage.getItem('searchValue');
757    setTimeout(function(){ 
758        if(passValue !='' && passValue !=undefined && passValue !=null){
759            $("#search_page_input").val(passValue);
760            startSearch(passValue, 1); // use the cached selector
761        }
762    }, 100);
763
764  $("#search-paging-perpage-input").on("change", function () {
765    startSearch($searchInput.val(), 1);
766  });
767
768  // Stats on suggestion clicks (header/mobile areas already wired above)
769  $(document).on("click", ".search_result_link", function () {
770    client.sendStatsEvent("click", $searchInput.val(), {
771      documentId: $(this).data("id"),
772      position: $(this).data("pos")
773    });
774  });
775
776  /* ===========================
777   * CONTENT PAGE SPECIAL ANCHORS
778   * =========================== */
779  (function wireSpecialAnchors() {
780    const curr = window.location.href;
781
782    // English page
783    if (curr === "https://dm.gov.ae/discover-dubai/saruq-al-hadid/") {
784      const pairs = [
785        { sel: "a[href='/saruq-al-hadid/#The-Site']", id: "#The-Site" },
786        { sel: "a[href='/saruq-al-hadid/#the-museum']", id: "#the-museum" },
787        { sel: "a[href='/saruq-al-hadid/#publications']", id: "#publications" },
788      ];
789      pairs.forEach(({ sel, id }) => {
790        if ($(sel).length) $(sel).attr("href","javascript:void(0);")
791          .on("click", () => $("html, body").animate({ scrollTop: $(id).offset().top }, 1000));
792      });
793    }
794
795    // Arabic page
796    if (curr.indexOf("/%d8%a7%d9%83%d8%aa%d8%b4%d9%81-%d8%af%d8%a8%d9%8a/%d8%b3%d8%a7%d8%b1%d9%88%d9%82-%d8%a7%d9%84%d8%ad%d8%af%d9%8a%d8%af/?lang=ar") !== -1) {
797      const pairs = [
798        { sel: "a[href='/ساروق-الحديد/?lang=ar#The-Site']", id: "#The-Site" },
799        { sel: "a[href='/ساروق-الحديد/?lang=ar#the-museum']", id: "#the-museum" },
800        { sel: "a[href='/ساروق-الحديد/?lang=ar#publications']", id: "#publications" },
801      ];
802      pairs.forEach(({ sel, id }) => {
803        if ($(sel).length) $(sel).attr("href","javascript:void(0);")
804          .on("click", () => $("html, body").animate({ scrollTop: $(id).offset().top }, 1000));
805      });
806    }
807  })();
808
809});

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.