1/******************************************************************************* 2 * Copyright 2019 Adobe 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17/** 18 * Element.matches() 19 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 20 */ 21if (!Element.prototype.matches) { 22 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 23} 24 25// eslint-disable-next-line valid-jsdoc 26/** 27 * Element.closest() 28 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 29 */ 30if (!Element.prototype.closest) { 31 Element.prototype.closest = function(s) { 32 "use strict"; 33 var el = this; 34 if (!document.documentElement.contains(el)) { 35 return null; 36 } 37 do { 38 if (el.matches(s)) { 39 return el; 40 } 41 el = el.parentElement || el.parentNode; 42 } while (el !== null && el.nodeType === 1); 43 return null; 44 }; 45} 46 47/******************************************************************************* 48 * Copyright 2019 Adobe 49 * 50 * Licensed under the Apache License, Version 2.0 (the "License"); 51 * you may not use this file except in compliance with the License. 52 * You may obtain a copy of the License at 53 * 54 * http://www.apache.org/licenses/LICENSE-2.0 55 * 56 * Unless required by applicable law or agreed to in writing, software 57 * distributed under the License is distributed on an "AS IS" BASIS, 58 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 59 * See the License for the specific language governing permissions and 60 * limitations under the License. 61 ******************************************************************************/ 62(function() { 63 "use strict"; 64 65 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 66 if (!containerUtils) { 67 // eslint-disable-next-line no-console 68 console.warn("Accordion: 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."); 69 } 70 var dataLayerEnabled; 71 var dataLayer; 72 var dataLayerName; 73 var delay = 100; 74 75 var NS = "cmp"; 76 var IS = "accordion"; 77 78 var keyCodes = { 79 ENTER: 13, 80 SPACE: 32, 81 END: 35, 82 HOME: 36, 83 ARROW_LEFT: 37, 84 ARROW_UP: 38, 85 ARROW_RIGHT: 39, 86 ARROW_DOWN: 40 87 }; 88 89 var selectors = { 90 self: "[data-" + NS + '-is="' + IS + '"]' 91 }; 92
93 var cssClasses = { 94 button: { 95 disabled: "cmp-accordion__button--disabled", 96 expanded: "cmp-accordion__button--expanded" 97 }, 98 panel: { 99 hidden: "cmp-accordion__panel--hidden", 100 expanded: "cmp-accordion__panel--expanded" 101 } 102 }; 103 104 var dataAttributes = { 105 item: { 106 expanded: "data-cmp-expanded" 107 } 108 }; 109 110 var properties = { 111 /** 112 * Determines whether a single accordion item is forced to be expanded at a time. 113 * Expanding one item will collapse all others. 114 * 115 * @memberof Accordion 116 * @type {Boolean} 117 * @default false 118 */ 119 "singleExpansion": { 120 "default": false, 121 "transform": function(value) { 122 return !(value === null || typeof value === "undefined"); 123 } 124 } 125 }; 126 127 /** 128 * Accordion Configuration. 129 * 130 * @typedef {Object} AccordionConfig Represents an Accordion configuration 131 * @property {HTMLElement} element The HTMLElement representing the Accordion 132 * @property {Object} options The Accordion options 133 */ 134 135 /** 136 * Accordion. 137 * 138 * @class Accordion 139 * @classdesc An interactive Accordion component for toggling panels of related content 140 * @param {AccordionConfig} config The Accordion configuration 141 */ 142 function Accordion(config) { 143 var that = this; 144 145 if (config && config.element) { 146 init(config); 147 } 148 149 /** 150 * Initializes the Accordion. 151 * 152 * @private 153 * @param {AccordionConfig} config The Accordion configuration 154 */ 155 function init(config) { 156 that._config = config; 157 158 // prevents multiple initialization 159 config.element.removeAttribute("data-" + NS + "-is"); 160 161 setupProperties(config.options); 162 cacheElements(config.element); 163 164 if (that._elements["item"]) { 165 // ensures multiple element types are arrays. 166 that._elements["item"] = Array.isArray(that._elements["item"]) ? that._elements["item"] : [that._elements["item"]]; 167 that._elements["button"] = Array.isArray(that._elements["button"]) ? that._elements["button"] : [that._elements["button"]]; 168 that._elements["panel"] = Array.isArray(that._elements["panel"]) ? that._elements["panel"] : [that._elements["panel"]]; 169 170 if (that._properties.singleExpansion) { 171 var expandedItems = getExpandedItems(); 172 // multiple expanded items annotated, display the last item open. 173 if (expandedItems.length > 1) { 174 toggle(expandedItems.length - 1); 175 } 176 } 177 178 refreshItems(); 179 bindEvents(); 180 scrollToDeepLinkIdInAccordion(); 181 } 182 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 183 /* 184 * Editor message handling: 185 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 186 * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component 187 * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data 188 */ 189 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 190 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 191 if (message.data && message.data.type === "cmp-accordion" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 192 if (message.data.operation === "navigate") { 193 // switch to single expansion mode when navigating in edit mode. 194 var singleExpansion = that._properties.singleExpansion; 195 that._properties.singleExpansion = true; 196 toggle(message.data.index); 197 198 // revert to the configured state. 199 that._properties.singleExpansion = singleExpansion; 200 } 201 } 202 }); 203 } 204 } 205 206 /**
207 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 208 * and scrolls the browser to this element. 209 */ 210 function scrollToDeepLinkIdInAccordion() { 211 if (containerUtils) { 212 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 213 if (deepLinkItemIdx > -1) { 214 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 215 if (deepLinkItem && !deepLinkItem.hasAttribute(dataAttributes.item.expanded)) { 216 // if single expansion: close all accordion items 217 if (that._properties.singleExpansion) { 218 for (var j = 0; j < that._elements["item"].length; j++) { 219 if (that._elements["item"][j].hasAttribute(dataAttributes.item.expanded)) { 220 setItemExpanded(that._elements["item"][j], false, true); 221 } 222 } 223 } 224 // expand the accordion item containing the deep link 225 setItemExpanded(deepLinkItem, true, true); 226 } 227 var hashId = window.location.hash.substring(1); 228 if (hashId) { 229 var hashItem = document.querySelector("[id='" + hashId + "']"); 230 if (hashItem) { 231 hashItem.scrollIntoView(); 232 } 233 } 234 } 235 } 236 } 237 238 /** 239 * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. 240 * 241 * @private 242 * @param {HTMLElement} wrapper The Accordion wrapper element 243 */ 244 function cacheElements(wrapper) { 245 that._elements = {}; 246 that._elements.self = wrapper; 247 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 248 249 for (var i = 0; i < hooks.length; i++) { 250 var hook = hooks[i]; 251 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own accordion elements 252 var capitalized = IS; 253 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 254 var key = hook.dataset[NS + "Hook" + capitalized]; 255 if (that._elements[key]) { 256 if (!Array.isArray(that._elements[key])) { 257 var tmp = that._elements[key]; 258 that._elements[key] = [tmp]; 259 } 260 that._elements[key].push(hook); 261 } else { 262 that._elements[key] = hook; 263 } 264 } 265 } 266 } 267 268 /** 269 * Sets up properties for the Accordion based on the passed options. 270 * 271 * @private 272 * @param {Object} options The Accordion options 273 */ 274 function setupProperties(options) { 275 that._properties = {}; 276 277 for (var key in properties) { 278 if (Object.prototype.hasOwnProperty.call(properties, key)) { 279 var property = properties[key]; 280 var value = null; 281 282 if (options && options[key] != null) { 283 value = options[key]; 284 285 // transform the provided option 286 if (property && typeof property.transform === "function") { 287 value = property.transform(value); 288 } 289 } 290 291 if (value === null) { 292 // value still null, take the property default 293 value = properties[key]["default"]; 294 } 295 296 that._properties[key] = value; 297 } 298 } 299 } 300 301 /** 302 * Binds Accordion event handling. 303 * 304 * @private 305 */ 306 function bindEvents() { 307 window.addEventListener("hashchange", scrollToDeepLinkIdInAccordion, false); 308 var buttons = that._elements["button"]; 309 if (buttons) { 310 for (var i = 0; i < buttons.length; i++) { 311 (function(index) { 312 buttons[i].addEventListener("click", function(event) { 313 toggle(index); 314 focusButton(index); 315 }); 316 buttons[i].addEventListener("keydown", function(event) { 317 onButtonKeyDown(event, index); 318 }); 319 })(i); 320 } 321 } 322 } 323 324 /** 325 * Handles button keydown events. 326 * 327 * @private 328 * @param {Object} event The keydown event 329 * @param {Number} index The index of the button triggering the event 330 */ 331 function onButtonKeyDown(event, index) { 332 var lastIndex = that._elements["button"].length - 1; 333 334 switch (event.keyCode) { 335 case keyCodes.ARROW_LEFT: 336 case keyCodes.ARROW_UP: 337 event.preventDefault(); 338 if (index > 0) { 339 focusButton(index - 1); 340 } 341 break; 342 case keyCodes.ARROW_RIGHT: 343 case keyCodes.ARROW_DOWN: 344 event.preventDefault(); 345 if (index < lastIndex) { 346 focusButton(index + 1); 347 } 348 break; 349 case keyCodes.HOME: 350 event.preventDefault(); 351 focusButton(0); 352 break; 353 case keyCodes.END: 354 event.preventDefault(); 355 focusButton(lastIndex); 356 break;
357 case keyCodes.ENTER: 358 case keyCodes.SPACE: 359 event.preventDefault(); 360 toggle(index); 361 focusButton(index); 362 break; 363 default: 364 return; 365 } 366 } 367 368 /** 369 * General handler for toggle of an item. 370 * 371 * @private 372 * @param {Number} index The index of the item to toggle 373 */ 374 function toggle(index) { 375 var item = that._elements["item"][index]; 376 if (item) { 377 if (that._properties.singleExpansion) { 378 // ensure only a single item is expanded if single expansion is enabled. 379 for (var i = 0; i < that._elements["item"].length; i++) { 380 if (that._elements["item"][i] !== item) { 381 var expanded = getItemExpanded(that._elements["item"][i]); 382 if (expanded) { 383 setItemExpanded(that._elements["item"][i], false); 384 } 385 } 386 } 387 } 388 setItemExpanded(item, !getItemExpanded(item)); 389 390 if (dataLayerEnabled) { 391 var accordionId = that._elements.self.id; 392 var expandedItems = getExpandedItems() 393 .map(function(item) { 394 return getDataLayerId(item); 395 }); 396 397 var uploadPayload = { component: {} }; 398 uploadPayload.component[accordionId] = { shownItems: expandedItems }; 399 400 var removePayload = { component: {} }; 401 removePayload.component[accordionId] = { shownItems: undefined }; 402 403 dataLayer.push(removePayload); 404 dataLayer.push(uploadPayload); 405 } 406 } 407 } 408 409 /** 410 * Sets an item's expanded state based on the provided flag and refreshes its internals. 411 * 412 * @private 413 * @param {HTMLElement} item The item to mark as expanded, or not expanded 414 * @param {Boolean} expanded true to mark the item expanded, false otherwise 415 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 416 */ 417 function setItemExpanded(item, expanded, keepHash) { 418 if (expanded) { 419 item.setAttribute(dataAttributes.item.expanded, ""); 420 var index = that._elements["item"].indexOf(item); 421 if (!keepHash && containerUtils) { 422 containerUtils.updateUrlHash(that, "item", index); 423 } 424 if (dataLayerEnabled) { 425 dataLayer.push({ 426 event: "cmp:show", 427 eventInfo: { 428 path: "component." + getDataLayerId(item) 429 } 430 }); 431 } 432 433 } else { 434 item.removeAttribute(dataAttributes.item.expanded); 435 if (!keepHash && containerUtils) { 436 containerUtils.removeUrlHash(); 437 } 438 if (dataLayerEnabled) { 439 dataLayer.push({ 440 event: "cmp:hide", 441 eventInfo: { 442 path: "component." + getDataLayerId(item) 443 } 444 }); 445 } 446 } 447 refreshItem(item); 448 } 449 450 /** 451 * Gets an item's expanded state. 452 * 453 * @private 454 * @param {HTMLElement} item The item for checking its expanded state 455 * @returns {Boolean} true if the item is expanded, false otherwise 456 */ 457 function getItemExpanded(item) { 458 return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; 459 } 460 461 /** 462 * Refreshes an item based on its expanded state. 463 * 464 * @private 465 * @param {HTMLElement} item The item to refresh 466 */ 467 function refreshItem(item) { 468 var expanded = getItemExpanded(item); 469 if (expanded) { 470 expandItem(item); 471 } else { 472 collapseItem(item); 473 } 474 } 475 476 /** 477 * Refreshes all items based on their expanded state. 478 * 479 * @private 480 */ 481 function refreshItems() { 482 for (var i = 0; i < that._elements["item"].length; i++) { 483 refreshItem(that._elements["item"][i]); 484 } 485 } 486 487 /** 488 * Returns all expanded items. 489 * 490 * @private 491 * @returns {HTMLElement[]} The expanded items 492 */ 493 function getExpandedItems() { 494 var expandedItems = []; 495 496 for (var i = 0; i < that._elements["item"].length; i++) { 497 var item = that._elements["item"][i]; 498 var expanded = getItemExpanded(item); 499 if (expanded) { 500 expandedItems.push(item); 501 } 502 } 503 504 return expandedItems; 505 } 506 507 /** 508 * Annotates the item and its internals with 509 * the necessary style and accessibility attributes to indicate it is expanded. 510 * 511 * @private 512 * @param {HTMLElement} item The item to annotate as expanded 513 */ 514 function expandItem(item) { 515 var index = that._elements["item"].indexOf(item); 516 if (index > -1) { 517 var button = that._elements["button"][index]; 518 var panel = that._elements["panel"][index];
519 button.classList.add(cssClasses.button.expanded); 520 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 521 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 522 setTimeout(function() { 523 button.setAttribute("aria-expanded", true); 524 }, delay); 525 panel.classList.add(cssClasses.panel.expanded); 526 panel.classList.remove(cssClasses.panel.hidden); 527 panel.setAttribute("aria-hidden", false); 528 } 529 } 530 531 /** 532 * Annotates the item and its internals with 533 * the necessary style and accessibility attributes to indicate it is not expanded. 534 * 535 * @private 536 * @param {HTMLElement} item The item to annotate as not expanded 537 */ 538 function collapseItem(item) { 539 var index = that._elements["item"].indexOf(item); 540 if (index > -1) { 541 var button = that._elements["button"][index]; 542 var panel = that._elements["panel"][index]; 543 button.classList.remove(cssClasses.button.expanded); 544 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 545 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 546 setTimeout(function() { 547 button.setAttribute("aria-expanded", false); 548 }, delay); 549 panel.classList.add(cssClasses.panel.hidden); 550 panel.classList.remove(cssClasses.panel.expanded); 551 panel.setAttribute("aria-hidden", true); 552 } 553 } 554 555 /** 556 * Focuses the button at the provided index. 557 * 558 * @private 559 * @param {Number} index The index of the button to focus 560 */ 561 function focusButton(index) { 562 var button = that._elements["button"][index]; 563 button.focus(); 564 } 565 } 566 567 /** 568 * Reads options data from the Accordion wrapper element, defined via {@code data-cmp-*} data attributes. 569 * 570 * @private 571 * @param {HTMLElement} element The Accordion element to read options data from 572 * @returns {Object} The options read from the component data attributes 573 */ 574 function readData(element) { 575 var data = element.dataset; 576 var options = []; 577 var capitalized = IS; 578 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 579 var reserved = ["is", "hook" + capitalized]; 580 581 for (var key in data) { 582 if (Object.prototype.hasOwnProperty.call(data, key)) { 583 var value = data[key]; 584 585 if (key.indexOf(NS) === 0) { 586 key = key.slice(NS.length); 587 key = key.charAt(0).toLowerCase() + key.substring(1); 588 589 if (reserved.indexOf(key) === -1) { 590 options[key] = value; 591 } 592 } 593 } 594 } 595 596 return options; 597 } 598 599 /** 600 * Parses the dataLayer string and returns the ID 601 * 602 * @private 603 * @param {HTMLElement} item the accordion item 604 * @returns {String} dataLayerId or undefined 605 */ 606 function getDataLayerId(item) { 607 if (item) { 608 if (item.dataset.cmpDataLayer) { 609 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 610 } else { 611 return item.id; 612 } 613 } 614 return null; 615 } 616 617 /** 618 * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. 619 * 620 * @private 621 */ 622 function onDocumentReady() { 623 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 624 if (dataLayerEnabled) { 625 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 626 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 627 } 628 629 var elements = document.querySelectorAll(selectors.self); 630 for (var i = 0; i < elements.length; i++) { 631 new Accordion({ element: elements[i], options: readData(elements[i]) }); 632 } 633 634 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 635 var body = document.querySelector("body"); 636 var observer = new MutationObserver(function(mutations) { 637 mutations.forEach(function(mutation) { 638 // needed for IE 639 var nodesArray = [].slice.call(mutation.addedNodes); 640 if (nodesArray.length > 0) { 641 nodesArray.forEach(function(addedNode) { 642 if (addedNode.querySelectorAll) { 643 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 644 elementsArray.forEach(function(element) { 645 new Accordion({ element: element, options: readData(element) }); 646 }); 647 } 648 }); 649 } 650 }); 651 }); 652 653 observer.observe(body, { 654 subtree: true, 655 childList: true, 656 characterData: true 657 }); 658 } 659 660 if (document.readyState !== "loading") { 661 onDocumentReady(); 662 } else { 663 document.addEventListener("DOMContentLoaded", onDocumentReady); 664 } 665 666 if (containerUtils) { 667 window.addEventListener("load", containerUtils.scrollToAnchor, false); 668 } 669 670}()); 671 672 673/******************************************************************************* 674 * Copyright 2018 Adobe 675 * 676 * Licensed under the Apache License, Version 2.0 (the "License");
677 * you may not use this file except in compliance with the License. 678 * You may obtain a copy of the License at 679 * 680 * http://www.apache.org/licenses/LICENSE2.0 681 * 682 * Unless required by applicable law or agreed to in writing, software 683 * distributed under the License is distributed on an "AS IS" BASIS, 684 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 685 * See the License for the specific language governing permissions and 686 * limitations under the License. 687 ******************************************************************************/ 688 689/** 690 * Element.matches() 691 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 692 */ 693if (!Element.prototype.matches) { 694 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 695} 696 697// eslint-disable-next-line valid-jsdoc 698/** 699 * Element.closest() 700 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 701 */ 702if (!Element.prototype.closest) { 703 Element.prototype.closest = function(s) { 704 "use strict"; 705 var el = this; 706 if (!document.documentElement.contains(el)) { 707 return null; 708 } 709 do { 710 if (el.matches(s)) { 711 return el; 712 } 713 el = el.parentElement || el.parentNode; 714 } while (el !== null && el.nodeType === 1); 715 return null; 716 }; 717} 718 719/******************************************************************************* 720 * Copyright 2018 Adobe 721 * 722 * Licensed under the Apache License, Version 2.0 (the "License"); 723 * you may not use this file except in compliance with the License. 724 * You may obtain a copy of the License at 725 * 726 * http://www.apache.org/licenses/LICENSE-2.0 727 * 728 * Unless required by applicable law or agreed to in writing, software 729 * distributed under the License is distributed on an "AS IS" BASIS, 730 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 731 * See the License for the specific language governing permissions and 732 * limitations under the License. 733 ******************************************************************************/ 734/* global 735 CQ 736 */ 737(function() { 738 "use strict"; 739 740 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 741 if (!containerUtils) { 742 // eslint-disable-next-line no-console 743 console.warn("Tabs: 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."); 744 } 745 var dataLayerEnabled; 746 var dataLayer; 747 var dataLayerName; 748 749 var NS = "cmp"; 750 var IS = "tabs"; 751 752 var keyCodes = { 753 END: 35, 754 HOME: 36, 755 ARROW_LEFT: 37, 756 ARROW_UP: 38, 757 ARROW_RIGHT: 39, 758 ARROW_DOWN: 40 759 }; 760 761 var selectors = { 762 self: "[data-" + NS + '-is="' + IS + '"]', 763 active: { 764 tab: "cmp-tabs__tab--active", 765 tabpanel: "cmp-tabs__tabpanel--active" 766 } 767 }; 768 769 /** 770 * Tabs Configuration 771 * 772 * @typedef {Object} TabsConfig Represents a Tabs configuration 773 * @property {HTMLElement} element The HTMLElement representing the Tabs 774 * @property {Object} options The Tabs options 775 */ 776 777 /** 778 * Tabs 779 * 780 * @class Tabs 781 * @classdesc An interactive Tabs component for navigating a list of tabs 782 * @param {TabsConfig} config The Tabs configuration 783 */ 784 function Tabs(config) { 785 var that = this; 786 787 if (config && config.element) { 788 init(config); 789 } 790 791 /** 792 * Initializes the Tabs 793 * 794 * @private 795 * @param {TabsConfig} config The Tabs configuration 796 */ 797 function init(config) { 798 that._config = config; 799 800 // prevents multiple initialization 801 config.element.removeAttribute("data-" + NS + "-is"); 802 803 cacheElements(config.element); 804 that._active = getActiveIndex(that._elements["tab"]); 805 806 if (that._elements.tabpanel) { 807 refreshActive(); 808 bindEvents(); 809 scrollToDeepLinkIdInTabs(); 810 } 811 812 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 813 /* 814 * Editor message handling: 815 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
816 * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component 817 * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data 818 */ 819 CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 820 CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 821 if (message.data && message.data.type === "cmp-tabs" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 822 if (message.data.operation === "navigate") { 823 navigate(message.data.index); 824 } 825 } 826 }); 827 } 828 } 829 830 /** 831 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 832 * and scrolls the browser to this element. 833 */ 834 function scrollToDeepLinkIdInTabs() { 835 if (containerUtils) { 836 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "tab", "tabpanel"); 837 if (deepLinkItemIdx > -1) { 838 var deepLinkItem = that._elements["tab"][deepLinkItemIdx]; 839 if (deepLinkItem && that._elements["tab"][that._active].id !== deepLinkItem.id) { 840 navigateAndFocusTab(deepLinkItemIdx, true); 841 } 842 var hashId = window.location.hash.substring(1); 843 if (hashId) { 844 var hashItem = document.querySelector("[id='" + hashId + "']"); 845 if (hashItem) { 846 hashItem.scrollIntoView(); 847 } 848 } 849 } 850 } 851 } 852 853 /** 854 * Returns the index of the active tab, if no tab is active returns 0 855 * 856 * @param {Array} tabs Tab elements 857 * @returns {Number} Index of the active tab, 0 if none is active 858 */ 859 function getActiveIndex(tabs) { 860 if (tabs) { 861 for (var i = 0; i < tabs.length; i++) { 862 if (tabs[i].classList.contains(selectors.active.tab)) { 863 return i; 864 } 865 } 866 } 867 return 0; 868 } 869 870 /** 871 * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API 872 * 873 * @private 874 * @param {HTMLElement} wrapper The Tabs wrapper element 875 */ 876 function cacheElements(wrapper) { 877 that._elements = {}; 878 that._elements.self = wrapper; 879 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 880 881 for (var i = 0; i < hooks.length; i++) { 882 var hook = hooks[i]; 883 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own tab elements 884 var capitalized = IS; 885 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 886 var key = hook.dataset[NS + "Hook" + capitalized]; 887 if (that._elements[key]) { 888 if (!Array.isArray(that._elements[key])) { 889 var tmp = that._elements[key]; 890 that._elements[key] = [tmp]; 891 } 892 that._elements[key].push(hook); 893 } else { 894 that._elements[key] = hook; 895 } 896 } 897 } 898 } 899 900 /** 901 * Binds Tabs event handling 902 * 903 * @private 904 */ 905 function bindEvents() { 906 window.addEventListener("hashchange", scrollToDeepLinkIdInTabs, false); 907 var tabs = that._elements["tab"]; 908 if (tabs) { 909 for (var i = 0; i < tabs.length; i++) { 910 (function(index) { 911 tabs[i].addEventListener("click", function(event) { 912 navigateAndFocusTab(index); 913 }); 914 tabs[i].addEventListener("keydown", function(event) { 915 onKeyDown(event); 916 }); 917 })(i); 918 } 919 } 920 } 921 922 /** 923 * Handles tab keydown events 924 * 925 * @private 926 * @param {Object} event The keydown event 927 */ 928 function onKeyDown(event) { 929 var index = that._active; 930 var lastIndex = that._elements["tab"].length - 1; 931 932 switch (event.keyCode) { 933 case keyCodes.ARROW_LEFT: 934 case keyCodes.ARROW_UP: 935 event.preventDefault(); 936 if (index > 0) { 937 navigateAndFocusTab(index - 1); 938 } 939 break; 940 case keyCodes.ARROW_RIGHT: 941 case keyCodes.ARROW_DOWN: 942 event.preventDefault(); 943 if (index < lastIndex) { 944 navigateAndFocusTab(index + 1); 945 } 946 break; 947 case keyCodes.HOME: 948 event.preventDefault(); 949 navigateAndFocusTab(0); 950 break; 951 case keyCodes.END: 952 event.preventDefault(); 953 navigateAndFocusTab(lastIndex); 954 break; 955 default: 956 return; 957 } 958 } 959 960 /** 961 * Refreshes the tab markup based on the current {@code Tabs#_active} index 962 * 963 * @private 964 */ 965 function refreshActive() { 966 var tabpanels = that._elements["tabpanel"]; 967 var tabs = that._elements["tab"]; 968 969 if (tabpanels) { 970 if (Array.isArray(tabpanels)) { 971 for (var i = 0; i < tabpanels.length; i++) { 972 if (i === parseInt(that._active)) { 973 tabpanels[i].classList.add(selectors.active.tabpanel); 974 tabpanels[i].removeAttribute("aria-hidden"); 975 tabs[i].classList.add(selectors.active.tab); 976 tabs[i].setAttribute("aria-selected", true); 977 tabs[i].setAttribute("tabindex", "0"); 978 } else { 979 tabpanels[i].classList.remove(selectors.active.tabpanel); 980 tabpanels[i].setAttribute("aria-hidden", true); 981 tabs[i].classList.remove(selectors.active.tab); 982 tabs[i].setAttribute("aria-selected", false); 983 tabs[i].setAttribute("tabindex", "-1"); 984 } 985 } 986 } else { 987 // only one tab 988 tabpanels.classList.add(selectors.active.tabpanel); 989 tabs.classList.add(selectors.active.tab); 990 } 991 } 992 } 993 994 /** 995 * Focuses the element and prevents scrolling the element into view 996 * 997 * @param {HTMLElement} element Element to focus 998 */ 999 function focusWithoutScroll(element) { 1000 var x = window.scrollX || window.pageXOffset; 1001 var y = window.scrollY || window.pageYOffset; 1002 element.focus(); 1003 window.scrollTo(x, y); 1004 } 1005 1006 /** 1007 * Navigates to the tab at the provided index 1008 * 1009 * @private 1010 * @param {Number} index The index of the tab to navigate to 1011 */ 1012 function navigate(index) { 1013 that._active = index; 1014 refreshActive(); 1015 } 1016 1017 /** 1018 * Navigates to the item at the provided index and ensures the active tab gains focus 1019 * 1020 * @private 1021 * @param {Number} index The index of the item to navigate to 1022 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1023 */ 1024 function navigateAndFocusTab(index, keepHash) { 1025 var exActive = that._active; 1026 if (!keepHash && containerUtils) { 1027 containerUtils.updateUrlHash(that, "tab", index); 1028 } 1029 navigate(index); 1030 focusWithoutScroll(that._elements["tab"][index]); 1031 1032 if (dataLayerEnabled) { 1033 1034 var activeItem = getDataLayerId(that._elements.tabpanel[index]); 1035 var exActiveItem = getDataLayerId(that._elements.tabpanel[exActive]); 1036 1037 dataLayer.push({ 1038 event: "cmp:show", 1039 eventInfo: { 1040 path: "component." + activeItem 1041 } 1042 }); 1043 1044 dataLayer.push({ 1045 event: "cmp:hide", 1046 eventInfo: { 1047 path: "component." + exActiveItem 1048 } 1049 }); 1050 1051 var tabsId = that._elements.self.id; 1052 var uploadPayload = { component: {} }; 1053 uploadPayload.component[tabsId] = { shownItems: [activeItem] }; 1054 1055 var removePayload = { component: {} }; 1056 removePayload.component[tabsId] = { shownItems: undefined }; 1057 1058 dataLayer.push(removePayload); 1059 dataLayer.push(uploadPayload); 1060 } 1061 } 1062 } 1063 1064 /**
1065 * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes 1066 * 1067 * @private 1068 * @param {HTMLElement} element The Tabs element to read options data from 1069 * @returns {Object} The options read from the component data attributes 1070 */ 1071 function readData(element) { 1072 var data = element.dataset; 1073 var options = []; 1074 var capitalized = IS; 1075 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1076 var reserved = ["is", "hook" + capitalized]; 1077 1078 for (var key in data) { 1079 if (Object.prototype.hasOwnProperty.call(data, key)) { 1080 var value = data[key]; 1081 1082 if (key.indexOf(NS) === 0) { 1083 key = key.slice(NS.length); 1084 key = key.charAt(0).toLowerCase() + key.substring(1); 1085 1086 if (reserved.indexOf(key) === -1) { 1087 options[key] = value; 1088 } 1089 } 1090 } 1091 } 1092 1093 return options; 1094 } 1095 1096 /** 1097 * Parses the dataLayer string and returns the ID 1098 * 1099 * @private 1100 * @param {HTMLElement} item the accordion item 1101 * @returns {String} dataLayerId or undefined 1102 */ 1103 function getDataLayerId(item) { 1104 if (item) { 1105 if (item.dataset.cmpDataLayer) { 1106 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 1107 } else { 1108 return item.id; 1109 } 1110 } 1111 return null; 1112 } 1113 1114 /** 1115 * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. 1116 * 1117 * @private 1118 */ 1119 function onDocumentReady() { 1120 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 1121 if (dataLayerEnabled) { 1122 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 1123 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 1124 } 1125 1126 var elements = document.querySelectorAll(selectors.self); 1127 for (var i = 0; i < elements.length; i++) { 1128 new Tabs({ element: elements[i], options: readData(elements[i]) }); 1129 } 1130 1131 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 1132 var body = document.querySelector("body"); 1133 var observer = new MutationObserver(function(mutations) { 1134 mutations.forEach(function(mutation) { 1135 // needed for IE 1136 var nodesArray = [].slice.call(mutation.addedNodes); 1137 if (nodesArray.length > 0) { 1138 nodesArray.forEach(function(addedNode) { 1139 if (addedNode.querySelectorAll) { 1140 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 1141 elementsArray.forEach(function(element) { 1142 new Tabs({ element: element, options: readData(element) }); 1143 }); 1144 } 1145 }); 1146 } 1147 }); 1148 }); 1149 1150 observer.observe(body, { 1151 subtree: true, 1152 childList: true, 1153 characterData: true 1154 }); 1155 } 1156 1157 if (document.readyState !== "loading") { 1158 onDocumentReady(); 1159 } else { 1160 document.addEventListener("DOMContentLoaded", onDocumentReady); 1161 } 1162 1163 if (containerUtils) { 1164 window.addEventListener("load", containerUtils.scrollToAnchor, false); 1165 } 1166 1167}()); 1168 1169 1170/******************************************************************************* 1171 * Copyright 2022 Adobe 1172 * 1173 * Licensed under the Apache License, Version 2.0 (the "License"); 1174 * you may not use this file except in compliance with the License. 1175 * You may obtain a copy of the License at 1176 * 1177 * http://www.apache.org/licenses/LICENSE-2.0 1178 * 1179 * Unless required by applicable law or agreed to in writing, software 1180 * distributed under the License is distributed on an "AS IS" BASIS, 1181 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1182 * See the License for the specific language governing permissions and 1183 * limitations under the License. 1184 ******************************************************************************/ 1185(function(document) { 1186 "use strict"; 1187 1188 window.CMP = window.CMP || {}; 1189 window.CMP.utils = (function() { 1190 var NS = "cmp"; 1191 1192 /**
1193 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 1194 * 1195 * @param {HTMLElement} element The component element to read options data from 1196 * @param {String} is The component identifier 1197 * @returns {String[]} The options read from the component data attributes 1198 */ 1199 var readData = function(element, is) { 1200 var data = element.dataset; 1201 var options = []; 1202 var capitalized = is; 1203 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1204 var reserved = ["is", "hook" + capitalized]; 1205 1206 for (var key in data) { 1207 if (Object.prototype.hasOwnProperty.call(data, key)) { 1208 var value = data[key]; 1209 1210 if (key.indexOf(NS) === 0) { 1211 key = key.slice(NS.length); 1212 key = key.charAt(0).toLowerCase() + key.substring(1); 1213 1214 if (reserved.indexOf(key) === -1) { 1215 options[key] = value; 1216 } 1217 } 1218 } 1219 } 1220 return options; 1221 }; 1222 1223 /** 1224 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 1225 * @param {String[]} options the options to transform 1226 * @param {Object} properties object of properties of property functions 1227 * @returns {Object} transformed properties 1228 */ 1229 var setupProperties = function(options, properties) { 1230 var transformedProperties = {}; 1231 1232 for (var key in properties) { 1233 if (Object.prototype.hasOwnProperty.call(properties, key)) { 1234 var property = properties[key]; 1235 if (options && options[key] != null) { 1236 if (property && typeof property.transform === "function") { 1237 transformedProperties[key] = property.transform(options[key]); 1238 } else { 1239 transformedProperties[key] = options[key]; 1240 } 1241 } else { 1242 transformedProperties[key] = properties[key]["default"]; 1243 } 1244 } 1245 } 1246 return transformedProperties; 1247 }; 1248 1249 1250 return { 1251 readData: readData, 1252 setupProperties: setupProperties 1253 }; 1254 }()); 1255}(window.document)); 1256 1257/******************************************************************************* 1258 * Copyright 2018 Adobe 1259 * 1260 * Licensed under the Apache License, Version 2.0 (the "License"); 1261 * you may not use this file except in compliance with the License. 1262 * You may obtain a copy of the License at 1263 * 1264 * http://www.apache.org/licenses/LICENSE-2.0 1265 * 1266 * Unless required by applicable law or agreed to in writing, software 1267 * distributed under the License is distributed on an "AS IS" BASIS, 1268 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1269 * See the License for the specific language governing permissions and 1270 * limitations under the License. 1271 ******************************************************************************/ 1272(function() { 1273 "use strict"; 1274 1275 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 1276 if (!containerUtils) { 1277 // eslint-disable-next-line no-console 1278 console.warn("Tabs: 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."); 1279 } 1280 var dataLayerEnabled; 1281 var dataLayer; 1282 var dataLayerName; 1283 1284 var NS = "cmp"; 1285 var IS = "carousel"; 1286 1287 var keyCodes = { 1288 SPACE: 32, 1289 END: 35, 1290 HOME: 36, 1291 ARROW_LEFT: 37, 1292 ARROW_UP: 38, 1293 ARROW_RIGHT: 39, 1294 ARROW_DOWN: 40 1295 }; 1296 1297 var selectors = { 1298 self: "[data-" + NS + '-is="' + IS + '"]' 1299 }; 1300 1301 var properties = { 1302 /** 1303 * Determines whether the Carousel will automatically transition between slides 1304 * 1305 * @memberof Carousel
1306 * @type {Boolean} 1307 * @default false 1308 */ 1309 "autoplay": { 1310 "default": false, 1311 "transform": function(value) { 1312 return !(value === null || typeof value === "undefined"); 1313 } 1314 }, 1315 /** 1316 * Duration (in milliseconds) before automatically transitioning to the next slide 1317 * 1318 * @memberof Carousel 1319 * @type {Number} 1320 * @default 5000 1321 */ 1322 "delay": { 1323 "default": 5000, 1324 "transform": function(value) { 1325 value = parseFloat(value); 1326 return !isNaN(value) ? value : null; 1327 } 1328 }, 1329 /** 1330 * Determines whether automatic pause on hovering the carousel is disabled 1331 * 1332 * @memberof Carousel 1333 * @type {Boolean} 1334 * @default false 1335 */ 1336 "autopauseDisabled": { 1337 "default": false, 1338 "transform": function(value) { 1339 return !(value === null || typeof value === "undefined"); 1340 } 1341 } 1342 }; 1343 1344 /** 1345 * Carousel Configuration 1346 * 1347 * @typedef {Object} CarouselConfig Represents a Carousel configuration 1348 * @property {HTMLElement} element The HTMLElement representing the Carousel 1349 * @property {*[]} options The Carousel options 1350 */ 1351 1352 /** 1353 * Carousel 1354 * 1355 * @class Carousel 1356 * @classdesc An interactive Carousel component for navigating a list of generic items 1357 * @param {CarouselConfig} config The Carousel configuration 1358 */ 1359 function Carousel(config) { 1360 var that = this; 1361 1362 if (config && config.element) { 1363 init(config); 1364 } 1365 1366 /** 1367 * Initializes the Carousel 1368 * 1369 * @private 1370 * @param {CarouselConfig} config The Carousel configuration 1371 */ 1372 function init(config) { 1373 that._config = config; 1374 1375 // prevents multiple initialization 1376 config.element.removeAttribute("data-" + NS + "-is"); 1377 1378 setupProperties(config.options); 1379 cacheElements(config.element); 1380 1381 that._active = 0; 1382 that._paused = false; 1383 1384 if (that._elements.item) { 1385 initializeActive(); 1386 bindEvents(); 1387 resetAutoplayInterval(); 1388 refreshPlayPauseActions(); 1389 scrollToDeepLinkIdInCarousel(); 1390 } 1391 1392 // TODO: This section is only relevant in edit mode and should move to the editor clientLib 1393 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 1394 /* 1395 * Editor message handling: 1396 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 1397 * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component 1398 * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data 1399 */ 1400 window.CQ = window.CQ || {}; 1401 window.CQ.CoreComponents = window.CQ.CoreComponents || {}; 1402 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 1403 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 1404 if (message.data && message.data.type === "cmp-carousel" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 1405 if (message.data.operation === "navigate") { 1406 navigate(message.data.index); 1407 } 1408 } 1409 }); 1410 } 1411 } 1412 1413 /** 1414 * Displays the slide containing the element that corresponds to the deep link in the URI fragment 1415 * and scrolls the browser to this element. 1416 */ 1417 function scrollToDeepLinkIdInCarousel() { 1418 if (containerUtils) { 1419 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 1420 if (deepLinkItemIdx > -1) { 1421 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 1422 if (deepLinkItem && that._elements["item"][that._active].id !== deepLinkItem.id) { 1423 navigateAndFocusIndicator(deepLinkItemIdx, true); 1424 // pause the carousel auto-rotation 1425 pause(); 1426 } 1427 var hashId = window.location.hash.substring(1); 1428 if (hashId) { 1429 var hashItem = document.querySelector("[id='" + hashId + "']"); 1430 if (hashItem) { 1431 hashItem.scrollIntoView(); 1432 } 1433 } 1434 } 1435 } 1436 } 1437 1438 /** 1439 * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API 1440 * 1441 * @private 1442 * @param {HTMLElement} wrapper The Carousel wrapper element 1443 */ 1444 function cacheElements(wrapper) { 1445 that._elements = {}; 1446 that._elements.self = wrapper; 1447 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 1448 1449 for (var i = 0; i < hooks.length; i++) { 1450 var hook = hooks[i]; 1451 var capitalized = IS; 1452 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1453 var key = hook.dataset[NS + "Hook" + capitalized]; 1454 if (that._elements[key]) { 1455 if (!Array.isArray(that._elements[key])) { 1456 var tmp = that._elements[key]; 1457 that._elements[key] = [tmp]; 1458 } 1459 that._elements[key].push(hook); 1460 } else { 1461 that._elements[key] = hook; 1462 } 1463 } 1464 } 1465 1466 /** 1467 * Sets up properties for the Carousel based on the passed options. 1468 * 1469 * @private 1470 * @param {Object} options The Carousel options 1471 */ 1472 function setupProperties(options) { 1473 that._properties = {}; 1474 1475 for (var key in properties) { 1476 if (Object.prototype.hasOwnProperty.call(properties, key)) { 1477 var property = properties[key]; 1478 var value = null; 1479 1480 if (options && options[key] != null) { 1481 value = options[key]; 1482 1483 // transform the provided option 1484 if (property && typeof property.transform === "function") { 1485 value = property.transform(value); 1486 } 1487 } 1488 1489 if (value === null) { 1490 // value still null, take the property default 1491 value = properties[key]["default"]; 1492 } 1493 1494 that._properties[key] = value; 1495 } 1496 } 1497 } 1498 1499 /** 1500 * Binds Carousel event handling 1501 * 1502 * @private 1503 */ 1504 function bindEvents() { 1505 window.addEventListener("hashchange", scrollToDeepLinkIdInCarousel, false); 1506 if (that._elements["previous"]) { 1507 that._elements["previous"].addEventListener("click", function() { 1508 var index = getPreviousIndex(); 1509 navigate(index); 1510 if (dataLayerEnabled) { 1511 dataLayer.push({ 1512 event: "cmp:show", 1513 eventInfo: { 1514 path: "component." + getDataLayerId(that._elements.item[index]) 1515 } 1516 }); 1517 } 1518 }); 1519 } 1520 1521 if (that._elements["next"]) { 1522 that._elements["next"].addEventListener("click", function() { 1523 var index = getNextIndex(); 1524 navigate(index); 1525 if (dataLayerEnabled) { 1526 dataLayer.push({ 1527 event: "cmp:show", 1528 eventInfo: { 1529 path: "component." + getDataLayerId(that._elements.item[index]) 1530 } 1531 }); 1532 } 1533 }); 1534 } 1535 1536 var indicators = that._elements["indicator"]; 1537 if (indicators) { 1538 for (var i = 0; i < indicators.length; i++) { 1539 (function(index) { 1540 indicators[i].addEventListener("click", function(event) { 1541 navigateAndFocusIndicator(index); 1542 // pause the carousel auto-rotation 1543 pause(); 1544 }); 1545 })(i); 1546 } 1547 } 1548 1549 if (that._elements["pause"]) { 1550 if (that._properties.autoplay) { 1551 that._elements["pause"].addEventListener("click", onPauseClick); 1552 } 1553 } 1554 1555 if (that._elements["play"]) { 1556 if (that._properties.autoplay) { 1557 that._elements["play"].addEventListener("click", onPlayClick); 1558 } 1559 } 1560 1561 that._elements.self.addEventListener("keydown", onKeyDown); 1562 1563 if (!that._properties.autopauseDisabled) { 1564 that._elements.self.addEventListener("mouseenter", onMouseEnter); 1565 that._elements.self.addEventListener("mouseleave", onMouseLeave); 1566 } 1567 1568 // for accessibility we pause animation when a element get focused 1569 var items = that._elements["item"]; 1570 if (items) { 1571 for (var j = 0; j < items.length; j++) { 1572 items[j].addEventListener("focusin", onMouseEnter); 1573 items[j].addEventListener("focusout", onMouseLeave); 1574 } 1575 } 1576 } 1577 1578 /**
1579 * Handles carousel keydown events 1580 * 1581 * @private 1582 * @param {Object} event The keydown event 1583 */ 1584 function onKeyDown(event) { 1585 var index = that._active; 1586 var lastIndex = that._elements["indicator"].length - 1; 1587 1588 switch (event.keyCode) { 1589 case keyCodes.ARROW_LEFT: 1590 case keyCodes.ARROW_UP: 1591 event.preventDefault(); 1592 if (index > 0) { 1593 navigateAndFocusIndicator(index - 1); 1594 } 1595 break; 1596 case keyCodes.ARROW_RIGHT: 1597 case keyCodes.ARROW_DOWN: 1598 event.preventDefault(); 1599 if (index < lastIndex) { 1600 navigateAndFocusIndicator(index + 1); 1601 } 1602 break; 1603 case keyCodes.HOME: 1604 event.preventDefault(); 1605 navigateAndFocusIndicator(0); 1606 break; 1607 case keyCodes.END: 1608 event.preventDefault(); 1609 navigateAndFocusIndicator(lastIndex); 1610 break; 1611 case keyCodes.SPACE: 1612 if (that._properties.autoplay && (event.target !== that._elements["previous"] && event.target !== that._elements["next"])) { 1613 event.preventDefault(); 1614 if (!that._paused) { 1615 pause(); 1616 } else { 1617 play(); 1618 } 1619 } 1620 if (event.target === that._elements["pause"]) { 1621 that._elements["play"].focus(); 1622 } 1623 if (event.target === that._elements["play"]) { 1624 that._elements["pause"].focus(); 1625 } 1626 break; 1627 default: 1628 return; 1629 } 1630 } 1631 1632 /** 1633 * Handles carousel mouseenter events 1634 * 1635 * @private 1636 * @param {Object} event The mouseenter event 1637 */ 1638 function onMouseEnter(event) { 1639 clearAutoplayInterval(); 1640 } 1641 1642 /** 1643 * Handles carousel mouseleave events 1644 * 1645 * @private 1646 * @param {Object} event The mouseleave event 1647 */ 1648 function onMouseLeave(event) { 1649 resetAutoplayInterval(); 1650 } 1651 1652 /** 1653 * Handles pause element click events 1654 * 1655 * @private 1656 * @param {Object} event The click event 1657 */ 1658 function onPauseClick(event) { 1659 pause(); 1660 that._elements["play"].focus(); 1661 } 1662 1663 /** 1664 * Handles play element click events 1665 * 1666 * @private 1667 * @param {Object} event The click event 1668 */ 1669 function onPlayClick() { 1670 play(); 1671 that._elements["pause"].focus(); 1672 } 1673 1674 /** 1675 * Pauses the playing of the Carousel. Sets {@code Carousel#_paused} marker. 1676 * Only relevant when autoplay is enabled 1677 * 1678 * @private 1679 */ 1680 function pause() { 1681 that._paused = true; 1682 clearAutoplayInterval(); 1683 refreshPlayPauseActions(); 1684 } 1685 1686 /** 1687 * Enables the playing of the Carousel. Sets {@code Carousel#_paused} marker. 1688 * Only relevant when autoplay is enabled 1689 * 1690 * @private 1691 */ 1692 function play() { 1693 that._paused = false; 1694 1695 // If the Carousel is hovered, don't begin auto transitioning until the next mouse leave event 1696 var hovered = false; 1697 if (that._elements.self.parentElement) { 1698 hovered = that._elements.self.parentElement.querySelector(":hover") === that._elements.self; 1699 } 1700 if (that._properties.autopauseDisabled || !hovered) { 1701 resetAutoplayInterval(); 1702 } 1703 1704 refreshPlayPauseActions(); 1705 } 1706 1707 /** 1708 * Refreshes the play/pause action markup based on the {@code Carousel#_paused} state 1709 * 1710 * @private 1711 */ 1712 function refreshPlayPauseActions() {
1713 setActionDisabled(that._elements["pause"], that._paused); 1714 setActionDisabled(that._elements["play"], !that._paused); 1715 } 1716 1717 /** 1718 * Initialize {@code Carousel#_active} based on the active item of the carousel. 1719 */ 1720 function initializeActive() { 1721 var items = that._elements["item"]; 1722 if (items && Array.isArray(items)) { 1723 for (var i = 0; i < items.length; i++) { 1724 if (items[i].classList.contains("cmp-carousel__item--active")) { 1725 that._active = i; 1726 break; 1727 } 1728 } 1729 } 1730 } 1731 1732 /** 1733 * Refreshes the item markup based on the current {@code Carousel#_active} index 1734 * 1735 * @private 1736 */ 1737 function refreshActive() { 1738 var items = that._elements["item"]; 1739 var indicators = that._elements["indicator"]; 1740 1741 if (items) { 1742 if (Array.isArray(items)) { 1743 for (var i = 0; i < items.length; i++) { 1744 if (i === parseInt(that._active)) { 1745 items[i].classList.add("cmp-carousel__item--active"); 1746 items[i].removeAttribute("aria-hidden"); 1747 indicators[i].classList.add("cmp-carousel__indicator--active"); 1748 indicators[i].setAttribute("aria-selected", true); 1749 indicators[i].setAttribute("tabindex", "0"); 1750 } else { 1751 items[i].classList.remove("cmp-carousel__item--active"); 1752 items[i].setAttribute("aria-hidden", true); 1753 indicators[i].classList.remove("cmp-carousel__indicator--active"); 1754 indicators[i].setAttribute("aria-selected", false); 1755 indicators[i].setAttribute("tabindex", "-1"); 1756 } 1757 } 1758 } else { 1759 // only one item 1760 items.classList.add("cmp-carousel__item--active"); 1761 indicators.classList.add("cmp-carousel__indicator--active"); 1762 } 1763 } 1764 } 1765 1766 /** 1767 * Focuses the element and prevents scrolling the element into view 1768 * 1769 * @param {HTMLElement} element Element to focus 1770 */ 1771 function focusWithoutScroll(element) { 1772 var x = window.scrollX || window.pageXOffset; 1773 var y = window.scrollY || window.pageYOffset; 1774 element.focus(); 1775 window.scrollTo(x, y); 1776 } 1777 1778 /** 1779 * Retrieves the next active index, with looping 1780 * 1781 * @private 1782 * @returns {Number} Index of the next carousel item 1783 */ 1784 function getNextIndex() { 1785 return that._active === (that._elements["item"].length - 1) ? 0 : that._active + 1; 1786 } 1787 1788 /** 1789 * Retrieves the previous active index, with looping 1790 * 1791 * @private 1792 * @returns {Number} Index of the previous carousel item 1793 */ 1794 function getPreviousIndex() { 1795 return that._active === 0 ? (that._elements["item"].length - 1) : that._active - 1; 1796 } 1797 1798 /** 1799 * Navigates to the item at the provided index 1800 * 1801 * @private 1802 * @param {Number} index The index of the item to navigate to 1803 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1804 */ 1805 function navigate(index, keepHash) { 1806 if (index < 0 || index > (that._elements["item"].length - 1)) { 1807 return; 1808 } 1809 1810 that._active = index; 1811 refreshActive(); 1812 1813 if (!keepHash && containerUtils) { 1814 containerUtils.updateUrlHash(that, "item", index); 1815 } 1816 1817 if (dataLayerEnabled) { 1818 var carouselId = that._elements.self.id; 1819 var activeItem = getDataLayerId(that._elements.item[index]); 1820 var updatePayload = { component: {} }; 1821 updatePayload.component[carouselId] = { shownItems: [activeItem] }; 1822 1823 var removePayload = { component: {} }; 1824 removePayload.component[carouselId] = { shownItems: undefined }; 1825 1826 dataLayer.push(removePayload); 1827 dataLayer.push(updatePayload); 1828 } 1829 1830 // reset the autoplay transition interval following navigation, if not already hovering the carousel
1831 if (that._elements.self.parentElement) { 1832 if (that._elements.self.parentElement.querySelector(":hover") !== that._elements.self) { 1833 resetAutoplayInterval(); 1834 } 1835 } 1836 } 1837 1838 /** 1839 * Navigates to the item at the provided index and ensures the active indicator gains focus 1840 * 1841 * @private 1842 * @param {Number} index The index of the item to navigate to 1843 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1844 */ 1845 function navigateAndFocusIndicator(index, keepHash) { 1846 navigate(index, keepHash); 1847 focusWithoutScroll(that._elements["indicator"][index]); 1848 1849 if (dataLayerEnabled) { 1850 dataLayer.push({ 1851 event: "cmp:show", 1852 eventInfo: { 1853 path: "component." + getDataLayerId(that._elements.item[index]) 1854 } 1855 }); 1856 } 1857 } 1858 1859 /** 1860 * Starts/resets automatic slide transition interval 1861 * 1862 * @private 1863 */ 1864 function resetAutoplayInterval() { 1865 if (that._paused || !that._properties.autoplay) { 1866 return; 1867 } 1868 clearAutoplayInterval(); 1869 that._autoplayIntervalId = window.setInterval(function() { 1870 if (document.visibilityState && document.hidden) { 1871 return; 1872 } 1873 var indicators = that._elements["indicators"]; 1874 if (indicators !== document.activeElement && indicators.contains(document.activeElement)) { 1875 // if an indicator has focus, ensure we switch focus following navigation 1876 navigateAndFocusIndicator(getNextIndex(), true); 1877 } else { 1878 navigate(getNextIndex(), true); 1879 } 1880 }, that._properties.delay); 1881 } 1882 1883 /** 1884 * Clears/pauses automatic slide transition interval 1885 * 1886 * @private 1887 */ 1888 function clearAutoplayInterval() { 1889 window.clearInterval(that._autoplayIntervalId); 1890 that._autoplayIntervalId = null; 1891 } 1892 1893 /** 1894 * Sets the disabled state for an action and toggles the appropriate CSS classes 1895 * 1896 * @private 1897 * @param {HTMLElement} action Action to disable 1898 * @param {Boolean} [disable] {@code true} to disable, {@code false} to enable 1899 */ 1900 function setActionDisabled(action, disable) { 1901 if (!action) { 1902 return; 1903 } 1904 if (disable !== false) { 1905 action.disabled = true; 1906 action.classList.add("cmp-carousel__action--disabled"); 1907 } else { 1908 action.disabled = false; 1909 action.classList.remove("cmp-carousel__action--disabled"); 1910 } 1911 } 1912 } 1913 1914 /** 1915 * Parses the dataLayer string and returns the ID 1916 * 1917 * @private 1918 * @param {HTMLElement} item the accordion item 1919 * @returns {String} dataLayerId or undefined 1920 */ 1921 function getDataLayerId(item) { 1922 if (item) { 1923 if (item.dataset.cmpDataLayer) { 1924 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 1925 } else { 1926 return item.id; 1927 } 1928 } 1929 return null; 1930 } 1931 1932 /** 1933 * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary. 1934 * 1935 * @private 1936 */ 1937 function onDocumentReady() { 1938 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 1939 if (dataLayerEnabled) { 1940 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 1941 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 1942 } 1943 1944 var elements = document.querySelectorAll(selectors.self); 1945 for (var i = 0; i < elements.length; i++) { 1946 new Carousel({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 1947 } 1948 1949 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 1950 var body = document.querySelector("body"); 1951 var observer = new MutationObserver(function(mutations) { 1952 mutations.forEach(function(mutation) { 1953 // needed for IE 1954 var nodesArray = [].slice.call(mutation.addedNodes); 1955 if (nodesArray.length > 0) { 1956 nodesArray.forEach(function(addedNode) { 1957 if (addedNode.querySelectorAll) { 1958 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 1959 elementsArray.forEach(function(element) { 1960 new Carousel({ element: element, options: CMP.utils.readData(element, IS) }); 1961 }); 1962 } 1963 }); 1964 } 1965 }); 1966 }); 1967 1968 observer.observe(body, { 1969 subtree: true, 1970 childList: true, 1971 characterData: true 1972 }); 1973 } 1974 1975 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 1976 document.addEventListener("DOMContentLoaded", resolve); 1977 }); 1978 Promise.all([documentReady]).then(onDocumentReady); 1979 1980 if (containerUtils) { 1981 window.addEventListener("load", containerUtils.scrollToAnchor, false); 1982 } 1983 1984}()); 1985 1986 1987/******************************************************************************* 1988 * Copyright 2022 Adobe 1989 * 1990 * Licensed under the Apache License, Version 2.0 (the "License");
1991 * you may not use this file except in compliance with the License. 1992 * You may obtain a copy of the License at 1993 * 1994 * http://www.apache.org/licenses/LICENSE-2.0 1995 * 1996 * Unless required by applicable law or agreed to in writing, software 1997 * distributed under the License is distributed on an "AS IS" BASIS, 1998 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1999 * See the License for the specific language governing permissions and 2000 * limitations under the License. 2001 ******************************************************************************/ 2002(function(document) { 2003 "use strict"; 2004 2005 window.CMP = window.CMP || {}; 2006 window.CMP.utils = (function() { 2007 var NS = "cmp"; 2008 2009 /** 2010 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 2011 * 2012 * @param {HTMLElement} element The component element to read options data from 2013 * @param {String} is The component identifier 2014 * @returns {String[]} The options read from the component data attributes 2015 */ 2016 var readData = function(element, is) { 2017 var data = element.dataset; 2018 var options = []; 2019 var capitalized = is; 2020 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2021 var reserved = ["is", "hook" + capitalized]; 2022 2023 for (var key in data) { 2024 if (Object.prototype.hasOwnProperty.call(data, key)) { 2025 var value = data[key]; 2026 2027 if (key.indexOf(NS) === 0) { 2028 key = key.slice(NS.length); 2029 key = key.charAt(0).toLowerCase() + key.substring(1); 2030 2031 if (reserved.indexOf(key) === -1) { 2032 options[key] = value; 2033 } 2034 } 2035 } 2036 } 2037 return options; 2038 }; 2039 2040 /** 2041 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 2042 * @param {String[]} options the options to transform 2043 * @param {Object} properties object of properties of property functions 2044 * @returns {Object} transformed properties 2045 */ 2046 var setupProperties = function(options, properties) { 2047 var transformedProperties = {}; 2048 2049 for (var key in properties) { 2050 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2051 var property = properties[key]; 2052 if (options && options[key] != null) { 2053 if (property && typeof property.transform === "function") { 2054 transformedProperties[key] = property.transform(options[key]); 2055 } else { 2056 transformedProperties[key] = options[key]; 2057 } 2058 } else { 2059 transformedProperties[key] = properties[key]["default"]; 2060 } 2061 } 2062 } 2063 return transformedProperties; 2064 }; 2065 2066 2067 return { 2068 readData: readData, 2069 setupProperties: setupProperties 2070 }; 2071 }()); 2072}(window.document)); 2073 2074/******************************************************************************* 2075 * Copyright 2022 Adobe 2076 * 2077 * Licensed under the Apache License, Version 2.0 (the "License"); 2078 * you may not use this file except in compliance with the License. 2079 * You may obtain a copy of the License at 2080 * 2081 * http://www.apache.org/licenses/LICENSE-2.0 2082 * 2083 * Unless required by applicable law or agreed to in writing, software 2084 * distributed under the License is distributed on an "AS IS" BASIS, 2085 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2086 * See the License for the specific language governing permissions and 2087 * limitations under the License. 2088 ******************************************************************************/ 2089(function(document) { 2090 "use strict"; 2091 2092 window.CMP = window.CMP || {}; 2093 window.CMP.image = window.CMP.image || {}; 2094 window.CMP.image.dynamicMedia = (function() { 2095 var autoSmartCrops = {}; 2096 var SRC_URI_TEMPLATE_WIDTH_VAR = "{.width}"; 2097 var SRC_URI_TEMPLATE_DPR_VAR = "{dpr}";
2098 var SRC_URI_DPR_OFF = "dpr=off"; 2099 var SRC_URI_DPR_ON = "dpr=on,{dpr}"; 2100 var dpr = window.devicePixelRatio || 1; 2101 var config = { 2102 minWidth: 20 2103 }; 2104 2105 /** 2106 * get auto smart crops from dm 2107 * @param {String} src the src uri 2108 * @returns {{}} the smart crop json object 2109 */ 2110 var getAutoSmartCrops = function(src) { 2111 var request = new XMLHttpRequest(); 2112 var url = src.split(SRC_URI_TEMPLATE_WIDTH_VAR)[0] + "?req=set,json"; 2113 request.open("GET", url, false); 2114 request.onload = function() { 2115 if (request.status >= 200 && request.status < 400) { 2116 // success status 2117 var responseText = request.responseText; 2118 var rePayload = new RegExp(/^(?:\/\*jsonp\*\/)?\s*([^()]+)\(([\s\S]+),\s*"[0-9]*"\);?$/gmi); 2119 var rePayloadJSON = new RegExp(/^{[\s\S]*}$/gmi); 2120 var resPayload = rePayload.exec(responseText); 2121 var payload; 2122 if (resPayload) { 2123 var payloadStr = resPayload[2]; 2124 if (rePayloadJSON.test(payloadStr)) { 2125 payload = JSON.parse(payloadStr); 2126 } 2127 2128 } 2129 // check "relation" - only in case of smartcrop preset 2130 if (payload && payload.set.relation && payload.set.relation.length > 0) { 2131 for (var i = 0; i < payload.set.relation.length; i++) { 2132 autoSmartCrops[parseInt(payload.set.relation[i].userdata.SmartCropWidth)] = 2133 ":" + payload.set.relation[i].userdata.SmartCropDef; 2134 } 2135 } 2136 } else { 2137 // error status 2138 } 2139 }; 2140 request.send(); 2141 return autoSmartCrops; 2142 }; 2143 2144 /** 2145 * Build and return the srcset value based on the available auto smart crops 2146 * @param {String} src the src uri 2147 * @param {Object} smartCrops the smart crops object 2148 * @returns {String} the srcset 2149 */ 2150 var getSrcSet = function(src, smartCrops) { 2151 var srcset; 2152 var keys = Object.keys(smartCrops); 2153 if (keys.length > 0) { 2154 srcset = []; 2155 for (var key in autoSmartCrops) { 2156 srcset.push(src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, smartCrops[key]) + " " + key + "w"); 2157 } 2158 } 2159 return srcset.join(","); 2160 }; 2161 2162 /** 2163 * Get the optimal width based on the available sizes 2164 * @param {[Number]} sizes the available sizes 2165 * @param {Number} width the element width 2166 * @returns {String} the optimal width 2167 */ 2168 function getOptimalWidth(sizes, width) { 2169 var len = sizes.length; 2170 var key = 0; 2171 2172 while ((key < len - 1) && (sizes[key] < width)) { 2173 key++; 2174 } 2175 2176 return sizes[key] !== undefined ? sizes[key].toString() : width; 2177 } 2178 2179 /** 2180 * Get the width of an element or parent element if the width is smaller than the minimum width 2181 * @param {HTMLElement} component the image component 2182 * @param {HTMLElement | Node} parent the parent element 2183 * @returns {Number} the width of the element 2184 */ 2185 var getWidth = function(component, parent) { 2186 var width = component.offsetWidth; 2187 while (width < config.minWidth && parent && !component._autoWidth) { 2188 width = parent.offsetWidth; 2189 parent = parent.parentNode; 2190 } 2191 return width; 2192 }; 2193 2194 /** 2195 * Set the src and srcset attribute for a Dynamic Media Image which auto smart crops enabled. 2196 * @param {HTMLElement} component the image component 2197 * @param {{}} properties the component properties 2198 */ 2199 var setDMAttributes = function(component, properties) { 2200 // for v3 we first have to turn the dpr on
2201 var src = properties.src.replace(SRC_URI_DPR_OFF, SRC_URI_DPR_ON); 2202 src = src.replace(SRC_URI_TEMPLATE_DPR_VAR, dpr); 2203 var smartCrops = {}; 2204 var width; 2205 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 2206 smartCrops = getAutoSmartCrops(src); 2207 } 2208 var hasWidths = (properties.widths && properties.widths.length > 0) || Object.keys(smartCrops).length > 0; 2209 if (hasWidths) { 2210 var image = component.querySelector("img"); 2211 var elemWidth = getWidth(component, component.parentNode); 2212 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 2213 image.setAttribute("srcset", CMP.image.dynamicMedia.getSrcSet(src, smartCrops)); 2214 width = getOptimalWidth(Object.keys(smartCrops, elemWidth)); 2215 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, smartCrops[width])); 2216 } else { 2217 width = getOptimalWidth(properties.widths, elemWidth); 2218 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, width)); 2219 } 2220 } 2221 }; 2222 2223 /** 2224 * Get the src attribute based on the optimal width 2225 * @param {String} src the src uri 2226 * @param {String} width the element width 2227 * @returns {String} the final src attribute 2228 */ 2229 var getSrc = function(src, width) { 2230 if (src.indexOf(SRC_URI_TEMPLATE_WIDTH_VAR) > -1) { 2231 src = src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, width); 2232 } 2233 return src; 2234 }; 2235 2236 2237 return { 2238 getAutoSmartCrops: getAutoSmartCrops, 2239 getSrcSet: getSrcSet, 2240 getSrc: getSrc, 2241 setDMAttributes: setDMAttributes, 2242 getWidth: getWidth 2243 }; 2244 }()); 2245 document.dispatchEvent(new CustomEvent("core.wcm.components.commons.site.image.dynamic-media.loaded")); 2246}(window.document)); 2247 2248/******************************************************************************* 2249 * Copyright 2022 Adobe 2250 * 2251 * Licensed under the Apache License, Version 2.0 (the "License"); 2252 * you may not use this file except in compliance with the License. 2253 * You may obtain a copy of the License at 2254 * 2255 * http://www.apache.org/licenses/LICENSE-2.0 2256 * 2257 * Unless required by applicable law or agreed to in writing, software 2258 * distributed under the License is distributed on an "AS IS" BASIS, 2259 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2260 * See the License for the specific language governing permissions and 2261 * limitations under the License. 2262 ******************************************************************************/ 2263(function(document) { 2264 2265 "use strict"; 2266 2267 window.CMP = window.CMP || {}; 2268 window.CMP.image = window.CMP.image || {}; 2269 window.CMP.image.v3 = (function() { 2270 var IS = "image"; 2271 2272 var selectors = { 2273 self: "[data-cmp-hook-image='imageV3']" 2274 }; 2275 2276 var properties = { 2277 "widths": { 2278 "default": [], 2279 "transform": function(value) { 2280 var widths = []; 2281 value.split(",").forEach(function(item) { 2282 item = parseFloat(item); 2283 if (!isNaN(item)) { 2284 widths.push(item); 2285 } 2286 }); 2287 return widths; 2288 } 2289 }, 2290 "dmimage": { 2291 "default": false, 2292 "transform": function(value) { 2293 return !(value === null || typeof value === "undefined"); 2294 } 2295 }, 2296 "src": { 2297 "transform": function(value) { 2298 return decodeURIComponent(value); 2299 } 2300 }, 2301 "smartcroprendition": "" 2302 }; 2303 2304 function Image(config) { 2305 var that = this; 2306 /** 2307 * Init the image if the image is from dynamic media 2308 * @param {HTMLElement} component the image component 2309 */ 2310 that.initImage = function(component) { 2311 var options = CMP.utils.readData(component, IS); 2312 that._properties = CMP.utils.setupProperties(options, properties); 2313 if (that._properties.dmimage) { 2314 // use encoded src for dynamic media 2315 that._properties.src = options["src"]; 2316 CMP.image.dynamicMedia.setDMAttributes(component, that._properties); 2317 } 2318 }; 2319 that.initImage(config.element); 2320 } 2321 2322 return { 2323 init: function() { 2324 var elements = document.querySelectorAll(selectors.self); 2325 for (var i = 0; i < elements.length; i++) { 2326 new Image({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 2327 } 2328 2329 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2330 var body = document.querySelector("body"); 2331 var observer = new MutationObserver(function(mutations) { 2332 mutations.forEach(function(mutation) { 2333 // needed for IE 2334 var nodesArray = [].slice.call(mutation.addedNodes); 2335 if (nodesArray.length > 0) { 2336 nodesArray.forEach(function(addedNode) { 2337 if (addedNode.querySelectorAll) { 2338 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2339 elementsArray.forEach(function(element) { 2340 new Image({ element: element, options: CMP.utils.readData(element, IS) }); 2341 }); 2342 } 2343 }); 2344 } 2345 }); 2346 }); 2347 2348 observer.observe(body, { 2349 subtree: true, 2350 childList: true, 2351 characterData: true 2352 }); 2353 } 2354 }; 2355 }()); 2356 2357 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 2358 document.addEventListener("DOMContentLoaded", resolve); 2359 }); 2360 Promise.all([documentReady]).then(window.CMP.image.v3.init); 2361}(window.document)); 2362 2363 2364/******************************************************************************* 2365 * Copyright 2022 Adobe 2366 * 2367 * Licensed under the Apache License, Version 2.0 (the "License");
2368 * you may not use this file except in compliance with the License. 2369 * You may obtain a copy of the License at 2370 * 2371 * http://www.apache.org/licenses/LICENSE-2.0 2372 * 2373 * Unless required by applicable law or agreed to in writing, software 2374 * distributed under the License is distributed on an "AS IS" BASIS, 2375 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2376 * See the License for the specific language governing permissions and 2377 * limitations under the License. 2378 ******************************************************************************/ 2379(function() { 2380 "use strict"; 2381 2382 var NS = "cmp"; 2383 var IS = "search"; 2384 2385 var DELAY = 300; // time before fetching new results when the user is typing a search string 2386 var LOADING_DISPLAY_DELAY = 300; // minimum time during which the loading indicator is displayed 2387 var PARAM_RESULTS_OFFSET = "resultsOffset"; 2388 2389 var keyCodes = { 2390 TAB: 9, 2391 ENTER: 13, 2392 ESCAPE: 27, 2393 ARROW_UP: 38, 2394 ARROW_DOWN: 40 2395 }; 2396 2397 var selectors = { 2398 self: "[data-" + NS + '-is="' + IS + '"]', 2399 item: { 2400 self: "[data-" + NS + "-hook-" + IS + '="item"]', 2401 title: "[data-" + NS + "-hook-" + IS + '="itemTitle"]', 2402 focused: "." + NS + "-search__item--is-focused" 2403 } 2404 }; 2405 2406 var properties = { 2407 /** 2408 * The minimum required length of the search term before results are fetched. 2409 * 2410 * @memberof Search 2411 * @type {Number} 2412 * @default 3 2413 */ 2414 minLength: { 2415 "default": 3, 2416 transform: function(value) { 2417 value = parseFloat(value); 2418 return isNaN(value) ? null : value; 2419 } 2420 }, 2421 /** 2422 * The maximal number of results fetched by a search request. 2423 * 2424 * @memberof Search 2425 * @type {Number} 2426 * @default 10 2427 */ 2428 resultsSize: { 2429 "default": 10, 2430 transform: function(value) { 2431 value = parseFloat(value); 2432 return isNaN(value) ? null : value; 2433 } 2434 } 2435 }; 2436 2437 var idCount = 0; 2438 2439 function readData(element) { 2440 var data = element.dataset; 2441 var options = []; 2442 var capitalized = IS; 2443 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2444 var reserved = ["is", "hook" + capitalized]; 2445 2446 for (var key in data) { 2447 if (Object.prototype.hasOwnProperty.call(data, key)) { 2448 var value = data[key]; 2449 2450 if (key.indexOf(NS) === 0) { 2451 key = key.slice(NS.length); 2452 key = key.charAt(0).toLowerCase() + key.substring(1); 2453 2454 if (reserved.indexOf(key) === -1) { 2455 options[key] = value; 2456 } 2457 } 2458 } 2459 } 2460 2461 return options; 2462 } 2463 2464 function toggleShow(element, show) { 2465 if (element) { 2466 if (show !== false) { 2467 element.style.display = "block"; 2468 element.setAttribute("aria-hidden", false); 2469 } else { 2470 element.style.display = "none"; 2471 element.setAttribute("aria-hidden", true); 2472 } 2473 } 2474 } 2475 2476 function serialize(form) { 2477 var query = []; 2478 if (form && form.elements) { 2479 for (var i = 0; i < form.elements.length; i++) { 2480 var node = form.elements[i]; 2481 if (!node.disabled && node.name) { 2482 var param = [node.name, encodeURIComponent(node.value)]; 2483 query.push(param.join("=")); 2484 } 2485 } 2486 } 2487 return query.join("&"); 2488 } 2489 2490 function mark(node, regex) { 2491 if (!node || !regex) { 2492 return; 2493 } 2494 2495 // text nodes 2496 if (node.nodeType === 3) { 2497 var nodeValue = node.nodeValue; 2498 var match = regex.exec(nodeValue); 2499 2500 if (nodeValue && match) { 2501 var element = document.createElement("mark"); 2502 element.className = NS + "-search__item-mark"; 2503 element.appendChild(document.createTextNode(match[0])); 2504 2505 var after = node.splitText(match.index); 2506 after.nodeValue = after.nodeValue.substring(match[0].length); 2507 node.parentNode.insertBefore(element, after); 2508 } 2509 } else if (node.hasChildNodes()) { 2510 for (var i = 0; i < node.childNodes.length; i++) { 2511 // recurse 2512 mark(node.childNodes[i], regex); 2513 } 2514 } 2515 } 2516 2517 function localizeMessage(searchElementId, message, values = []) { 2518 const i18nMessages = JSON.parse(document.querySelector("#" + searchElementId).getAttribute("data-i18n-messages")); 2519 let translation = i18nMessages[message]; 2520 // Insert values 2521 if (values.length > 0) { 2522 translation = translation.replace(/{(\d+)}/g, function(match, index) { 2523 return typeof values[index] !== "undefined" ? values[index] : match; 2524 }); 2525 } 2526 return translation; 2527 } 2528 2529 // useful for Accessibility, helping users with low vision and users with cognitive disabilities to identify the change in results 2530 function updateSearchResultsStatusMessageElement(searchElementId, totalResults) { 2531 var searchResultsStatusMessage = document.querySelector("#" + searchElementId + "> .cmp_search__info"); 2532 searchResultsStatusMessage.style.visibility = "visible"; 2533 var searchResultsFoundMessage = localizeMessage(searchElementId, totalResults === 1 ? "{0} result" : "{0} results", [totalResults]); 2534 var searchResultsNotFoundMessage = localizeMessage(searchElementId, "No results"); 2535 searchResultsStatusMessage.innerText = totalResults > 0 ? searchResultsFoundMessage : searchResultsNotFoundMessage; 2536 } 2537 2538 function Search(config) { 2539 if (config.element) { 2540 // prevents multiple initialization 2541 config.element.removeAttribute("data-" + NS + "-is"); 2542 } 2543 2544 this._cacheElements(config.element); 2545 this._setupProperties(config.options); 2546 2547 this._action = this._elements.form.getAttribute("action"); 2548 this._resultsOffset = 0; 2549 this._hasMoreResults = true; 2550 2551 this._elements.input.addEventListener("input", this._onInput.bind(this)); 2552 this._elements.input.addEventListener("focus", this._onInput.bind(this)); 2553 this._elements.input.addEventListener("keydown", this._onKeydown.bind(this)); 2554 this._elements.clear.addEventListener("click", this._onClearClick.bind(this)); 2555 document.addEventListener("click", this._onDocumentClick.bind(this)); 2556 this._elements.results.addEventListener("scroll", this._onScroll.bind(this)); 2557 2558 this._makeAccessible(); 2559 } 2560 2561 Search.prototype._displayResults = function() { 2562 if (this._elements.input.value.length === 0) { 2563 toggleShow(this._elements.clear, false); 2564 this._cancelResults(); 2565 } else if (this._elements.input.value.length < this._properties.minLength) { 2566 toggleShow(this._elements.clear, true); 2567 } else { 2568 this._updateResults(); 2569 toggleShow(this._elements.clear, true); 2570 } 2571 }; 2572 2573 Search.prototype._onScroll = function(event) { 2574 // fetch new results when the results to be scrolled down are less than the visible results 2575 if (this._elements.results.scrollTop + 2 * this._elements.results.clientHeight >= this._elements.results.scrollHeight) { 2576 this._resultsOffset += this._properties.resultsSize; 2577 this._displayResults(); 2578 } 2579 }; 2580 2581 Search.prototype._onInput = function(event) { 2582 var self = this; 2583 self._cancelResults(); 2584 // start searching when the search term reaches the minimum length 2585 this._timeout = setTimeout(function() { 2586 self._displayResults(); 2587 }, DELAY); 2588 }; 2589 2590 Search.prototype._onKeydown = function(event) { 2591 var self = this; 2592 2593 switch (event.keyCode) { 2594 case keyCodes.TAB: 2595 if (self._resultsOpen()) { 2596 toggleShow(self._elements.results, false); 2597 self._elements.input.setAttribute("aria-expanded", "false"); 2598 this._hideSearchResultsStatusMessage(); 2599 } 2600 break;
2601 case keyCodes.ENTER: 2602 event.preventDefault(); 2603 if (self._resultsOpen()) { 2604 var focused = self._elements.results.querySelector(selectors.item.focused); 2605 if (focused) { 2606 focused.click(); 2607 } 2608 } 2609 break; 2610 case keyCodes.ESCAPE: 2611 self._cancelResults(); 2612 break; 2613 case keyCodes.ARROW_UP: 2614 if (self._resultsOpen()) { 2615 event.preventDefault(); 2616 self._stepResultFocus(true); 2617 } 2618 break; 2619 case keyCodes.ARROW_DOWN: 2620 if (self._resultsOpen()) { 2621 event.preventDefault(); 2622 self._stepResultFocus(); 2623 } else { 2624 // test the input and if necessary fetch and display the results 2625 self._onInput(); 2626 } 2627 break; 2628 default: 2629 return; 2630 } 2631 }; 2632 2633 Search.prototype._onClearClick = function(event) { 2634 event.preventDefault(); 2635 this._elements.input.value = ""; 2636 toggleShow(this._elements.clear, false); 2637 toggleShow(this._elements.results, false); 2638 this._elements.input.setAttribute("aria-expanded", "false"); 2639 this._hideSearchResultsStatusMessage(); 2640 }; 2641 2642 Search.prototype._onDocumentClick = function(event) { 2643 var inputContainsTarget = this._elements.input.contains(event.target); 2644 var resultsContainTarget = this._elements.results.contains(event.target); 2645 2646 if (!(inputContainsTarget || resultsContainTarget)) { 2647 toggleShow(this._elements.results, false); 2648 this._elements.input.setAttribute("aria-expanded", "false"); 2649 this._hideSearchResultsStatusMessage(); 2650 } 2651 }; 2652 2653 Search.prototype._resultsOpen = function() { 2654 return this._elements.results.style.display !== "none"; 2655 }; 2656 2657 Search.prototype._makeAccessible = function() { 2658 var id = NS + "-search-results-" + idCount; 2659 this._elements.input.setAttribute("aria-owns", id); 2660 this._elements.results.id = id; 2661 idCount++; 2662 }; 2663 2664 Search.prototype._generateItems = function(data, results) { 2665 var self = this; 2666 2667 data.forEach(function(item) { 2668 var el = document.createElement("span"); 2669 el.innerHTML = self._elements.itemTemplate.innerHTML; 2670 el.querySelectorAll(selectors.item.title)[0].appendChild(document.createTextNode(item.title)); 2671 el.querySelectorAll(selectors.item.self)[0].setAttribute("href", self._safeHref(item.url)); 2672 results.innerHTML += el.innerHTML; 2673 }); 2674 }; 2675 2676 Search.prototype._safeHref = function(href) { 2677 var a = document.createElement("a"); 2678 a.href = href; 2679 return a.pathname; 2680 }; 2681 2682 Search.prototype._markResults = function() { 2683 var nodeList = this._elements.results.querySelectorAll(selectors.item.self); 2684 var escapedTerm = this._elements.input.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 2685 var regex = new RegExp("(" + escapedTerm + ")", "gi"); 2686 2687 for (var i = this._resultsOffset - 1; i < nodeList.length; ++i) { 2688 var result = nodeList[i]; 2689 mark(result, regex); 2690 } 2691 }; 2692 2693 Search.prototype._stepResultFocus = function(reverse) { 2694 var results = this._elements.results.querySelectorAll(selectors.item.self); 2695 var focused = this._elements.results.querySelector(selectors.item.focused); 2696 var newFocused; 2697 var index = Array.prototype.indexOf.call(results, focused); 2698 var focusedCssClass = NS + "-search__item--is-focused"; 2699 2700 if (results.length > 0) { 2701 2702 if (!reverse) { 2703 // highlight the next result 2704 if (index < 0) { 2705 results[0].classList.add(focusedCssClass); 2706 results[0].setAttribute("aria-selected", "true"); 2707 } else if (index + 1 < results.length) { 2708 results[index].classList.remove(focusedCssClass); 2709 results[index].setAttribute("aria-selected", "false"); 2710 results[index + 1].classList.add(focusedCssClass); 2711 results[index + 1].setAttribute("aria-selected", "true"); 2712 } 2713 2714 // if the last visible result is partially hidden, scroll up until it's completely visible 2715 newFocused = this._elements.results.querySelector(selectors.item.focused); 2716 if (newFocused) { 2717 var bottomHiddenHeight = newFocused.offsetTop + newFocused.offsetHeight - this._elements.results.scrollTop - this._elements.results.clientHeight; 2718 if (bottomHiddenHeight > 0) { 2719 this._elements.results.scrollTop += bottomHiddenHeight; 2720 } else { 2721 this._onScroll(); 2722 } 2723 } 2724 2725 } else { 2726 // highlight the previous result 2727 if (index >= 1) { 2728 results[index].classList.remove(focusedCssClass); 2729 results[index].setAttribute("aria-selected", "false"); 2730 results[index - 1].classList.add(focusedCssClass); 2731 results[index - 1].setAttribute("aria-selected", "true"); 2732 } 2733 2734 // if the first visible result is partially hidden, scroll down until it's completely visible 2735 newFocused = this._elements.results.querySelector(selectors.item.focused); 2736 if (newFocused) { 2737 var topHiddenHeight = this._elements.results.scrollTop - newFocused.offsetTop; 2738 if (topHiddenHeight > 0) { 2739 this._elements.results.scrollTop -= topHiddenHeight; 2740 } 2741 } 2742 } 2743 } 2744 }; 2745 2746 Search.prototype._updateResults = function() { 2747 var self = this; 2748 if (self._hasMoreResults) { 2749 var request = new XMLHttpRequest(); 2750 var url = self._action + "?" + serialize(self._elements.form) + "&" + PARAM_RESULTS_OFFSET + "=" + self._resultsOffset; 2751 2752 request.open("GET", url, true); 2753 request.onload = function() { 2754 // when the results are loaded: hide the loading indicator and display the search icon after a minimum period 2755 setTimeout(function() { 2756 toggleShow(self._elements.loadingIndicator, false); 2757 toggleShow(self._elements.icon, true); 2758 }, LOADING_DISPLAY_DELAY); 2759 if (request.status >= 200 && request.status < 400) { 2760 // success status 2761 var data = JSON.parse(request.responseText); 2762 updateSearchResultsStatusMessageElement(self._elements.self.id, data.length); 2763 if (data.length > 0) { 2764 self._generateItems(data, self._elements.results); 2765 self._markResults(); 2766 toggleShow(self._elements.results, true); 2767 self._elements.input.setAttribute("aria-expanded", "true"); 2768 } else { 2769 self._hasMoreResults = false;
2770 } 2771 // the total number of results is not a multiple of the fetched results: 2772 // -> we reached the end of the query 2773 if (self._elements.results.querySelectorAll(selectors.item.self).length % self._properties.resultsSize > 0) { 2774 self._hasMoreResults = false; 2775 } 2776 } else { 2777 // error status 2778 } 2779 }; 2780 // when the results are loading: display the loading indicator and hide the search icon 2781 toggleShow(self._elements.loadingIndicator, true); 2782 toggleShow(self._elements.icon, false); 2783 request.send(); 2784 } 2785 }; 2786 2787 Search.prototype._cancelResults = function() { 2788 clearTimeout(this._timeout); 2789 this._elements.results.scrollTop = 0; 2790 this._resultsOffset = 0; 2791 this._hasMoreResults = true; 2792 this._elements.results.innerHTML = ""; 2793 this._elements.input.setAttribute("aria-expanded", "false"); 2794 this._hideSearchResultsStatusMessage(); 2795 }; 2796 2797 Search.prototype._hideSearchResultsStatusMessage = function() { 2798 var searchResultsStatusMessage = document.querySelector("#" + this._elements.self.id + "> .cmp_search__info"); 2799 searchResultsStatusMessage.style.visibility = "hidden"; 2800 }; 2801 2802 Search.prototype._cacheElements = function(wrapper) { 2803 this._elements = {}; 2804 this._elements.self = wrapper; 2805 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 2806 2807 for (var i = 0; i < hooks.length; i++) { 2808 var hook = hooks[i]; 2809 var capitalized = IS; 2810 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2811 var key = hook.dataset[NS + "Hook" + capitalized]; 2812 this._elements[key] = hook; 2813 } 2814 }; 2815 2816 Search.prototype._setupProperties = function(options) { 2817 this._properties = {}; 2818 2819 for (var key in properties) { 2820 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2821 var property = properties[key]; 2822 if (options && options[key] != null) { 2823 if (property && typeof property.transform === "function") { 2824 this._properties[key] = property.transform(options[key]); 2825 } else { 2826 this._properties[key] = options[key]; 2827 } 2828 } else { 2829 this._properties[key] = properties[key]["default"]; 2830 } 2831 } 2832 } 2833 }; 2834 2835 function onDocumentReady() { 2836 var elements = document.querySelectorAll(selectors.self); 2837 for (var i = 0; i < elements.length; i++) { 2838 new Search({ element: elements[i], options: readData(elements[i]) }); 2839 } 2840 2841 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2842 var body = document.querySelector("body"); 2843 var observer = new MutationObserver(function(mutations) { 2844 mutations.forEach(function(mutation) { 2845 // needed for IE 2846 var nodesArray = [].slice.call(mutation.addedNodes); 2847 if (nodesArray.length > 0) { 2848 nodesArray.forEach(function(addedNode) { 2849 if (addedNode.querySelectorAll) { 2850 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2851 elementsArray.forEach(function(element) { 2852 new Search({ element: element, options: readData(element) }); 2853 }); 2854 } 2855 }); 2856 } 2857 }); 2858 }); 2859 2860 observer.observe(body, { 2861 subtree: true, 2862 childList: true, 2863 characterData: true 2864 }); 2865 } 2866 2867 if (document.readyState !== "loading") { 2868 onDocumentReady(); 2869 } else { 2870 document.addEventListener("DOMContentLoaded", onDocumentReady); 2871 } 2872 2873})(); 2874 2875 2876/******************************************************************************* 2877 * Copyright 2016 Adobe 2878 * 2879 * Licensed under the Apache License, Version 2.0 (the "License");
2880 * you may not use this file except in compliance with the License. 2881 * You may obtain a copy of the License at 2882 * 2883 * http://www.apache.org/licenses/LICENSE-2.0 2884 * 2885 * Unless required by applicable law or agreed to in writing, software 2886 * distributed under the License is distributed on an "AS IS" BASIS, 2887 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2888 * See the License for the specific language governing permissions and 2889 * limitations under the License. 2890 ******************************************************************************/ 2891(function() { 2892 "use strict"; 2893 2894 var NS = "cmp"; 2895 var IS = "formText"; 2896 var IS_DASH = "form-text"; 2897 var SELF_SELECTOR = "[data-" + NS + '-is="' + IS + '"]'; 2898 2899 var selectors = { 2900 self: SELF_SELECTOR, 2901 validationMessage: SELF_SELECTOR + " .cmp-form-text__validation-message" 2902 }; 2903 2904 var displayValidationMessage = false; 2905 2906 var properties = { 2907 /** 2908 * A validation message to display if there is a type mismatch between the user input and expected input. 2909 * 2910 * @type {String} 2911 */ 2912 constraintMessage: "", 2913 /** 2914 * A validation message to display if no input is supplied, but input is expected for the field. 2915 * 2916 * @type {String} 2917 */ 2918 requiredMessage: "" 2919 }; 2920 2921 function readData(element) { 2922 var data = element.dataset; 2923 var options = []; 2924 var capitalized = IS; 2925 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2926 var reserved = ["is", "hook" + capitalized]; 2927 2928 for (var key in data) { 2929 if (Object.prototype.hasOwnProperty.call(data, key)) { 2930 var value = data[key]; 2931 2932 if (key.indexOf(NS) === 0) { 2933 key = key.slice(NS.length); 2934 key = key.charAt(0).toLowerCase() + key.substring(1); 2935 2936 if (reserved.indexOf(key) === -1) { 2937 options[key] = value; 2938 } 2939 } 2940 } 2941 } 2942 2943 return options; 2944 } 2945 2946 function FormText(config) { 2947 if (config.element) { 2948 // prevents multiple initialization 2949 config.element.removeAttribute("data-" + NS + "-is"); 2950 } 2951 2952 this._cacheElements(config.element); 2953 this._setupProperties(config.options); 2954 2955 this._elements.input.addEventListener("invalid", this._onInvalid.bind(this)); 2956 this._elements.input.addEventListener("input", this._onInput.bind(this)); 2957 if (displayValidationMessage) { 2958 this._elements.input.checkValidity(); 2959 } 2960 } 2961 2962 FormText.prototype._onInvalid = function(event) { 2963 event.target.setCustomValidity(""); 2964 if (event.target.validity.typeMismatch) { 2965 if (this._properties.constraintMessage) { 2966 event.target.setCustomValidity(this._properties.constraintMessage); 2967 } 2968 } else if (event.target.validity.valueMissing) { 2969 if (this._properties.requiredMessage) { 2970 event.target.setCustomValidity(this._properties.requiredMessage); 2971 } 2972 } 2973 if (displayValidationMessage) { 2974 var validationMessage = event.target.parentElement.querySelector(".cmp-form-text__validation-message"); 2975 if (validationMessage) { 2976 validationMessage.innerText = event.target.validationMessage; 2977 } 2978 } 2979 }; 2980 2981 FormText.prototype._onInput = function(event) { 2982 event.target.setCustomValidity(""); 2983 if (displayValidationMessage) { 2984 event.target.checkValidity(); 2985 } 2986 }; 2987 2988 FormText.prototype._cacheElements = function(wrapper) { 2989 this._elements = {}; 2990 this._elements.self = wrapper; 2991 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS_DASH + "]"); 2992 for (var i = 0; i < hooks.length; i++) { 2993 var hook = hooks[i]; 2994 var capitalized = IS; 2995 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2996 var key = hook.dataset[NS + "Hook" + capitalized]; 2997 this._elements[key] = hook; 2998 } 2999 }; 3000 3001 FormText.prototype._setupProperties = function(options) { 3002 this._properties = {}; 3003 3004 for (var key in properties) { 3005 if (Object.prototype.hasOwnProperty.call(properties, key)) { 3006 var property = properties[key]; 3007 if (options && options[key] != null) { 3008 if (property && typeof property.transform === "function") { 3009 this._properties[key] = property.transform(options[key]); 3010 } else { 3011 this._properties[key] = options[key]; 3012 } 3013 } else { 3014 this._properties[key] = properties[key]["default"]; 3015 } 3016 } 3017 } 3018 }; 3019 3020 function onDocumentReady() {
3021 var validationMessages = document.querySelectorAll(selectors.validationMessage); 3022 if (validationMessages && validationMessages.length > 0) { 3023 displayValidationMessage = true; 3024 } 3025 3026 var elements = document.querySelectorAll(selectors.self); 3027 for (var i = 0; i < elements.length; i++) { 3028 new FormText({ element: elements[i], options: readData(elements[i]) }); 3029 } 3030 3031 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 3032 var body = document.querySelector("body"); 3033 var observer = new MutationObserver(function(mutations) { 3034 mutations.forEach(function(mutation) { 3035 // needed for IE 3036 var nodesArray = [].slice.call(mutation.addedNodes); 3037 if (nodesArray.length > 0) { 3038 nodesArray.forEach(function(addedNode) { 3039 if (addedNode.querySelectorAll) { 3040 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 3041 elementsArray.forEach(function(element) { 3042 new FormText({ element: element, options: readData(element) }); 3043 }); 3044 } 3045 }); 3046 } 3047 }); 3048 }); 3049 3050 observer.observe(body, { 3051 subtree: true, 3052 childList: true, 3053 characterData: true 3054 }); 3055 } 3056 3057 if (document.readyState !== "loading") { 3058 onDocumentReady(); 3059 } else { 3060 document.addEventListener("DOMContentLoaded", onDocumentReady); 3061 } 3062 3063})(); 3064 3065 3066/******************************************************************************* 3067 * Copyright 2020 Adobe 3068 * 3069 * Licensed under the Apache License, Version 2.0 (the "License"); 3070 * you may not use this file except in compliance with the License. 3071 * You may obtain a copy of the License at 3072 * 3073 * http://www.apache.org/licenses/LICENSE-2.0 3074 * 3075 * Unless required by applicable law or agreed to in writing, software 3076 * distributed under the License is distributed on an "AS IS" BASIS, 3077 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3078 * See the License for the specific language governing permissions and 3079 * limitations under the License. 3080 ******************************************************************************/ 3081(function() { 3082 "use strict"; 3083 3084 var NS = "cmp"; 3085 var IS = "pdfviewer"; 3086 var SDK_URL = "https://acrobatservices.adobe.com/view-sdk/viewer.js"; 3087 var SDK_READY_EVENT = "adobe_dc_view_sdk.ready"; 3088 3089 var selectors = { 3090 self: "[data-" + NS + '-is="' + IS + '"]', 3091 sdkScript: 'script[src="' + SDK_URL + '"]' 3092 }; 3093 3094 function initSDK() { 3095 var sdkIncluded = document.querySelectorAll(selectors.sdkScript).length > 0; 3096 if (!window.adobe_dc_view_sdk && !sdkIncluded) { 3097 var dcv = document.createElement("script"); 3098 dcv.type = "text/javascript"; 3099 dcv.src = SDK_URL; 3100 document.body.appendChild(dcv); 3101 } 3102 } 3103 3104 function previewPdf(component) { 3105 // prevents multiple initialization 3106 component.removeAttribute("data-" + NS + "-is"); 3107 3108 // add the view sdk to the page 3109 initSDK(); 3110 3111 // manage the preview 3112 if (component.dataset && component.id) { 3113 if (window.AdobeDC && window.AdobeDC.View) { 3114 dcView(component); 3115 } else { 3116 document.addEventListener(SDK_READY_EVENT, function() { 3117 dcView(component); 3118 }); 3119 } 3120 } 3121 } 3122 3123 function dcView(component) { 3124 var adobeDCView = new window.AdobeDC.View({ 3125 clientId: component.dataset.cmpClientId, 3126 divId: component.id + "-content", 3127 reportSuiteId: component.dataset.cmpReportSuiteId, 3128 locale: typeof Granite !== "undefined" && Granite && Granite.I18n ? Granite.I18n.getLocale() : navigator.language || navigator.userLanguage 3129 }); 3130 adobeDCView.previewFile({ 3131 content: { location: { url: component.dataset.cmpDocumentPath } }, 3132 metaData: { fileName: component.dataset.cmpDocumentFileName } 3133 }, JSON.parse(component.dataset.cmpViewerConfigJson)); 3134 } 3135 3136 /** 3137 * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. 3138 * 3139 * @private 3140 */ 3141 function onDocumentReady() { 3142 var elements = document.querySelectorAll(selectors.self); 3143 for (var i = 0; i < elements.length; i++) { 3144 previewPdf(elements[i]); 3145 } 3146 3147 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 3148 var body = document.querySelector("body"); 3149 var observer = new MutationObserver(function(mutations) { 3150 mutations.forEach(function(mutation) { 3151 // needed for IE 3152 var nodesArray = [].slice.call(mutation.addedNodes); 3153 if (nodesArray.length > 0) { 3154 nodesArray.forEach(function(addedNode) { 3155 if (addedNode.querySelectorAll) { 3156 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 3157 elementsArray.forEach(function(element) { 3158 previewPdf(element); 3159 }); 3160 } 3161 }); 3162 } 3163 }); 3164 }); 3165 3166 observer.observe(body, { 3167 subtree: true, 3168 childList: true, 3169 characterData: true 3170 }); 3171 3172 } 3173 3174 if (document.readyState !== "loading") { 3175 onDocumentReady(); 3176 } else { 3177 document.addEventListener("DOMContentLoaded", onDocumentReady); 3178 } 3179}()); 3180 3181 3182/******************************************************************************* 3183 * Copyright 2022 Adobe 3184 * 3185 * Licensed under the Apache License, Version 2.0 (the "License");
3186 * you may not use this file except in compliance with the License. 3187 * You may obtain a copy of the License at 3188 * 3189 * http://www.apache.org/licenses/LICENSE-2.0 3190 * 3191 * Unless required by applicable law or agreed to in writing, software 3192 * distributed under the License is distributed on an "AS IS" BASIS, 3193 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3194 * See the License for the specific language governing permissions and 3195 * limitations under the License. 3196 ******************************************************************************/ 3197(function() { 3198 "use strict"; 3199 3200 var dataLayerEnabled; 3201 var dataLayer; 3202 var dataLayerName; 3203 3204 /** 3205 * Adds Click Event Listener to the main <div> of the Text Components 3206 */ 3207 function addClickEventListenerToTextComponents() { 3208 var componentMainDivs = document.getElementsByClassName("cmp-text"); 3209 3210 for (var i = 0; i < componentMainDivs.length; i++) { 3211 componentMainDivs[i].addEventListener("click", addClickedLinkToDataLayer); 3212 } 3213 } 3214 3215 /** 3216 * Adds clicked link contained by the Text Component to Data Layer 3217 * 3218 * @private 3219 * @param {Object} event The Click event 3220 */ 3221 function addClickedLinkToDataLayer(event) { 3222 var element = event.currentTarget; 3223 var componentId = getDataLayerId(element); 3224 3225 if (event.target.tagName === "A") { 3226 dataLayer.push({ 3227 event: "cmp:click", 3228 eventInfo: { 3229 path: "component." + componentId, 3230 link: event.target.getAttribute("href") 3231 } 3232 }); 3233 } 3234 } 3235 3236 /** 3237 * Parses the dataLayer string and returns the ID 3238 * 3239 * @private 3240 * @param {HTMLElement} item, the Text item 3241 * @returns {String} dataLayerId or undefined 3242 */ 3243 function getDataLayerId(item) { 3244 if (item) { 3245 if (item.dataset.cmpDataLayer) { 3246 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 3247 } else { 3248 return item.id; 3249 } 3250 } 3251 return null; 3252 } 3253 3254 function onDocumentReady() { 3255 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 3256 if (dataLayerEnabled) { 3257 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 3258 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 3259 3260 addClickEventListenerToTextComponents(); 3261 } 3262 } 3263 3264 if (document.readyState !== "loading") { 3265 onDocumentReady(); 3266 } else { 3267 document.addEventListener("DOMContentLoaded", onDocumentReady); 3268 } 3269})(); 3270
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.