1(function() { 2 "use strict"; 3 4 const containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 5 if (!containerUtils) { 6 // eslint-disable-next-line no-console 7 console.warn("Carousel: container utilities at window.CQ.CoreComponents.container.utils are not available. This can lead to missing features. Ensure the core.wcm.components.commons.site.container client library is included on the page."); 8 } 9 10 let dataLayerEnabled; 11 let dataLayer; 12 let dataLayerName; 13 14 const NS = "cmp"; 15 const IS = "carousel"; 16 17 const selectors = { 18 self: "[data-" + NS + '-is="' + IS + '"]', 19 }; 20 21 /** 22 * Carousel Configuration 23 * 24 * @typedef {Object} CarouselConfig Represents a Carousel configuration 25 * @property {HTMLElement} element The HTMLElement representing the Carousel 26 * @property {*[]} options The Carousel options 27 */ 28 29 /** 30 * Carousel 31 * 32 * @class Carousel 33 * @classdesc An interactive Carousel component for navigating a list of generic items 34 * @param {CarouselConfig} config The Carousel configuration 35 */ 36 function Carousel(config) { 37 const that = this; 38 39 if (config && config.element) { 40 init(config); 41 } 42 43 /** 44 * Initializes the Carousel 45 * 46 * @private 47 * @param {CarouselConfig} config The Carousel configuration 48 */ 49 function init(config) { 50 that._config = config; 51 52 // prevents multiple initialization 53 config.element.removeAttribute("data-" + NS + "-is"); 54 55 cacheElements(config.element); 56 57 that._active = 0; 58 that._paused = false; 59 60 // TODO: This section is only relevant in edit mode and should move to the editor clientLib 61 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 62 /* 63 * Editor message handling: 64 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 65 * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component 66 * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data 67 */ 68 window.CQ = window.CQ || {}; 69 window.CQ.CoreComponents = window.CQ.CoreComponents || {}; 70 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 71 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 72 if ( 73 message?.data.type === "cmp-carousel" 74 && message?.data.id === that._elements.self.dataset["cmpPanelcontainerId"] 75 && message?.data.operation === "navigate" 76 ) navigate(message.data.index); 77 }); 78 } 79 } 80 81 /** 82 * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API 83 * 84 * @private 85 * @param {HTMLElement} wrapper The Carousel wrapper element 86 */ 87 function cacheElements(wrapper) { 88 that._elements = {}; 89 that._elements.self = wrapper; 90 const hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 91 92 for (const hook of hooks) { 93 const capitalized = IS.charAt(0).toUpperCase() + IS.slice(1); 94 const key = hook.dataset[NS + "Hook" + capitalized]; 95 96 if (that._elements[key]) { 97 98 if (!Array.isArray(that._elements[key])) { 99 const tmp = that._elements[key]; 100 that._elements[key] = [tmp]; 101 } 102 that._elements[key].push(hook); 103 } else that._elements[key] = hook; 104 } 105 } 106 107 /** 108 * Refreshes the item markup based on the current {@code Carousel#_active} index 109 * 110 * @private 111 */ 112 function refreshActive() { 113 const items = that._elements["item"]; 114 115 116 if (Array.isArray(items)) { 117 const activeIndex = parseInt(that._active); 118 119 for (let i = 0; i < items.length; i++) { 120 121 if (i === activeIndex) { 122 items[i].classList.add("cmp-carousel__item--active"); 123 items[i].removeAttribute("aria-hidden"); 124 document.querySelector("zin-carousel").goto(that._active); 125 } else { 126 items[i].classList.remove("cmp-carousel__item--active"); 127 items[i].setAttribute("aria-hidden", true); 128 } 129 } 130 } 131 } 132 133 /** 134 * Navigates to the item at the provided index 135 * 136 * @private 137 * @param {Number} index The index of the item to navigate to 138 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 139 */ 140 function navigate(index, keepHash) { 141 if (index < 0 || index > (that._elements["item"].length - 1)) return; 142 143 that._active = index; 144 refreshActive(); 145 146 if (!keepHash && containerUtils) containerUtils.updateUrlHash(that, "item", index); 147 148 if (dataLayerEnabled) { 149 const carouselId = that._elements.self.id; 150 const activeItem = getDataLayerId(that._elements.item[index]); 151 const updatePayload = { component: {} }; 152 updatePayload.component[carouselId] = { shownItems: [activeItem] }; 153 154 const removePayload = { component: {} }; 155 removePayload.component[carouselId] = { shownItems: undefined }; 156 157 dataLayer.push(removePayload); 158 dataLayer.push(updatePayload); 159 } 160 161 } 162 163 } 164 165 /**
166 * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes 167 * 168 * @private 169 * @param {HTMLElement} element The Tabs element to read options data from 170 * @returns {Object} The options read from the component data attributes 171 */ 172 function readData(element) { 173 const data = element.dataset; 174 const options = []; 175 let capitalized = IS; 176 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 177 const reserved = ["is", "hook" + capitalized]; 178 179 for (let key in data) { 180 if (Object.prototype.hasOwnProperty.call(data, key)) { 181 const value = data[key]; 182 183 if (key.indexOf(NS) === 0) { 184 key = key.slice(NS.length); 185 key = key.charAt(0).toLowerCase() + key.substring(1); 186 187 if (reserved.indexOf(key) === -1) { 188 options[key] = value; 189 } 190 } 191 } 192 } 193 return options; 194 } 195 196 /** 197 * Parses the dataLayer string and returns the ID 198 * 199 * @private 200 * @param {HTMLElement} item the accordion item 201 * @returns {String} dataLayerId or undefined 202 */ 203 function getDataLayerId(item) { 204 let id = null; 205 206 if (item) { 207 if (item.dataset.cmpDataLayer) id = Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 208 else id = item.id; 209 } 210 return id; 211 } 212 213 /** 214 * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary. 215 * 216 * @private 217 */ 218 function onDocumentReady() { 219 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 220 if (dataLayerEnabled) { 221 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 222 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 223 } 224 225 const elements = document.querySelectorAll(selectors.self); 226 for (const element of elements) { 227 new Carousel({ element: element, options: readData(element) }); 228 } 229 230 const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 231 const body = document.querySelector("body"); 232 const observer = new MutationObserver(function(mutations) { 233 mutations.forEach(function(mutation) { 234 // needed for IE 235 const nodesArray = [].slice.call(mutation.addedNodes); 236 if (nodesArray.length > 0) { 237 nodesArray.forEach(function(addedNode) { 238 if (addedNode.querySelectorAll) { 239 const elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 240 elementsArray.forEach(function(element) { 241 new Carousel({ element: element, options: readData(element) }); 242 }); 243 } 244 }); 245 } 246 }); 247 }); 248 249 observer.observe(body, { 250 subtree: true, 251 childList: true, 252 characterData: true 253 }); 254 } 255 256 const documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 257 document.addEventListener("DOMContentLoaded", resolve); 258 }); 259 Promise.all([documentReady]).then(onDocumentReady); 260 261 if (containerUtils) { 262 window.addEventListener("load", containerUtils.scrollToAnchor, false); 263 } 264 265 266})();
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.