PageSourceSearch

https://www.americaneagle.ae/scripts/aem.js

js americaneagle.ae collected 2026-10-02 13:25:24 UTC 22,358 bytes, 713 lines download raw bytes

1/*
2 * Copyright 2024 Adobe. All rights reserved.
3 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License. You may obtain a copy
5 * of the License at http://www.apache.org/licenses/LICENSE-2.0
6 *
7 * Unless required by applicable law or agreed to in writing, software distributed under
8 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 * OF ANY KIND, either express or implied. See the License for the specific language
10 * governing permissions and limitations under the License.
11 */
12
13/* eslint-env browser */
14
15/* eslint-env browser */
16function sampleRUM(checkpoint, data) {
17  // eslint-disable-next-line max-len
18  const timeShift = () => (window.performance ? window.performance.now() : Date.now() - window.hlx.rum.firstReadTime);
19  try {
20    window.hlx = window.hlx || {};
21    sampleRUM.enhance = () => {};
22    if (!window.hlx.rum) {
23      const param = new URLSearchParams(window.location.search).get('rum');
24      const weight = (window.SAMPLE_PAGEVIEWS_AT_RATE === 'high' && 10)
25        || (window.SAMPLE_PAGEVIEWS_AT_RATE === 'low' && 1000)
26        || (param === 'on' && 1)
27        || 100;
28      const id = Math.random().toString(36).slice(-4);
29      const isSelected = param !== 'off' && Math.random() * weight < 1;
30      // eslint-disable-next-line object-curly-newline, max-len
31      window.hlx.rum = {
32        weight,
33        id,
34        isSelected,
35        firstReadTime: window.performance ? window.performance.timeOrigin : Date.now(),
36        sampleRUM,
37        queue: [],
38        collector: (...args) => window.hlx.rum.queue.push(args),
39      };
40      if (isSelected) {
41        const dataFromErrorObj = (error) => {
42          const errData = { source: 'undefined error' };
43          try {
44            errData.target = error.toString();
45            errData.source = error.stack
46              .split('\n')
47              .filter((line) => line.match(/https?:\/\//))
48              .shift()
49              .replace(/at ([^ ]+) \((.+)\)/, '$1@$2')
50              .replace(/ at /, '@')
51              .trim();
52          } catch (err) {
53            /* error structure was not as expected */
54          }
55          return errData;
56        };
57
58        window.addEventListener('error', ({ error }) => {
59          const errData = dataFromErrorObj(error);
60          sampleRUM('error', errData);
61        });
62
63        window.addEventListener('unhandledrejection', ({ reason }) => {
64          let errData = {
65            source: 'Unhandled Rejection',
66            target: reason || 'Unknown',
67          };
68          if (reason instanceof Error) {
69            errData = dataFromErrorObj(reason);
70          }
71          sampleRUM('error', errData);
72        });
73
74        sampleRUM.baseURL = sampleRUM.baseURL || new URL(window.RUM_BASE || '/', new URL('https://rum.hlx.page'));
75        sampleRUM.collectBaseURL = sampleRUM.collectBaseURL || sampleRUM.baseURL;
76        sampleRUM.sendPing = (ck, time, pingData = {}) => {
77          // eslint-disable-next-line max-len, object-curly-newline
78          const rumData = JSON.stringify({
79            weight,
80            id,
81            referer: window.location.href,
82            checkpoint: ck,
83            t: time,
84            ...pingData,
85          });
86          const urlParams = window.RUM_PARAMS
87            ? `?${new URLSearchParams(window.RUM_PARAMS).toString()}`
88            : '';
89          const { href: url, origin } = new URL(
90            `.rum/${weight}${urlParams}`,
91            sampleRUM.collectBaseURL,
92          );
93          const body = origin === window.location.origin
94            ? new Blob([rumData], { type: 'application/json' })
95            : rumData;
96          navigator.sendBeacon(url, body);
97          // eslint-disable-next-line no-console
98          console.debug(`ping:${ck}`, pingData);
99        };
100        sampleRUM.sendPing('top', timeShift());
101
102        sampleRUM.enhance = () => {
103          // only enhance once
104          if (document.querySelector('script[src*="rum-enhancer"]')) return;
105          const { enhancerVersion, enhancerHash } = sampleRUM.enhancerContext || {};
106          const script = document.createElement('script');
107          if (enhancerHash) {
108            script.integrity = enhancerHash;
109            script.setAttribute('crossorigin', 'anonymous');
110          }
111          script.src = new URL(
112            `.rum/@adobe/helix-rum-enhancer@${enhancerVersion || '^2'}/src/index.js`,
113            sampleRUM.baseURL,
114          ).href;
115          document.head.appendChild(script);
116        };
117        if (!window.hlx.RUM_MANUAL_ENHANCE) {
118          sampleRUM.enhance();
119        }
120      }
121    }
122    if (window.hlx.rum && window.hlx.rum.isSelected && checkpoint) {
123      window.hlx.rum.collector(checkpoint, data, timeShift());
124    }
125    document.dispatchEvent(new CustomEvent('rum', { detail: { checkpoint, data } }));
126  } catch (error) {
127    // something went awry
128  }
129}
130
131/**
132 * Setup block utils.
133 */
134function setup() {
135  window.hlx = window.hlx || {};
136  window.hlx.RUM_MASK_URL = 'full';
137  window.hlx.RUM_MANUAL_ENHANCE = true;
138  window.hlx.codeBasePath = '';
139  window.hlx.lighthouse = new URLSearchParams(window.location.search).get('lighthouse') 
139=== 'on';
140
141  const scriptEl = document.querySelector('script[src$="/scripts/scripts.js"]');
142  if (scriptEl) {
143    try {
144      [window.hlx.codeBasePath] = new URL(scriptEl.src).pathname.split('/scripts/scripts.js');
145    } catch (error) {
146      // eslint-disable-next-line no-console
147      console.log(error);
148    }
149  }
150}
151
152/**
153 * Auto initializiation.
154 */
155
156function init() {
157  setup();
158  sampleRUM();
159}
160
161/**
162 * Sanitizes a string for use as class name.
163 * @param {string} name The unsanitized string
164 * @returns {string} The class name
165 */
166function toClassName(name) {
167  return typeof name === 'string'
168    ? name
169      .toLowerCase()
170      .replace(/[^0-9a-z]/gi, '-')
171      .replace(/-+/g, '-')
172      .replace(/^-|-$/g, '')
173    : '';
174}
175
176/**
177 * Sanitizes a string for use as a js property name.
178 * @param {string} name The unsanitized string
179 * @returns {string} The camelCased name
180 */
181function toCamelCase(name) {
182  return toClassName(name).replace(/-([a-z])/g, (g) => g[1].toUpperCase());
183}
184
185/**
186 * Extracts the config from a block.
187 * @param {Element} block The block element
188 * @returns {object} The block config
189 */
190// eslint-disable-next-line import/prefer-default-export
191function readBlockConfig(block) {
192  const config = {};
193  block.querySelectorAll(':scope > div').forEach((row) => {
194    if (row.children) {
195      const cols = [...row.children];
196      if (cols[1]) {
197        const col = cols[1];
198        const name = toClassName(cols[0].textContent);
199        let value = '';
200        if (col.querySelector('a')) {
201          const as = [...col.querySelectorAll('a')];
202          if (as.length === 1) {
203            value = as[0].href;
204          } else {
205            value = as.map((a) => a.href);
206          }
207        } else if (col.querySelector('img')) {
208          const imgs = [...col.querySelectorAll('img')];
209          if (imgs.length === 1) {
210            value = imgs[0].src;
211          } else {
212            value = imgs.map((img) => img.src);
213          }
214        } else if (col.querySelector('p')) {
215          const ps = [...col.querySelectorAll('p')];
216          if (ps.length === 1) {
217            value = ps[0].textContent;
218          } else {
219            value = ps.map((p) => p.textContent);
220          }
221        } else value = row.children[1].textContent;
222        config[name] = value;
223      }
224    }
225  });
226  return config;
227}
228
229/**
230 * Loads a CSS file.
231 * @param {string} href URL to the CSS file
232 */
233async function loadCSS(href) {
234  return new Promise((resolve, reject) => {
235    if (!document.querySelector(`head > link[href="${href}"]`)) {
236      const link = document.createElement('link');
237      link.rel = 'stylesheet';
238      link.href = href;
239      link.onload = resolve;
240      link.onerror = reject;
241      document.head.append(link);
242    } else {
243      resolve();
244    }
245  });
246}
247
248/**
249 * Loads a non module JS file.
250 * @param {string} src URL to the JS file
251 * @param {Object} attrs additional optional attributes
252 */
253async function loadScript(src, attrs) {
254  return new Promise((resolve, reject) => {
255    if (!document.querySelector(`head > script[src="${src}"]`)) {
256      const script = document.createElement('script');
257      script.src = src;
258      if (attrs) {
259        // eslint-disable-next-line no-restricted-syntax, guard-for-in
260        for (const attr in attrs) {
261          script.setAttribute(attr, attrs[attr]);
262        }
263      }
264      script.onload = resolve;
265      script.onerror = reject;
266      document.head.append(script);
267    } else {
268      resolve();
269    }
270  });
271}
272
273/**
274 * Retrieves the content of metadata tags.
275 * @param {string} name The metadata name (or property)
276 * @param {Document} doc Document object to query for metadata. Defaults to the window's document
277 * @returns {string} The metadata value(s)
278 */
279function getMetadata(name, doc = document) {
280  const attr = name && name.includes(':') ? 'property' : 'name';
281  const meta = [...doc.head.querySelectorAll(`meta[${attr}="${name}"]`)]
282    .map((m) => m.content)
283    .join(', ');
284  return meta || '';
285}
286
287/**
288 * Returns a picture element with webp and fallbacks
289 * @param {string} src The image URL
290 * @param {string} [alt] The image alternative text
291 * @param {boolean} [eager] Set loading attribute to eager
292 * @param {Array}
292 [breakpoints] Breakpoints and corresponding params (eg. width)
293 * @returns {Element} The picture element
294 */
295function createOptimizedPicture(
296  src,
297  alt = '',
298  eager = false,
299  breakpoints = [{ media: '(min-width: 600px)', width: '2000' }, { width: '750' }],
300) {
301  const url = new URL(src, window.location.href);
302  const picture = document.createElement('picture');
303  const { pathname } = url;
304  const ext = pathname.substring(pathname.lastIndexOf('.') + 1);
305
306  // webp
307  breakpoints.forEach((br) => {
308    const source = document.createElement('source');
309    if (br.media) source.setAttribute('media', br.media);
310    source.setAttribute('type', 'image/webp');
311    source.setAttribute('srcset', `${pathname}?width=${br.width}&format=webply&optimize=medium`);
312    picture.appendChild(source);
313  });
314
315  // fallback
316  breakpoints.forEach((br, i) => {
317    if (i < breakpoints.length - 1) {
318      const source = document.createElement('source');
319      if (br.media) source.setAttribute('media', br.media);
320      source.setAttribute('srcset', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
321      source.classList.add('fallback');
322      picture.appendChild(source);
323    } else {
324      const img = document.createElement('img');
325      img.setAttribute('loading', eager ? 'eager' : 'lazy');
326      img.setAttribute('alt', alt);
327      img.classList.add('fallback');
328      picture.appendChild(img);
329      img.setAttribute('src', `${pathname}?width=${br.width}&format=${ext}&optimize=medium`);
330    }
331  });
332
333  return picture;
334}
335
336/**
337 * Set template (page structure) and theme (page styles).
338 */
339function decorateTemplateAndTheme() {
340  const addClasses = (element, classes) => {
341    classes.split(',').forEach((c) => {
342      element.classList.add(toClassName(c.trim()));
343    });
344  };
345  const template = getMetadata('template');
346  if (template) addClasses(document.body, template);
347  const theme = getMetadata('theme');
348  if (theme) addClasses(document.body, theme);
349}
350
351/**
352 * Decorates paragraphs containing a single link as buttons.
353 * @param {Element} element container element
354 */
355function decorateButtons(element) {
356  element.querySelectorAll('a').forEach((a) => {
357    a.title = a.title || a.textContent;
358    if (a.href !== a.textContent && !a.closest('.no-button')) {
359      const up = a.parentElement;
360      const twoup = a.parentElement.parentElement;
361      if (!a.querySelector('img')) {
362        if (up.childNodes.length === 1 && (up.tagName === 'P' || up.tagName === 'DIV')) {
363          a.className = 'button'; // default
364          up.classList.add('button-container');
365        }
366        if (
367          up.childNodes.length === 1
368          && up.tagName === 'STRONG'
369          && twoup.childNodes.length === 1
370          && twoup.tagName === 'P'
371        ) {
372          a.className = 'button primary';
373          twoup.classList.add('button-container');
374        }
375        if (
376          up.childNodes.length === 1
377          && up.tagName === 'EM'
378          && twoup.childNodes.length === 1
379          && twoup.tagName === 'P'
380        ) {
381          a.className = 'button secondary';
382          twoup.classList.add('button-container');
383        }
384      }
385    }
386  });
387}
388
389/**
390 * Add <img> for icon, prefixed with codeBasePath and optional prefix.
391 * @param {Element} [span] span element with icon classes
392 * @param {string} [prefix] prefix to be added to icon src
393 * @param {string} [alt] alt text to be added to icon
394 */
395function decorateIcon(span, prefix = '', alt = '') {
396  const iconName = Array.from(span.classList)
397    .find((c) => c.startsWith('icon-'))
398    .substring(5);
399  const img = document.createElement('img');
400  img.dataset.iconName = iconName;
401  img.src = `${window.hlx.codeBasePath}${prefix}/icons/${iconName}.svg`;
402  img.alt = alt || iconName;
403  img.loading = 'lazy';
404  span.append(img);
405}
406
407/**
408 * Add <img> for icons, prefixed with codeBasePath and optional prefix.
409 * @param {Element} [element] Element containing icons
410 * @param {string} [prefix] prefix to be added to icon the src
411 */
412function decorateIcons(element, prefix = '') {
413  const icons = [...element.querySelectorAll('span.icon')];
414  icons.forEach((span) => {
415    decorateIcon(span, prefix);
416  });
417}
418
419/**
420 * Decorates all sections in a container element.
421 * @param {Element} main The container element
422 */
423function decorateSections(main) {
424  main.querySelectorAll(':scope > div').forEach((section) => {
425    const wrappers = [];
426    let defaultContent = false;
427    [...section.children].forEach((e) => {
428      if (e.tagName === 'DIV' || !defaultContent) {
429        const wrapper = document.createElement('div');
430        wrappers.push(wrapper);
431        defaultContent = e.tagName !== 'DIV';
432        if (defaultContent) wrapper.classList.add('default-content-wrapper');
433      }
434      wrappers[wrappers.length - 1].append(e);
435    });
436    wrappers.forEach((wrapper) => section.append(wrapper));
437    section.classList.add('section');
438    section.dataset.sectionStatus = 'initialized';
439    section.style.display = 'none';
440
441    // Process section metadata
442    const sectionMeta = section.querySelector('div.section-metadata');
443    if (sectionMeta) {
444      const meta = readBlockConfig(sectionMeta);
445      Object.keys(meta).forEach((key) => {
446        if (key === 'style') {
447          const styles = meta.style
448            .split(',')
449            .filter((style) => style)
450            .map((style) => toClassName(style.trim()));
451          styles.forEach((style) => section.classList.add(style));
452        } else {
453          section.dataset[toCamelCase(key)] = meta[key];
454        }
455      });
456      sectionMeta.parentNode.remove();
457    }
458  });
459}
460
461/**
462 * Gets placeholders object.
463 * @param {string} [prefix] Location of placeholders
464 * @returns {object} Window placeholders object
465 */
466// eslint-disable-next-line import/prefer-default-export
467async function fetchPlaceholders(prefix = 'default') {
468  window.placeholders = window.placeholders || {};
469  if (!window.placeholders[prefix]) {
470    window.placeholders[prefix] = new Promise((resolve) => {
471      fetch(`${prefix === 'default' ? '' : prefix}/placeholders.json`)
472        .then((resp) => {
473          if (resp.ok) {
474            return resp.json();
475          }
476          return {};
477        })
478        .then((json) => {
479          const placeholders = {};
480          json.data
481            .filter((placeholder) => placeholder.Key)
482            .forEach((placeholder) => {
483              placeholders[toCamelCase(placeholder.Key)] = placeholder.Text;
484            });
485          window.placeholders[prefix] = placeholders;
486          resolve(window.placeholders[prefix]);
487        })
488        .catch(() => {
489          // error loading placeholders
490          window.placeholders[prefix] = {};
491          resolve(window.placeholders[prefix]);
492        });
493    });
494  }
495  return window.placeholders[`${prefix}`];
496}
497
498/**
499 * Updates all section status in a container element.
500 * @param {Element} main The container element
501 */
502function updateSectionsStatus(main) {
503  const sections = [...main.querySelectorAll(':scope > div.section')];
504  for (let i = 0; i < sections.length; i += 1) {
505    const section = sections[i];
506    const status = section.dataset.sectionStatus;
507    if (status !== 'loaded') {
508      const loadingBlock = section.querySelector(
509        '.block[data-block-status="initialized"], .block[data-block-status="loading"]',
510      );
511      if (loadingBlock) {
512        section.dataset.sectionStatus = 'loading';
513        break;
514      } else {
515        section.dataset.sectionStatus = 'loaded';
516        section.style.display = null;
517      }
518    }
519  }
520}
521
522/**
523 * Builds a block DOM Element from a two dimensional array, string, or object
524 * @param {string} blockName name of the block
525 * @param {*} content two dimensional array or string or object of content
526 */
527function buildBlock(blockName, content) {
528  const table = Array.isArray(content) ? content : [[content]];
529  const blockEl = document.createElement('div');
530  // build image block nested div structure
531  // Alshaya: added condition to allow variations to be passed while building block
532  if (Array.isArray(blockName)) {
533    blockEl.classList.add(...blockName);
534  } else {
535    blockEl.classList.add(blockName);
536  }
537
538  table.forEach((row) => {
539    const rowEl = document.createElement('div');
540    row.forEach((col) => {
541      const colEl = document.createElement('div');
542      const vals = col.elems ? col.elems : [col];
543      vals.forEach((val) => {
544        if (val) {
545          if (typeof val === 'string') {
546            colEl.innerHTML += val;
547          } else {
548            colEl.appendChild(val);
549          }
550        }
551      });
552      rowEl.appendChild(colEl);
553    });
554    blockEl.appendChild(rowEl);
555  });
556  return blockEl;
557}
558
559/**
560 * Loads JS and CSS for a block.
561 * @param {Element} block The block element
562 */
563async function loadBlock(block) {
564  const status = block.dataset.blockStatus;
565  if (status !== 'loading' && status !== 'loaded') {
566    block.dataset.blockStatus = 'loading';
567    const { blockName } = block.dataset;
568    try {
569      const cssLoaded = loadCSS(`${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.css`);
570      const decorationComplete = new Promise((resolve) => {
571        (async () => {
572          try {
573            const mod = await import(
574              `${window.hlx.codeBasePath}/blocks/${blockName}/${blockName}.js`
575            );
576            if (mod.default) {
577              await mod.default(block);
578            }
579          } catch (error) {
580            // eslint-disable-next-line no-console
581            console.log(`failed to load module for ${blockName}`, error);
582          }
583          resolve();
584        })();
585      });
586      await Promise.all([cssLoaded, decorationComplete]);
587    } catch (error) {
588      // eslint-disable-next-line no-console
589      console.log(`failed to load block ${blockName}`, error);
590    }
591    block.dataset.blockStatus = 'loaded';
592  }
593  return block;
594}
595
596async function loadBlocksSubselection(main, selection) {
597  updateSectionsStatus(main);
598  const blocks = [...selection];
599  for (let i = 0; i < blocks.length; i += 1) {
600    // eslint-disable-next-line no-await-in-loop
601    await loadBlock(blocks[i]);
602    updateSectionsStatus(main);
603  }
604}
605
606/**
607 * Loads JS and CSS for all blocks in a container element.
608 * @param {Element} main The container element
609 */
610async function loadBlocks(main) {
611  return loadBlocksSubselection(main, main.querySelectorAll('div.block'));
612}
613
614/**
615 * Decorates a block.
616 * @param {Element} block The block element
617 */
618function decorateBlock(block) {
619  const shortBlockName = block.classList[0];
620  if (shortBlockName) {
621    block.classList.add('block');
622    block.dataset.blockName = shortBlockName;
623    block.dataset.blockStatus = 'initialized';
624    const blockWrapper = block.parentElement;
625    blockWrapper.classList.add(`${shortBlockName}-wrapper`);
626    const section = block.closest('.section');
627    if (section) section.classList.add(`${shortBlockName}-container`);
628  }
629}
630
631/**
632 * Decorates all blocks in a container element.
633 * @param {Element} main The container element
634 */
635function decorateBlocks(main) {
636  main.querySelectorAll('div.section > div > div').forEach(decorateBlock);
637}
638
639/**
640 * Loads a block named 'header' into header
641 * @param {Element} header header element
642 * @returns {Promise}
643 */
644async function loadHeader(header) {
645  const headerBlock = buildBlock('header', '');
646  header.append(headerBlock);
647  decorateBlock(headerBlock);
648  return loadBlock(headerBlock);
649}
650
651/**
652 * Loads a block named 'footer' into footer
653 * @param footer footer element
654 * @returns {Promise}
655 */
656async function loadFooter(footer) {
657  const footerBlock = buildBlock('footer', '');
658  footer.append(footerBlock);
659  decorateBlock(footerBlock);
660  return loadBlock(footerBlock);
661}
662
663/**
664 * Load LCP block and/or wait for LCP in default content.
665 * @param {Array} lcpBlocks Array of blocks
666 */
667async function waitForLCP(lcpBlocks) {
668  const block = document.querySelector('.block');
669  const hasLCPBlock = block && lcpBlocks.includes(block.dataset.blockName);
670  if (hasLCPBlock) await loadBlock(block);
671
672  document.body.style.display = null;
673  const lcpCandidate = document.querySelector('main img');
674
675  await new Promise((resolve) => {
676    if (lcpCandidate && !lcpCandidate.complete) {
677      lcpCandidate.setAttribute('loading', 'eager');
678      lcpCandidate.addEventListener('load', resolve);
679      lcpCandidate.addEventListener('error', resolve);
680    } else {
681      resolve();
682    }
683  });
684}
685
686init();
687
688export {
689  buildBlock,
690  createOptimizedPicture,
691  decorateBlock,
692  decorateBlocks,
693  decorateButtons,
694  decorateIcons,
695  decorateSections,
696  decorateTemplateAndTheme,
697  fetchPlaceholders,
698  getMetadata,
699  loadBlock,
700  loadBlocks,
701  loadBlocksSubselection,
702  loadCSS,
703  loadFooter,
704  loadHeader,
705  loadScript,
706  readBlockConfig,
707  sampleRUM,
708  setup,
709  toCamelCase,
710  toClassName,
711  updateSectionsStatus,
712  waitForLCP,
713};

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.