PageSourceSearch

https://www.phenomenex.com/blocks/header/header-search.js

js phenomenex.com collected 2026-10-03 23:17:45 UTC 5,944 bytes, 164 lines download raw bytes

1import { decorateIcons } from '../../scripts/aem.js';
2// eslint-disable-next-line import/no-cycle
3import { RECENT_KEY } from '../../scripts/constants.js';
4import {
5  div,
6  span,
7  li,
8} from '../../scripts/dom-builder.js';
9import { trackClickEvent } from '../../scripts/tracking/page_click_event.js';
10import { EventName } from '../../scripts/tracking/page_constants.js';
11import { locale } from './header-utils.js';
12
13// Key for storing recent searches in localStorage
14export default function renderRecentSearch() {
15  // Always use the unified search-suggestions class for both desktop and mobile
16  const searchLists = document.querySelectorAll('ul.search-suggestions');
17  if (!searchLists.length) return;
18
19  // Get last 5 unique recent search terms (case-insensitive, preserve order)
20  let items = [];
21  try {
22    const raw = JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
23    const seen = new Set();
24    raw.forEach((term) => {
25      if (items.length >= 5) return;
26      const key = term.trim().toLowerCase();
27      if (!seen.has(key)) {
28        seen.add(key);
29        items.push(term);
30      }
31    });
32  } catch {
33    items = [];
34  }
35
36  // If no items, clear and hide all lists
37  if (!items.length) {
38    searchLists.forEach((searchList) => {
39      searchList.innerHTML = '';
40      searchList.parentElement?.classList.add('hidden');
41    });
42    return;
43  }
44
45  // Render header and items for each list
46  searchLists.forEach((searchList) => {
47    searchList.innerHTML = '';
48    searchList.parentElement?.classList.remove('hidden');
49    searchList.parentElement?.classList.remove('border-b', 'border-grey-dim');
50    searchList.nextElementSibling?.classList.add('hidden');
51    searchList.classList.remove('py-4', 'pb-4', 'border-b', 'border-grey-dim');
52    searchList.classList.add('pb-4');
53
54    // Header row
55    const headerLi = li(
56      { class: 'flex justify-between items-center mb-2 px-4 pt-4' },
57      span(
58        {
59          class:
60            'font-proxima font-bold text-[1.125rem] leading-[1] tracking-normal text-grey-45',
61        },
62        'Recent Searches',
63      ),
64      span(
65        {
66          class:
67            'font-proxima font-normal text-[0.875rem] leading-[1] tracking-normal text-grey-45 cursor-pointer hover:underline',
68        },
69        'Clear',
70      ),
71    );
72    searchList.appendChild(headerLi);
73
74    // Clear all lists when 'Clear' is clicked
75    headerLi.lastChild.addEventListener('click', () => {
76      localStorage.removeItem(RECENT_KEY);
77      renderRecentSearch();
78    });
79
80    // Render each unique recent search term
81    items.forEach((term) => {
82      const item = li(
83        {
84          class:
85            'group flex justify-between items-center py-2 hover:bg-grey-55 px-4 !cursor-pointer',
86        },
87        span(
88          {
89            onclick: (e) => {
90              e.preventDefault();
91              window.location.assign(
92                `${(window.location.origin.includes('phx') || window.location.origin.includes('localhost') ? `${locale}/` : '/')}search-result#q=${encodeURIComponent(term)}`,
93              );
94              setTimeout(() => window.location.reload(), 1000);
95              trackClickEvent({ eventName: EventName.SEARCH, clickTitle: term });
96              // Remove focus from input boxes immediately
97              document.querySelectorAll('.search-bar-desktop, .search-bar-mobile').forEach((inputEl) => {
98                inputEl.blur();
99              });
100
101              // Close suggestions container
102              document.querySelectorAll('.suggestions-container').forEach((container) => {
103                container.classList.add('hidden');
104              });
105
106              // Use same logic as enteredRedirect to avoid page refresh on search-result page
107              const isOnSearchPage = window.location.pathname.includes('/search-result');
108              if (isOnSearchPage) {
109                // Just update hash - Coveo's URL manager will handle the rest
110                window.location.hash = `q=${encodeURIComponent(term)}`;
111              } else {
112                // Navigate to search page from other pages
113                window.location.assign(
114                  `${(window.location.origin.includes('phx') || window.location.origin.includes('localhost') ? `${locale}/` : '/')}search-result#q=${encodeURIComponent(term)}`,
115                );
116              }
117            },
118            class: 'flex items-center gap-2 hover:no-underline w-full break-all',
119          },
120          div(
121            { class: 'h-5 w-5 flex items-center justify-center' },
122            span({
123              class: 'icon icon-recent-history w-[0.980625rem] h-[0.980625rem]',
124            }),
125          ),
126          span(
127            {
128              class:
129                'py-0.5 font-proxima font-normal not-italic text-base leading-none tracking-normal text-grey-45 no-underline group-hover:underline group-hover:decoration-solid group-hover:decoration-skip-ink-auto cursor-pointer',
130            },
131            term,
132          ),
133        ),
134        div(
135          { class: 'h-6 w-6 flex items-center justify-center cursor-pointer' },
136          span({
137            class:
138              'icon icon-cross w-[0.564375rem] h-[0.564375rem] cursor-pointer',
139          }),
140        ),
141      );
142
143      // Remove item from all lists when cross is clicked
144      item.querySelector('.icon-cross').addEventListener('click', (e) => {
145        e.preventDefault();
146        e.stopPropagation();
147        let raw = [];
148        try {
149          raw = JSON.parse(localStorage.getItem(RECENT_KEY) || '[]');
150        } catch {
151          // Ignore parse errors, use empty array
152        }
153        const key = term.trim().toLowerCase();
154        const updated = raw.filter((t) => t.trim().toLowerCase() !== key);
155        localStorage.setItem(RECENT_KEY, JSON.stringify(updated));
156        renderRecentSearch();
157      });
158
159      searchList.appendChild(item);
160    });
161
162    decorateIcons(searchList);
163  });
164}

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.