PageSourceSearch

https://era.je/themes/custom/astro_audio_theme/components/content-row-carousel/content-row-carousel.js?tluj44

js era.je collected 2026-10-02 13:49:20 UTC 12,673 bytes, 356 lines download raw bytes

1(function ($, Drupal, once) {
2  'use strict';
3  Drupal.behaviors.contentrowCarousel = {
4    attach: function (context, settings) {
5      const apiUrl = drupalSettings?.astro_radio_components?.api_url ?? '';
6      
7      
8      
9      const carousels = once('contentrowCarouselInit', '.content-row-items', context);
10      //console.log(carousels);
11      if (!carousels.length) {
12        return;
13      }
14      const settingsGroup = drupalSettings.astro_radix || {};
15      const useFade = settingsGroup.enableFade === true;
16      const sliderSpeed = settingsGroup.sliderSpeed || 3000;
17      //console.log(carousels);
18      carousels.forEach(function (el) {
19        if (el.classList.contains('dummy-slider')) {
20          fetchData(apiUrl, el);
21          return
22        };
23        
24        initSwiper(el);
25        
26      });
27      function initSwiper(el) {
28        if (window.innerWidth > 960) {
29          return;
30        }
31       
32        const hasThirteenLayout = el.querySelector('.thirteen-content-row');
33        
34        const slides = el.querySelectorAll('.swiper-slide');
35        
36        const totalItems = slides.length;
37        
38        const swiperOptions = {
39          slidesPerView: 'auto',
40          autoplay: {
41            delay: sliderSpeed,
42          },
43        };
44        if (hasThirteenLayout) {
45          swiperOptions.spaceBetween = 8;
46        }
47        else {;
48          swiperOptions.spaceBetween = 12;
49          if (totalItems > 6) {
50            const rows = Math.ceil(totalItems / 6);
51            swiperOptions.grid = {
52              rows: rows,
53              fill: 'row',
54            };
55            swiperOptions.autoplay.disableOnInteraction = false;
56            swiperOptions.autoplay.pauseOnMouseEnter = true;
57            swiperOptions.watchOverflow = true;
58          }
59        }
60        if (useFade && !swiperOptions.grid) {
61          swiperOptions.effect = 'fade';
62          swiperOptions.fadeEffect = {
63            crossFade: true,
64          };
65        }
66       
67        new Swiper(el, swiperOptions);
68      }
69
70      
71      async function fetchData(apiUrl, el) {
72        
73        const controller = new AbortController();
74        const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
75       
76        let payload = null;
77        try {
78          const res = await fetch(apiUrl, {
79            method: 'GET',
80            headers: { 'Accept': 'application/json' },
81            credentials: 'omit',
82            cache: 'no-store',
83            signal: controller.signal
84          });
85          if (!res.ok) throw new Error('HTTP ' + res.status);
86          payload = await res.json();
87          
88          const ok = payload && Number(payload.statusCode) === 200 && Array.isArray(payload.data) && payload.data.length > 0;
89          if (!ok) {
90            
91            return;
92          }
93          console.log(payload);
94          applyUpdates(el, payload);
95        } catch (e) {
96          // console.warn('Podcast API error:', e);
97          clearTimeout(timeoutId);
98          // Return the container (now placed) so caller can decide on fallback UI
99          return null;
100        } finally {
101          clearTimeout(timeoutId);
102        }
103        return payload;
104      }
105
106      
107      function applyUpdates(el, payload) {
108        // A) Identify nodes
109        const templateHost = el.closest('.js-content-row-template');
110        if (!templateHost) return;
111
112        const swiperCarousel = el.closest('.swiper-carousel');
113        if (!swiperCarousel) return;
114
115        // B) Find the sibling `.content-row`
116        const contentRow = findSiblingContentRow(templateHost) || templateHost.parentElement || document.body;
117
118        try {
119          // 1) Normalize data array
120          const dataArray = Array.isArray(payload)
121            ? payload
122            : (payload && Array.isArray(payload.data) ? payload.data : []);
123
124          if (!dataArray.length) {
125            console.warn('[applyUpdates] No items in payload to render.');
126            return;
127          }
128
129          // 2) Get the template slide HTML from inside this carousel
130          const wrapper = swiperCarousel.querySelector('.swiper-wrapper') || swiperCarousel;
131          const templateSlide = swiperCarousel.querySelector('.swiper-slide');
132          let templateHTML;
133
134          if (templateSlide) {
135            templateHTML = templateSlide.outerHTML;
136          }
137          else {
138            return;
139          }
140          
141          // 3) Replace existing slides with new ones
142          wrapper.innerHTML = ''; // replace, not append
143          const frag = document.createDocumentFragment();
144
145          dataArray.forEach(item => {
146            
147            const imgUrl =
148              item.squareImage || item.landscapeImage || item.portraitImage || '';
149            const title = item.name || item.alternativeTitle || '';
150            const link = item.showUrl || '#';
151            let show_id = item.id || 'show_id';
152            const slugtitle = item.alternativeTitle || item.name || '';
153
154            if (typeof slugUtil?.generateSlug == 'function') {
155              show_id = slugUtil.generateSlug(show_id, slugtitle);
156            }
157            // Use the same token replacement style you used previously
158            let html = templateHTML;
159            html = html
160              .replace(/@img/g, imgUrl || '')
161              .replace(/@title/g, title || '')
162              .replace(/show_id_token/g, show_id || '');
163              
164            
165            const slideEl = htmlToElement(html);
166
167            // Ensure the root has the correct class
168            if (!slideEl.classList.contains('swiper-slide')) {
169              slideEl.classList.add('swiper-slide');
170            }
171            // If the template was initially in the DOM with real attributes that
172            // didn’t include tokens (rare), apply a minimal DOM fallback as a safety net:
173            //domSafetyFallback(slideEl, { imgUrl, title, link });
174
175            frag.appendChild(slideEl);
176          });
177
178          wrapper.appendChild(frag);
179
180          // 4) Re-init Swiper safely (optional but recommended after rebuilding slides)
181          //safeReinitSwiper(swiperCarousel);
182         
183          // 5) Move `.swiper-carousel` after `.content-row-head` (keep header intact)
184          const head = contentRow.querySelector('.content-row-head');
185          
186          if (head && head.parentNode === contentRow) {
187            contentRow.insertBefore(swiperCarousel, head.nextSibling);
188          } else {
189            contentRow.appendChild(swiperCarousel);
190          }
191          
192          try {
193            const parent = contentRow.parentElement;
194            if (parent) {
195              // Option A: remove the first matching sibling under the same parent
196              const loader = parent.querySelector(':scope > .loading-state-wrapper');
197              if (loader) {
198                loader.remove?.() || loader.parentNode?.removeChild(loader);
199              }
200            }
201          } catch (e) {
202            console.warn('[applyUpdates] Could not remove sibling .loading-state-wrapper:', e);
203          }
204
205
206
207          const container = contentRow.querySelector('.content-row-items');
208
209          if (!container) {
210            console.warn('[applyUpdates] No .content-row-items container found to initialize Swiper.');
211            return;
212          }
213
214          initSwiper(container);
215        } catch (err) {
216          console.error('[applyUpdates] Error while building/replacing swiper slides:', err);
217        }
218
219        // ========= Helpers =========
220        function findSiblingContentRow(node) {
221          // 1) Direct prev/next siblings
222          const prev = previousMatching(node, '.content-row');
223          if (prev) return prev;
224          const next = nextMatching(node, '.content-row');
225          if (next) return next;
226
227          // 2) Scan parent’s children (same level)
228          if (node.parentElement) {
229            const siblings = Array.from(node.parentElement.children);
230            const found = siblings.find(
231              (n) => n !== node && n.classList && n.classList.contains('content-row')
232            );
233            if (found) return found;
234          }
235          return null;
236        }
237
238        function previousMatching(node, selector) {
239          let cur = node.previousElementSibling;
240          while (cur) {
241            if (cur.matches && cur.matches(selector)) return cur;
242            cur = cur.previousElementSibling;
243          }
244          return null;
245        }
246
247        function nextMatching(node, selector) {
248          let cur = node.nextElementSibling;
249          while (cur) {
250            if (cur.matches && cur.matches(selector)) return cur;
251            cur = cur.nextElementSibling;
252          }
253          return null;
254        }
255
256        function htmlToElement(html) {
257          const t = document.createElement('template');
258          t.innerHTML = html.trim();
259          return t.content.firstElementChild;
260        }
261
262        // Minimal DOM safety in case the template had some static attributes without tokens
263        function domSafetyFallback(slideEl, { imgUrl, title, link }) {
264          // Anchor
265          const a = slideEl.querySelector('a');
266          if (a && (!a.hasAttribute('href') || a.getAttribute('href').includes('@link'))) {
267            a.href = link || '#';
268          }
269
270          // Image
271          const imgEl = slideEl.querySelector('img');
272          if (imgEl) {
273            const needsSrc =
274              !imgEl.hasAttribute('src') ||
275              imgEl.getAttribute('src').includes('@img') ||
276              imgEl.getAttribute('src') === '';
277            if (needsSrc && imgUrl) imgEl.setAttribute('src', imgUrl);
278            if (!imgEl.hasAttribute('alt') || imgEl.getAttribute('alt').includes('@title')) {
279              imgEl.setAttribute('alt', title || 'Image');
280            }
281            if (imgEl.hasAttribute('data-src')) imgEl.setAttribute('data-src', imgUrl || '');
282            if (imgEl.hasAttribute('srcset')) imgEl.setAttribute('srcset', imgUrl || '');
283          }
284
285          // Title text
286          const titleEl =
287            slideEl.querySelector('[data-field="title"], .js-card-title, .card-title, h3, h4, .slide-title');
288          if (titleEl) {
289            const current = (titleEl.textContent || '').trim();
290            if (!current || current.includes('@title')) {
291              titleEl.textContent = title || '';
292            }
293          }
294        }
295
296        function safeReinitSwiper(containerEl) {
297          // Destroy existing instance if any
298          if (containerEl.swiper && typeof containerEl.swiper.destroy === 'function') {
299            containerEl.swiper.destroy(true, true);
300          }
301          // Recreate if Swiper is available
302          if (typeof Swiper !== 'undefined') {
303            // eslint-disable-next-line no-undef
304            const instance = new Swiper(containerEl, {
305              slidesPerView: 2,
306              spaceBetween: 12,
307              loop: false,
308              watchSlidesProgress: true,
309              breakpoints: {
310                480: { slidesPerView: 2.2, spaceBetween: 12 },
311                768: { slidesPerView: 3, spaceBetween: 16 },
312                1024: { slidesPerView: 4, spaceBetween: 20 },
313              },
314              navigation: {
315                nextEl: containerEl.querySelector('.swiper-button-next'),
316                prevEl: containerEl.querySelector('.swiper-button-prev'),
317              },
318              pagination: {
319                el: containerEl.querySelector('.swiper-pagination'),
320                clickable: true,
321              },
322            });
323            
324          } else {
325            console.warn('[applyUpdates] Swiper constructor not found; skipped reinit.');
326          }
327        }
328      }
329
330
331
332
333    }
334  };
335  Drupal.behaviors.contentRowCarouselListing = {
336    attach: function (context, settings) {
337      const analyticsPage = settings?.analytics_data?.page;
338      const contentBrand = drupalSettings?.astro_analytics?.brand || '';
339      if (!analyticsPage) return;
340      once('content_row_carousel', 'body', context).forEach(() => {
341        document.addEventListener('click', function (e) {
342          const anchor = e.target.closest('.content-row .content-row-item .content-row-wrap a');
343          if (!anchor) return;
344          const rowItem = anchor.closest('.content-row-item');
345          const data = {
346            event: 'content_click',
347            content_brand: contentBrand,
348            list_type: 'Content row',
349          };
350          Drupal.astro_analytics.setCustomEvent('content_click', data, analyticsPage);
351          anchor.classList.add('analytics-processed');
352        }, true);
353      });
354    }
355  };
356})(jQuery, Drupal, once);

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.