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/LICENSE-2.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(function() { 17 "use strict"; 18 const version2 = document.querySelector('html').classList.contains('version2'); 19 20 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 21 if (!containerUtils) { 22 // eslint-disable-next-line no-console 23 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."); 24 } 25 var dataLayerEnabled; 26 var dataLayer; 27 var dataLayerName; 28 var delay = 100; 29 30 var NS = "cmp"; 31 var IS = "accordion"; 32 33 var keyCodes = { 34 ENTER: 13, 35 SPACE: 32, 36 END: 35, 37 HOME: 36, 38 ARROW_LEFT: 37, 39 ARROW_UP: 38, 40 ARROW_RIGHT: 39, 41 ARROW_DOWN: 40 42 }; 43 44 var selectors = { 45 self: "[data-" + NS + '-is="' + IS + '"]' 46 }; 47
48 var cssClasses = { 49 button: { 50 disabled: "cmp-accordion__button--disabled", 51 expanded: "cmp-accordion__button--expanded" 52 }, 53 panel: { 54 hidden: (version2) ? "cmp-accordion__panel--collapse" : "cmp-accordion__panel--hidden", 55 expanded: (version2) ? "cmp-accordion__panel--show" : "cmp-accordion__panel--expanded" 56 } 57 }; 58 59 var dataAttributes = { 60 item: { 61 expanded: "data-cmp-expanded" 62 } 63 }; 64 65 var properties = { 66 /** 67 * Determines whether a single accordion item is forced to be expanded at a time. 68 * Expanding one item will collapse all others. 69 * 70 * @memberof Accordion 71 * @type {Boolean} 72 * @default false 73 */ 74 "singleExpansion": { 75 "default": false, 76 "transform": function(value) { 77 return !(value === null || typeof value === "undefined"); 78 } 79 } 80 }; 81 82 /** 83 * Accordion Configuration. 84 * 85 * @typedef {Object} AccordionConfig Represents an Accordion configuration 86 * @property {HTMLElement} element The HTMLElement representing the Accordion 87 * @property {Object} options The Accordion options 88 */ 89 90 /** 91 * Accordion. 92 * 93 * @class Accordion 94 * @classdesc An interactive Accordion component for toggling panels of related content 95 * @param {AccordionConfig} config The Accordion configuration 96 */ 97 function Accordion(config) { 98 var that = this; 99 100 if (config && config.element) { 101 init(config); 102 } 103 104 /** 105 * Initializes the Accordion. 106 * 107 * @private 108 * @param {AccordionConfig} config The Accordion configuration 109 */ 110 function init(config) { 111 that._config = config; 112 113 // prevents multiple initialization 114 config.element.removeAttribute("data-" + NS + "-is"); 115 116 setupProperties(config.options); 117 cacheElements(config.element); 118 if (that._elements["item"]) { 119 // ensures multiple element types are arrays. 120 that._elements["item"] = Array.isArray(that._elements["item"]) ? that._elements["item"] : [that._elements["item"]]; 121 that._elements["button"] = Array.isArray(that._elements["button"]) ? that._elements["button"] : [that._elements["button"]]; 122 that._elements["panel"] = Array.isArray(that._elements["panel"]) ? that._elements["panel"] : [that._elements["panel"]]; 123 124 if (that._properties.singleExpansion) { 125 var expandedItems = getExpandedItems(); 126 // multiple expanded items annotated, display the last item open. 127 if (expandedItems.length > 1) { 128 toggle(expandedItems.length - 1); 129 } 130 } 131 132 trackInitialView(); 133 refreshItems(); 134 bindEvents(); 135 scrollToDeepLinkIdInAccordion(); 136 } 137 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 138 /* 139 * Editor message handling: 140 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 141 * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component 142 * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data 143 */ 144 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 145 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 146 if (message.data && message.data.type === "cmp-accordion" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 147 if (message.data.operation === "navigate") { 148 // switch to single expansion mode when navigating in edit mode. 149 var singleExpansion = that._properties.singleExpansion; 150 that._properties.singleExpansion = true; 151 toggle(message.data.index); 152 153 // revert to the configured state. 154 that._properties.singleExpansion = singleExpansion; 155 } 156 } 157 }); 158 } 159 } 160 161 /**
162 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 163 * and scrolls the browser to this element. 164 */ 165 function scrollToDeepLinkIdInAccordion() { 166 if (containerUtils) { 167 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 168 if (deepLinkItemIdx > -1) { 169 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 170 if (deepLinkItem && !deepLinkItem.hasAttribute(dataAttributes.item.expanded)) { 171 // if single expansion: close all accordion items 172 if (that._properties.singleExpansion) { 173 for (var j = 0; j < that._elements["item"].length; j++) { 174 if (that._elements["item"][j].hasAttribute(dataAttributes.item.expanded)) { 175 setItemExpanded(that._elements["item"][j], false, true); 176 } 177 } 178 } 179 // expand the accordion item containing the deep link 180 setItemExpanded(deepLinkItem, true, true); 181 } 182 var hashId = window.location.hash.substring(1); 183 if (hashId) { 184 var hashItem = document.querySelector("[id='" + hashId + "']"); 185 if (hashItem) { 186 hashItem.scrollIntoView(); 187 } 188 } 189 } 190 } 191 } 192 193 /** 194 * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. 195 * 196 * @private 197 * @param {HTMLElement} wrapper The Accordion wrapper element 198 */ 199 function cacheElements(wrapper) { 200 that._elements = {}; 201 that._elements.self = wrapper; 202 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 203 204 for (var i = 0; i < hooks.length; i++) { 205 var hook = hooks[i]; 206 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own accordion elements 207 var capitalized = IS; 208 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 209 var key = hook.dataset[NS + "Hook" + capitalized]; 210 if (that._elements[key]) { 211 if (!Array.isArray(that._elements[key])) { 212 var tmp = that._elements[key]; 213 that._elements[key] = [tmp]; 214 } 215 that._elements[key].push(hook); 216 } else { 217 that._elements[key] = hook; 218 } 219 } 220 } 221 } 222 223 /** 224 * Sets up properties for the Accordion based on the passed options. 225 * 226 * @private 227 * @param {Object} options The Accordion options 228 */ 229 function setupProperties(options) { 230 that._properties = {}; 231 232 for (var key in properties) { 233 if (Object.prototype.hasOwnProperty.call(properties, key)) { 234 var property = properties[key]; 235 var value = null; 236 237 if (options && options[key] != null) { 238 value = options[key]; 239 240 // transform the provided option 241 if (property && typeof property.transform === "function") { 242 value = property.transform(value); 243 } 244 } 245 246 if (value === null) { 247 // value still null, take the property default 248 value = properties[key]["default"]; 249 } 250 251 that._properties[key] = value; 252 } 253 } 254 } 255 256 /** 257 * Binds Accordion event handling. 258 * 259 * @private 260 */ 261 function bindEvents() { 262 window.addEventListener("hashchange", scrollToDeepLinkIdInAccordion, false); 263 var buttons = that._elements["button"]; 264 if (buttons) { 265 for (var i = 0; i < buttons.length; i++) { 266 (function(index) { 267 buttons[i].addEventListener("click", function(event) { 268 toggle(index); 269 focusButton(index); 270 }); 271 buttons[i].addEventListener("keydown", function(event) { 272 onButtonKeyDown(event, index); 273 }); 274 })(i); 275 } 276 } 277 } 278 279 /** 280 * Handles button keydown events. 281 * 282 * @private 283 * @param {Object} event The keydown event 284 * @param {Number} index The index of the button triggering the event 285 */ 286 function onButtonKeyDown(event, index) { 287 var lastIndex = that._elements["button"].length - 1; 288 289 switch (event.keyCode) { 290 case keyCodes.ARROW_LEFT: 291 case keyCodes.ARROW_UP: 292 event.preventDefault(); 293 if (index > 0) { 294 focusButton(index - 1); 295 } 296 break; 297 case keyCodes.ARROW_RIGHT: 298 case keyCodes.ARROW_DOWN: 299 event.preventDefault(); 300 if (index < lastIndex) { 301 focusButton(index + 1); 302 } 303 break; 304 case keyCodes.HOME: 305 event.preventDefault(); 306 focusButton(0); 307 break; 308 case keyCodes.END: 309 event.preventDefault(); 310 focusButton(lastIndex); 311 break;
312 case keyCodes.ENTER: 313 case keyCodes.SPACE: 314 event.preventDefault(); 315 toggle(index); 316 focusButton(index); 317 break; 318 default: 319 return; 320 } 321 } 322 323 /** 324 * General handler for toggle of an item. 325 * 326 * @private 327 * @param {Number} index The index of the item to toggle 328 */ 329 function toggle(index) { 330 var item = that._elements["item"][index]; 331 if (item) { 332 if (that._properties.singleExpansion) { 333 // ensure only a single item is expanded if single expansion is enabled. 334 for (var i = 0; i < that._elements["item"].length; i++) { 335 if (that._elements["item"][i] !== item) { 336 var expanded = getItemExpanded(that._elements["item"][i]); 337 if (expanded) { 338 setItemExpanded(that._elements["item"][i], false); 339 } 340 } 341 } 342 } 343 setItemExpanded(item, !getItemExpanded(item)); 344 345 if (dataLayerEnabled) { 346 if (window.customTracking && typeof window.customTracking.trackingFlagGetter === 'function') { 347 if(window.customTracking.trackingFlagGetter()){ 348 var accordionId = that._elements.self.id; 349 var expandedItems = getExpandedItems() 350 .map(function(item) { 351 return getDataLayerId(item); 352 }); 353 354 var uploadPayload = { component: {} }; 355 uploadPayload.component[accordionId] = { shownItems: expandedItems }; 356 357 var removePayload = { component: {} }; 358 removePayload.component[accordionId] = { shownItems: undefined }; 359 360 dataLayer.push(removePayload); 361 dataLayer.push(uploadPayload); 362 } 363 } 364 365 } 366 } 367 } 368 369 /** 370 * Sets an item's expanded state based on the provided flag and refreshes its internals. 371 * 372 * @private 373 * @param {HTMLElement} item The item to mark as expanded, or not expanded 374 * @param {Boolean} expanded true to mark the item expanded, false otherwise 375 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 376 */ 377 function setItemExpanded(item, expanded, keepHash) { 378 if (expanded) { 379 item.setAttribute(dataAttributes.item.expanded, ""); 380 var index = that._elements["item"].indexOf(item); 381 if (!keepHash && containerUtils) { 382 containerUtils.updateUrlHash(that, "item", index); 383 } 384 if (dataLayerEnabled) { 385 if (window.customTracking && typeof window.customTracking.trackingFlagGetter === 'function') { 386 if(window.customTracking.trackingFlagGetter()){ 387 dataLayer.push({ 388 event: "cmp:click", 389 eventInfo: { 390 path: "component." + getDataLayerId(item) 391 } 392 }); 393 } 394 } 395 396 } 397 398 } else { 399 item.removeAttribute(dataAttributes.item.expanded); 400 if (!keepHash && containerUtils) { 401 containerUtils.removeUrlHash(); 402 } 403 if (dataLayerEnabled) { 404 if (window.customTracking && typeof window.customTracking.trackingFlagGetter === 'function') { 405 if(window.customTracking.trackingFlagGetter()){ 406 dataLayer.push({ 407 event: "cmp:hide", 408 eventInfo: { 409 path: "component." + getDataLayerId(item) 410 } 411 }); 412 } 413 } 414 415 } 416 } 417 refreshItem(item); 418 } 419 420 /** 421 * Gets an item's expanded state. 422 * 423 * @private 424 * @param {HTMLElement} item The item for checking its expanded state 425 * @returns {Boolean} true if the item is expanded, false otherwise 426 */ 427 function getItemExpanded(item) { 428 return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; 429 } 430 431 /** 432 * Refreshes an item based on its expanded state. 433 * 434 * @private 435 * @param {HTMLElement} item The item to refresh 436 */ 437 function refreshItem(item) { 438 var expanded = getItemExpanded(item); 439 if (expanded) { 440 expandItem(item); 441 } else { 442 collapseItem(item); 443 } 444 } 445 446 /** 447 * Refreshes all items based on their expanded state. 448 * 449 * @private 450 */ 451 function refreshItems() { 452 for (var i = 0; i < that._elements["item"].length; i++) { 453 refreshItem(that._elements["item"][i]); 454 } 455 } 456 457 /** 458 * Custom tracking function to track initial view of item 459 * 460 * @private 461 */ 462 function initialViewItem(item) { 463 var expanded = getItemExpanded(item); 464 if (expanded) { 465 if (dataLayerEnabled) { 466 dataLayer.push({ 467 event: "cmp:initial", 468 eventInfo: { 469 path: "component." + getDataLayerId(item) 470 } 471 }); 472 // if (window.customTracking && typeof window.customTracking.trackingFlagGetter === 'function') { 473 // if(window.customTracking.trackingFlagGetter()){ 474 // dataLayer.push({ 475 // event: "cmp:initial", 476 // eventInfo: { 477 // path: "component." + getDataLayerId(item) 478 // } 479 // }); 480 // } 481 // } 482 483 } 484 } 485 } 486 487 function trackInitialView() { 488 for (var i = 0; i < that._elements["item"].length; i++) { 489 initialViewItem(that._elements["item"][i]); 490 } 491 } 492 493 /** 494 * Returns all expanded items. 495 * 496 * @private 497 * @returns {HTMLElement[]} The expanded items 498 */ 499 function getExpandedItems() { 500 var expandedItems = []; 501 502 for (var i = 0; i < that._elements["item"].length; i++) { 503 var item = that._elements["item"][i]; 504 var expanded = getItemExpanded(item); 505 if (expanded) { 506 expandedItems.push(item); 507 } 508 } 509 510 return expandedItems; 511 } 512 513 /** 514 * Annotates the item and its internals with 515 * the necessary style and accessibility attributes to indicate it is expanded. 516 * 517 * @private 518 * @param {HTMLElement} item The item to annotate as expanded 519 */ 520 function expandItem(item) { 521 var index = that._elements["item"].indexOf(item); 522 if (index > -1) { 523 var button = that._elements["button"][index]; 524 var panel = that._elements["panel"][index];
525 button.classList.add(cssClasses.button.expanded); 526 button.classList.remove('collapsed'); 527 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 528 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 529 setTimeout(function() { 530 button.setAttribute("aria-expanded", true); 531 }, delay); 532 panel.classList.add(cssClasses.panel.expanded); 533 panel.classList.add('show'); 534 panel.classList.remove(cssClasses.panel.hidden); 535 panel.setAttribute("aria-hidden", false); 536 } 537 } 538 539 /** 540 * Annotates the item and its internals with 541 * the necessary style and accessibility attributes to indicate it is not expanded. 542 * 543 * @private 544 * @param {HTMLElement} item The item to annotate as not expanded 545 */ 546 function collapseItem(item) { 547 var index = that._elements["item"].indexOf(item); 548 if (index > -1) { 549 var button = that._elements["button"][index]; 550 var panel = that._elements["panel"][index]; 551 button.classList.remove(cssClasses.button.expanded); 552 button.classList.add('collapsed'); 553 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 554 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 555 setTimeout(function() { 556 button.setAttribute("aria-expanded", false); 557 }, delay); 558 panel.classList.add(cssClasses.panel.hidden); 559 panel.classList.remove(cssClasses.panel.expanded); 560 panel.classList.remove('show'); 561 panel.setAttribute("aria-hidden", true); 562 } 563 } 564 565 /** 566 * Focuses the button at the provided index. 567 * 568 * @private 569 * @param {Number} index The index of the button to focus 570 */ 571 function focusButton(index) { 572 var button = that._elements["button"][index]; 573 button.focus(); 574 } 575 } 576 577 /** 578 * Reads options data from the Accordion wrapper element, defined via {@code data-cmp-*} data attributes. 579 * 580 * @private 581 * @param {HTMLElement} element The Accordion element to read options data from 582 * @returns {Object} The options read from the component data attributes 583 */ 584 function readData(element) { 585 var data = element.dataset; 586 var options = []; 587 var capitalized = IS; 588 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 589 var reserved = ["is", "hook" + capitalized]; 590 591 for (var key in data) { 592 if (Object.prototype.hasOwnProperty.call(data, key)) { 593 var value = data[key]; 594 595 if (key.indexOf(NS) === 0) { 596 key = key.slice(NS.length); 597 key = key.charAt(0).toLowerCase() + key.substring(1); 598 599 if (reserved.indexOf(key) === -1) { 600 options[key] = value; 601 } 602 } 603 } 604 } 605 606 return options; 607 } 608 609 /** 610 * Parses the dataLayer string and returns the ID 611 * 612 * @private 613 * @param {HTMLElement} item the accordion item 614 * @returns {String} dataLayerId or undefined 615 */ 616 function getDataLayerId(item) { 617 if (item) { 618 if (item.dataset.cmpDataLayer) { 619 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 620 } else { 621 return item.id; 622 } 623 } 624 return null; 625 } 626 627 /** 628 * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. 629 * 630 * @private 631 */ 632 function onDocumentReady() { 633 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 634 if (dataLayerEnabled) { 635 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 636 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 637 } 638 639 var elements = document.querySelectorAll(selectors.self); 640 for (var i = 0; i < elements.length; i++) { 641 new Accordion({ element: elements[i], options: readData(elements[i]) }); 642 } 643 644 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 645 var body = document.querySelector("body"); 646 var observer = new MutationObserver(function(mutations) { 647 mutations.forEach(function(mutation) { 648 // needed for IE 649 var nodesArray = [].slice.call(mutation.addedNodes); 650 if (nodesArray.length > 0) { 651 nodesArray.forEach(function(addedNode) { 652 if (addedNode.querySelectorAll) { 653 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 654 elementsArray.forEach(function(element) { 655 new Accordion({ element: element, options: readData(element) }); 656 }); 657 } 658 }); 659 } 660 }); 661 }); 662 663 observer.observe(body, { 664 subtree: true, 665 childList: true, 666 characterData: true 667 }); 668 } 669 670 if (document.readyState !== "loading") { 671 onDocumentReady(); 672 } else { 673 document.addEventListener("DOMContentLoaded", onDocumentReady); 674 } 675 676 if (containerUtils) { 677 window.addEventListener("load", containerUtils.scrollToAnchor, false); 678 } 679 680}()); 681/******************************************************************************* 682 * Copyright 2019 Adobe 683 * 684 * Licensed under the Apache License, Version 2.0 (the "License");
685 * you may not use this file except in compliance with the License. 686 * You may obtain a copy of the License at 687 * 688 * http://www.apache.org/licenses/LICENSE2.0 689 * 690 * Unless required by applicable law or agreed to in writing, software 691 * distributed under the License is distributed on an "AS IS" BASIS, 692 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 693 * See the License for the specific language governing permissions and 694 * limitations under the License. 695 ******************************************************************************/ 696 697/** 698 * Element.matches() 699 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 700 */ 701if (!Element.prototype.matches) { 702 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 703} 704 705// eslint-disable-next-line valid-jsdoc 706/** 707 * Element.closest() 708 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 709 */ 710if (!Element.prototype.closest) { 711 Element.prototype.closest = function(s) { 712 "use strict"; 713 var el = this; 714 if (!document.documentElement.contains(el)) { 715 return null; 716 } 717 do { 718 if (el.matches(s)) { 719 return el; 720 } 721 el = el.parentElement || el.parentNode; 722 } while (el !== null && el.nodeType === 1); 723 return null; 724 }; 725} 726 727/******************************************************************************* 728 * Copyright 2019 Adobe 729 * 730 * Licensed under the Apache License, Version 2.0 (the "License"); 731 * you may not use this file except in compliance with the License. 732 * You may obtain a copy of the License at 733 * 734 * http://www.apache.org/licenses/LICENSE-2.0 735 * 736 * Unless required by applicable law or agreed to in writing, software 737 * distributed under the License is distributed on an "AS IS" BASIS, 738 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 739 * See the License for the specific language governing permissions and 740 * limitations under the License. 741 ******************************************************************************/ 742(function() { 743 "use strict"; 744 745 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 746 if (!containerUtils) { 747 // eslint-disable-next-line no-console 748 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."); 749 } 750 var dataLayerEnabled; 751 var dataLayer; 752 var dataLayerName; 753 var delay = 100; 754 755 var NS = "cmp"; 756 var IS = "accordion"; 757 758 var keyCodes = { 759 ENTER: 13, 760 SPACE: 32, 761 END: 35, 762 HOME: 36, 763 ARROW_LEFT: 37, 764 ARROW_UP: 38, 765 ARROW_RIGHT: 39, 766 ARROW_DOWN: 40 767 }; 768 769 var selectors = { 770 self: "[data-" + NS + '-is="' + IS + '"]' 771 }; 772
773 var cssClasses = { 774 button: { 775 disabled: "cmp-accordion__button--disabled", 776 expanded: "cmp-accordion__button--expanded" 777 }, 778 panel: { 779 hidden: "cmp-accordion__panel--hidden", 780 expanded: "cmp-accordion__panel--expanded" 781 } 782 }; 783 784 var dataAttributes = { 785 item: { 786 expanded: "data-cmp-expanded" 787 } 788 }; 789 790 var properties = { 791 /** 792 * Determines whether a single accordion item is forced to be expanded at a time. 793 * Expanding one item will collapse all others. 794 * 795 * @memberof Accordion 796 * @type {Boolean} 797 * @default false 798 */ 799 "singleExpansion": { 800 "default": false, 801 "transform": function(value) { 802 return !(value === null || typeof value === "undefined"); 803 } 804 } 805 }; 806 807 /** 808 * Accordion Configuration. 809 * 810 * @typedef {Object} AccordionConfig Represents an Accordion configuration 811 * @property {HTMLElement} element The HTMLElement representing the Accordion 812 * @property {Object} options The Accordion options 813 */ 814 815 /** 816 * Accordion. 817 * 818 * @class Accordion 819 * @classdesc An interactive Accordion component for toggling panels of related content 820 * @param {AccordionConfig} config The Accordion configuration 821 */ 822 function Accordion(config) { 823 var that = this; 824 825 if (config && config.element) { 826 init(config); 827 } 828 829 /** 830 * Initializes the Accordion. 831 * 832 * @private 833 * @param {AccordionConfig} config The Accordion configuration 834 */ 835 function init(config) { 836 that._config = config; 837 838 // prevents multiple initialization 839 config.element.removeAttribute("data-" + NS + "-is"); 840 841 setupProperties(config.options); 842 cacheElements(config.element); 843 844 if (that._elements["item"]) { 845 // ensures multiple element types are arrays. 846 that._elements["item"] = Array.isArray(that._elements["item"]) ? that._elements["item"] : [that._elements["item"]]; 847 that._elements["button"] = Array.isArray(that._elements["button"]) ? that._elements["button"] : [that._elements["button"]]; 848 that._elements["panel"] = Array.isArray(that._elements["panel"]) ? that._elements["panel"] : [that._elements["panel"]]; 849 850 if (that._properties.singleExpansion) { 851 var expandedItems = getExpandedItems(); 852 // multiple expanded items annotated, display the last item open. 853 if (expandedItems.length > 1) { 854 toggle(expandedItems.length - 1); 855 } 856 } 857 858 refreshItems(); 859 bindEvents(); 860 scrollToDeepLinkIdInAccordion(); 861 } 862 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 863 /* 864 * Editor message handling: 865 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 866 * - check that the message data panel container type is correct and that the id (path) matches this specific Accordion component 867 * - if so, route the "navigate" operation to enact a navigation of the Accordion based on index data 868 */ 869 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 870 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 871 if (message.data && message.data.type === "cmp-accordion" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 872 if (message.data.operation === "navigate") { 873 // switch to single expansion mode when navigating in edit mode. 874 var singleExpansion = that._properties.singleExpansion; 875 that._properties.singleExpansion = true; 876 toggle(message.data.index); 877 878 // revert to the configured state. 879 that._properties.singleExpansion = singleExpansion; 880 } 881 } 882 }); 883 } 884 } 885 886 /**
887 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 888 * and scrolls the browser to this element. 889 */ 890 function scrollToDeepLinkIdInAccordion() { 891 if (containerUtils) { 892 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 893 if (deepLinkItemIdx > -1) { 894 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 895 if (deepLinkItem && !deepLinkItem.hasAttribute(dataAttributes.item.expanded)) { 896 // if single expansion: close all accordion items 897 if (that._properties.singleExpansion) { 898 for (var j = 0; j < that._elements["item"].length; j++) { 899 if (that._elements["item"][j].hasAttribute(dataAttributes.item.expanded)) { 900 setItemExpanded(that._elements["item"][j], false, true); 901 } 902 } 903 } 904 // expand the accordion item containing the deep link 905 setItemExpanded(deepLinkItem, true, true); 906 } 907 var hashId = window.location.hash.substring(1); 908 if (hashId) { 909 var hashItem = document.querySelector("[id='" + hashId + "']"); 910 if (hashItem) { 911 hashItem.scrollIntoView(); 912 } 913 } 914 } 915 } 916 } 917 918 /** 919 * Caches the Accordion elements as defined via the {@code data-accordion-hook="ELEMENT_NAME"} markup API. 920 * 921 * @private 922 * @param {HTMLElement} wrapper The Accordion wrapper element 923 */ 924 function cacheElements(wrapper) { 925 that._elements = {}; 926 that._elements.self = wrapper; 927 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 928 929 for (var i = 0; i < hooks.length; i++) { 930 var hook = hooks[i]; 931 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own accordion elements 932 var capitalized = IS; 933 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 934 var key = hook.dataset[NS + "Hook" + capitalized]; 935 if (that._elements[key]) { 936 if (!Array.isArray(that._elements[key])) { 937 var tmp = that._elements[key]; 938 that._elements[key] = [tmp]; 939 } 940 that._elements[key].push(hook); 941 } else { 942 that._elements[key] = hook; 943 } 944 } 945 } 946 } 947 948 /** 949 * Sets up properties for the Accordion based on the passed options. 950 * 951 * @private 952 * @param {Object} options The Accordion options 953 */ 954 function setupProperties(options) { 955 that._properties = {}; 956 957 for (var key in properties) { 958 if (Object.prototype.hasOwnProperty.call(properties, key)) { 959 var property = properties[key]; 960 var value = null; 961 962 if (options && options[key] != null) { 963 value = options[key]; 964 965 // transform the provided option 966 if (property && typeof property.transform === "function") { 967 value = property.transform(value); 968 } 969 } 970 971 if (value === null) { 972 // value still null, take the property default 973 value = properties[key]["default"]; 974 } 975 976 that._properties[key] = value; 977 } 978 } 979 } 980 981 /** 982 * Binds Accordion event handling. 983 * 984 * @private 985 */ 986 function bindEvents() { 987 window.addEventListener("hashchange", scrollToDeepLinkIdInAccordion, false); 988 var buttons = that._elements["button"]; 989 if (buttons) { 990 for (var i = 0; i < buttons.length; i++) { 991 (function(index) { 992 buttons[i].addEventListener("click", function(event) { 993 toggle(index); 994 focusButton(index); 995 }); 996 buttons[i].addEventListener("keydown", function(event) { 997 onButtonKeyDown(event, index); 998 }); 999 })(i); 1000 } 1001 } 1002 } 1003 1004 /** 1005 * Handles button keydown events. 1006 * 1007 * @private 1008 * @param {Object} event The keydown event 1009 * @param {Number} index The index of the button triggering the event 1010 */ 1011 function onButtonKeyDown(event, index) { 1012 var lastIndex = that._elements["button"].length - 1; 1013 1014 switch (event.keyCode) { 1015 case keyCodes.ARROW_LEFT: 1016 case keyCodes.ARROW_UP: 1017 event.preventDefault(); 1018 if (index > 0) { 1019 focusButton(index - 1); 1020 } 1021 break; 1022 case keyCodes.ARROW_RIGHT: 1023 case keyCodes.ARROW_DOWN: 1024 event.preventDefault(); 1025 if (index < lastIndex) { 1026 focusButton(index + 1); 1027 } 1028 break; 1029 case keyCodes.HOME: 1030 event.preventDefault(); 1031 focusButton(0); 1032 break; 1033 case keyCodes.END: 1034 event.preventDefault(); 1035 focusButton(lastIndex); 1036 break;
1037 case keyCodes.ENTER: 1038 case keyCodes.SPACE: 1039 event.preventDefault(); 1040 toggle(index); 1041 focusButton(index); 1042 break; 1043 default: 1044 return; 1045 } 1046 } 1047 1048 /** 1049 * General handler for toggle of an item. 1050 * 1051 * @private 1052 * @param {Number} index The index of the item to toggle 1053 */ 1054 function toggle(index) { 1055 var item = that._elements["item"][index]; 1056 if (item) { 1057 if (that._properties.singleExpansion) { 1058 // ensure only a single item is expanded if single expansion is enabled. 1059 for (var i = 0; i < that._elements["item"].length; i++) { 1060 if (that._elements["item"][i] !== item) { 1061 var expanded = getItemExpanded(that._elements["item"][i]); 1062 if (expanded) { 1063 setItemExpanded(that._elements["item"][i], false); 1064 } 1065 } 1066 } 1067 } 1068 setItemExpanded(item, !getItemExpanded(item)); 1069 1070 if (dataLayerEnabled) { 1071 var accordionId = that._elements.self.id; 1072 var expandedItems = getExpandedItems() 1073 .map(function(item) { 1074 return getDataLayerId(item); 1075 }); 1076 1077 var uploadPayload = { component: {} }; 1078 uploadPayload.component[accordionId] = { shownItems: expandedItems }; 1079 1080 var removePayload = { component: {} }; 1081 removePayload.component[accordionId] = { shownItems: undefined }; 1082 1083 dataLayer.push(removePayload); 1084 dataLayer.push(uploadPayload); 1085 } 1086 } 1087 } 1088 1089 /** 1090 * Sets an item's expanded state based on the provided flag and refreshes its internals. 1091 * 1092 * @private 1093 * @param {HTMLElement} item The item to mark as expanded, or not expanded 1094 * @param {Boolean} expanded true to mark the item expanded, false otherwise 1095 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1096 */ 1097 function setItemExpanded(item, expanded, keepHash) { 1098 if (expanded) { 1099 item.setAttribute(dataAttributes.item.expanded, ""); 1100 var index = that._elements["item"].indexOf(item); 1101 if (!keepHash && containerUtils) { 1102 containerUtils.updateUrlHash(that, "item", index); 1103 } 1104 if (dataLayerEnabled) { 1105 dataLayer.push({ 1106 event: "cmp:show", 1107 eventInfo: { 1108 path: "component." + getDataLayerId(item) 1109 } 1110 }); 1111 } 1112 1113 } else { 1114 item.removeAttribute(dataAttributes.item.expanded); 1115 if (!keepHash && containerUtils) { 1116 containerUtils.removeUrlHash(); 1117 } 1118 if (dataLayerEnabled) { 1119 dataLayer.push({ 1120 event: "cmp:hide", 1121 eventInfo: { 1122 path: "component." + getDataLayerId(item) 1123 } 1124 }); 1125 } 1126 } 1127 refreshItem(item); 1128 } 1129 1130 /** 1131 * Gets an item's expanded state. 1132 * 1133 * @private 1134 * @param {HTMLElement} item The item for checking its expanded state 1135 * @returns {Boolean} true if the item is expanded, false otherwise 1136 */ 1137 function getItemExpanded(item) { 1138 return item && item.dataset && item.dataset["cmpExpanded"] !== undefined; 1139 } 1140 1141 /** 1142 * Refreshes an item based on its expanded state. 1143 * 1144 * @private 1145 * @param {HTMLElement} item The item to refresh 1146 */ 1147 function refreshItem(item) { 1148 var expanded = getItemExpanded(item); 1149 if (expanded) { 1150 expandItem(item); 1151 } else { 1152 collapseItem(item); 1153 } 1154 } 1155 1156 /** 1157 * Refreshes all items based on their expanded state. 1158 * 1159 * @private 1160 */ 1161 function refreshItems() { 1162 for (var i = 0; i < that._elements["item"].length; i++) { 1163 refreshItem(that._elements["item"][i]); 1164 } 1165 } 1166 1167 /** 1168 * Returns all expanded items. 1169 * 1170 * @private 1171 * @returns {HTMLElement[]} The expanded items 1172 */ 1173 function getExpandedItems() { 1174 var expandedItems = []; 1175 1176 for (var i = 0; i < that._elements["item"].length; i++) { 1177 var item = that._elements["item"][i]; 1178 var expanded = getItemExpanded(item); 1179 if (expanded) { 1180 expandedItems.push(item); 1181 } 1182 } 1183 1184 return expandedItems; 1185 } 1186 1187 /** 1188 * Annotates the item and its internals with 1189 * the necessary style and accessibility attributes to indicate it is expanded. 1190 * 1191 * @private 1192 * @param {HTMLElement} item The item to annotate as expanded 1193 */ 1194 function expandItem(item) { 1195 var index = that._elements["item"].indexOf(item); 1196 if (index > -1) { 1197 var button = that._elements["button"][index]; 1198 var panel = that._elements["panel"][index];
1199 button.classList.add(cssClasses.button.expanded); 1200 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 1201 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 1202 setTimeout(function() { 1203 button.setAttribute("aria-expanded", true); 1204 }, delay); 1205 panel.classList.add(cssClasses.panel.expanded); 1206 panel.classList.remove(cssClasses.panel.hidden); 1207 panel.setAttribute("aria-hidden", false); 1208 } 1209 } 1210 1211 /** 1212 * Annotates the item and its internals with 1213 * the necessary style and accessibility attributes to indicate it is not expanded. 1214 * 1215 * @private 1216 * @param {HTMLElement} item The item to annotate as not expanded 1217 */ 1218 function collapseItem(item) { 1219 var index = that._elements["item"].indexOf(item); 1220 if (index > -1) { 1221 var button = that._elements["button"][index]; 1222 var panel = that._elements["panel"][index]; 1223 button.classList.remove(cssClasses.button.expanded); 1224 // used to fix some known screen readers issues in reading the correct state of the 'aria-expanded' attribute 1225 // e.g. https://bugs.webkit.org/show_bug.cgi?id=210934 1226 setTimeout(function() { 1227 button.setAttribute("aria-expanded", false); 1228 }, delay); 1229 panel.classList.add(cssClasses.panel.hidden); 1230 panel.classList.remove(cssClasses.panel.expanded); 1231 panel.setAttribute("aria-hidden", true); 1232 } 1233 } 1234 1235 /** 1236 * Focuses the button at the provided index. 1237 * 1238 * @private 1239 * @param {Number} index The index of the button to focus 1240 */ 1241 function focusButton(index) { 1242 var button = that._elements["button"][index]; 1243 button.focus(); 1244 } 1245 } 1246 1247 /** 1248 * Reads options data from the Accordion wrapper element, defined via {@code data-cmp-*} data attributes. 1249 * 1250 * @private 1251 * @param {HTMLElement} element The Accordion element to read options data from 1252 * @returns {Object} The options read from the component data attributes 1253 */ 1254 function readData(element) { 1255 var data = element.dataset; 1256 var options = []; 1257 var capitalized = IS; 1258 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1259 var reserved = ["is", "hook" + capitalized]; 1260 1261 for (var key in data) { 1262 if (Object.prototype.hasOwnProperty.call(data, key)) { 1263 var value = data[key]; 1264 1265 if (key.indexOf(NS) === 0) { 1266 key = key.slice(NS.length); 1267 key = key.charAt(0).toLowerCase() + key.substring(1); 1268 1269 if (reserved.indexOf(key) === -1) { 1270 options[key] = value; 1271 } 1272 } 1273 } 1274 } 1275 1276 return options; 1277 } 1278 1279 /** 1280 * Parses the dataLayer string and returns the ID 1281 * 1282 * @private 1283 * @param {HTMLElement} item the accordion item 1284 * @returns {String} dataLayerId or undefined 1285 */ 1286 function getDataLayerId(item) { 1287 if (item) { 1288 if (item.dataset.cmpDataLayer) { 1289 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 1290 } else { 1291 return item.id; 1292 } 1293 } 1294 return null; 1295 } 1296 1297 /** 1298 * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. 1299 * 1300 * @private 1301 */ 1302 function onDocumentReady() { 1303 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 1304 if (dataLayerEnabled) { 1305 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 1306 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 1307 } 1308 1309 var elements = document.querySelectorAll(selectors.self); 1310 for (var i = 0; i < elements.length; i++) { 1311 new Accordion({ element: elements[i], options: readData(elements[i]) }); 1312 } 1313 1314 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 1315 var body = document.querySelector("body"); 1316 var observer = new MutationObserver(function(mutations) { 1317 mutations.forEach(function(mutation) { 1318 // needed for IE 1319 var nodesArray = [].slice.call(mutation.addedNodes); 1320 if (nodesArray.length > 0) { 1321 nodesArray.forEach(function(addedNode) { 1322 if (addedNode.querySelectorAll) { 1323 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 1324 elementsArray.forEach(function(element) { 1325 new Accordion({ element: element, options: readData(element) }); 1326 }); 1327 } 1328 }); 1329 } 1330 }); 1331 }); 1332 1333 observer.observe(body, { 1334 subtree: true, 1335 childList: true, 1336 characterData: true 1337 }); 1338 } 1339 1340 if (document.readyState !== "loading") { 1341 onDocumentReady(); 1342 } else { 1343 document.addEventListener("DOMContentLoaded", onDocumentReady); 1344 } 1345 1346 if (containerUtils) { 1347 window.addEventListener("load", containerUtils.scrollToAnchor, false); 1348 } 1349 1350}()); 1351 1352/******************************************************************************* 1353 * Copyright 2018 Adobe 1354 * 1355 * Licensed under the Apache License, Version 2.0 (the "License");
1356 * you may not use this file except in compliance with the License. 1357 * You may obtain a copy of the License at 1358 * 1359 * http://www.apache.org/licenses/LICENSE2.0 1360 * 1361 * Unless required by applicable law or agreed to in writing, software 1362 * distributed under the License is distributed on an "AS IS" BASIS, 1363 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1364 * See the License for the specific language governing permissions and 1365 * limitations under the License. 1366 ******************************************************************************/ 1367 1368/** 1369 * Element.matches() 1370 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 1371 */ 1372if (!Element.prototype.matches) { 1373 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 1374} 1375 1376// eslint-disable-next-line valid-jsdoc 1377/** 1378 * Element.closest() 1379 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 1380 */ 1381if (!Element.prototype.closest) { 1382 Element.prototype.closest = function(s) { 1383 "use strict"; 1384 var el = this; 1385 if (!document.documentElement.contains(el)) { 1386 return null; 1387 } 1388 do { 1389 if (el.matches(s)) { 1390 return el; 1391 } 1392 el = el.parentElement || el.parentNode; 1393 } while (el !== null && el.nodeType === 1); 1394 return null; 1395 }; 1396} 1397/******************************************************************************* 1398 * Copyright 2018 Adobe 1399 * 1400 * Licensed under the Apache License, Version 2.0 (the "License"); 1401 * you may not use this file except in compliance with the License. 1402 * You may obtain a copy of the License at 1403 * 1404 * http://www.apache.org/licenses/LICENSE-2.0 1405 * 1406 * Unless required by applicable law or agreed to in writing, software 1407 * distributed under the License is distributed on an "AS IS" BASIS, 1408 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1409 * See the License for the specific language governing permissions and 1410 * limitations under the License. 1411 ******************************************************************************/ 1412/* global 1413 CQ 1414 */ 1415 (function() { 1416 "use strict"; 1417 1418 const version2 = document.querySelector('html').classList.contains('version2'); 1419 1420 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 1421 if (!containerUtils) { 1422 // eslint-disable-next-line no-console 1423 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."); 1424 } 1425 var dataLayerEnabled; 1426 var dataLayer; 1427 var dataLayerName; 1428 1429 var NS = "cmp"; 1430 var IS = "tabs"; 1431 1432 var keyCodes = { 1433 END: 35, 1434 HOME: 36, 1435 ARROW_LEFT: 37, 1436 ARROW_UP: 38, 1437 ARROW_RIGHT: 39, 1438 ARROW_DOWN: 40 1439 }; 1440 1441 var selectors = { 1442 self: "[data-" + NS + '-is="' + IS + '"]', 1443 active: { 1444 tab: (version2) ? "active" : "cmp-tabs__tab--active", 1445 tabpanel: (version2) ? "active" : "cmp-tabs__tabpanel--active" 1446 } 1447 }; 1448 1449 /** 1450 * Tabs Configuration 1451 * 1452 * @typedef {Object} TabsConfig Represents a Tabs configuration 1453 * @property {HTMLElement} element The HTMLElement representing the Tabs 1454 * @property {Object} options The Tabs options 1455 */ 1456 1457 /** 1458 * Tabs 1459 * 1460 * @class Tabs 1461 * @classdesc An interactive Tabs component for navigating a list of tabs 1462 * @param {TabsConfig} config The Tabs configuration 1463 */ 1464 function Tabs(config) { 1465 var that = this; 1466 1467 if (config && config.element) { 1468 init(config); 1469 } 1470 1471 /** 1472 * Initializes the Tabs 1473 * 1474 * @private 1475 * @param {TabsConfig} config The Tabs configuration 1476 */ 1477 function init(config) { 1478 that._config = config; 1479 1480 // prevents multiple initialization 1481 config.element.removeAttribute("data-" + NS + "-is"); 1482 1483 cacheElements(config.element); 1484 that._active = getActiveIndex(that._elements["tab"]); 1485 1486 if (that._elements.tabpanel) { 1487 refreshActive(); 1488 bindEvents(); 1489 scrollToDeepLinkIdInTabs(); 1490 } 1491 1492 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 1493 /* 1494 * Editor message handling: 1495 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame
1496 * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component 1497 * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data 1498 */ 1499 CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 1500 CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 1501 if (message.data && message.data.type === "cmp-tabs" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 1502 if (message.data.operation === "navigate") { 1503 navigate(message.data.index); 1504 } 1505 } 1506 }); 1507 } 1508 } 1509 1510 /** 1511 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 1512 * and scrolls the browser to this element. 1513 */ 1514 function scrollToDeepLinkIdInTabs() { 1515 if (containerUtils) { 1516 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "tab", "tabpanel"); 1517 if (deepLinkItemIdx > -1) { 1518 var deepLinkItem = that._elements["tab"][deepLinkItemIdx]; 1519 if (deepLinkItem && that._elements["tab"][that._active].id !== deepLinkItem.id) { 1520 navigateAndFocusTab(deepLinkItemIdx, true); 1521 } 1522 var hashId = window.location.hash.substring(1); 1523 if (hashId) { 1524 var hashItem = document.querySelector("[id='" + hashId + "']"); 1525 if (hashItem) { 1526 hashItem.scrollIntoView(); 1527 } 1528 } 1529 } 1530 } 1531 } 1532 1533 /** 1534 * Returns the index of the active tab, if no tab is active returns 0 1535 * 1536 * @param {Array} tabs Tab elements 1537 * @returns {Number} Index of the active tab, 0 if none is active 1538 */ 1539 function getActiveIndex(tabs) { 1540 if (tabs) { 1541 for (var i = 0; i < tabs.length; i++) { 1542 if (tabs[i].classList.contains(selectors.active.tab)) { 1543 return i; 1544 } 1545 } 1546 } 1547 return 0; 1548 } 1549 1550 /** 1551 * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API 1552 * 1553 * @private 1554 * @param {HTMLElement} wrapper The Tabs wrapper element 1555 */ 1556 function cacheElements(wrapper) { 1557 that._elements = {}; 1558 that._elements.self = wrapper; 1559 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 1560 1561 for (var i = 0; i < hooks.length; i++) { 1562 var hook = hooks[i]; 1563 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own tab elements 1564 var capitalized = IS; 1565 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1566 var key = hook.dataset[NS + "Hook" + capitalized]; 1567 if (that._elements[key]) { 1568 if (!Array.isArray(that._elements[key])) { 1569 var tmp = that._elements[key]; 1570 that._elements[key] = [tmp]; 1571 } 1572 that._elements[key].push(hook); 1573 } else { 1574 that._elements[key] = hook; 1575 } 1576 } 1577 } 1578 } 1579 1580 /** 1581 * Binds Tabs event handling 1582 * 1583 * @private 1584 */ 1585 function bindEvents() { 1586 if(version2) { return; } 1587 1588 window.addEventListener("hashchange", scrollToDeepLinkIdInTabs, false); 1589 var tabs = that._elements["tab"]; 1590 if (tabs) { 1591 for (var i = 0; i < tabs.length; i++) { 1592 (function(index) { 1593 tabs[i].addEventListener("click", function(event) { 1594 navigateAndFocusTab(index); 1595 }); 1596 tabs[i].addEventListener("keydown", function(event) { 1597 onKeyDown(event); 1598 }); 1599 })(i); 1600 } 1601 } 1602 } 1603 1604 /** 1605 * Handles tab keydown events 1606 * 1607 * @private 1608 * @param {Object} event The keydown event 1609 */ 1610 function onKeyDown(event) { 1611 var index = that._active; 1612 var lastIndex = that._elements["tab"].length - 1; 1613 if(version2){ 1614 switch (event.keyCode) { 1615 case keyCodes.ARROW_LEFT: 1616 case keyCodes.ARROW_UP: 1617 case keyCodes.ARROW_RIGHT: 1618 case keyCodes.ARROW_DOWN: 1619 case keyCodes.HOME: 1620 case keyCodes.END: 1621 event.preventDefault(); 1622 removeActive(); 1623 break; 1624 default: 1625 return; 1626 } 1627 } else { 1628 switch (event.keyCode) { 1629 case keyCodes.ARROW_LEFT: 1630 case keyCodes.ARROW_UP: 1631 event.preventDefault(); 1632 if (index > 0) { 1633 navigateAndFocusTab(index - 1); 1634 } 1635 break; 1636 case keyCodes.ARROW_RIGHT: 1637 case keyCodes.ARROW_DOWN: 1638 event.preventDefault(); 1639 if (index < lastIndex) { 1640 navigateAndFocusTab(index + 1); 1641 } 1642 break; 1643 case keyCodes.HOME: 1644 event.preventDefault(); 1645 navigateAndFocusTab(0); 1646 break; 1647 case keyCodes.END: 1648 event.preventDefault(); 1649 navigateAndFocusTab(lastIndex); 1650 break; 1651 default: 1652 return; 1653 } 1654 } 1655 } 1656 1657 /** 1658 * Refreshes the tab markup based on the current {@code Tabs#_active} index 1659 * 1660 * @private 1661 */ 1662 function refreshActive() { 1663 var tabpanels = that._elements["tabpanel"]; 1664 var tabs = that._elements["tab"]; 1665 var dropdown = that._elements["dropdown"]; 1666 if(dropdown){ 1667 var dropdownText = dropdown.querySelector('.dropdown-text'); 1668 } 1669 1670 if (tabpanels) { 1671 if (Array.isArray(tabpanels)) { 1672 for (var i = 0; i < tabpanels.length; i++) { 1673 if (i === parseInt(that._active)) { 1674 tabpanels[i].classList.add(selectors.active.tabpanel); 1675 tabpanels[i].classList.add('show'); 1676 tabpanels[i].removeAttribute("aria-hidden"); 1677 tabs[i].classList.add(selectors.active.tab); 1678 tabs[i].setAttribute("aria-selected", true); 1679 tabs[i].setAttribute("tabindex", "0"); 1680 if(dropdown && dropdownText){ 1681 dropdownText.innerText = tabs[i].innerText.trim(); 1682 } 1683 } else { 1684 tabpanels[i].classList.remove(selectors.active.tabpanel); 1685 tabpanels[i].setAttribute("aria-hidden", true); 1686 tabs[i].classList.remove(selectors.active.tab); 1687 tabs[i].setAttribute("aria-selected", false); 1688 tabs[i].setAttribute("tabindex", "-1"); 1689 } 1690 } 1691 } else { 1692 // only one tab 1693 tabpanels.classList.add(selectors.active.tabpanel); 1694 tabs.classList.add(selectors.active.tab); 1695 if(dropdown && dropdownText){ 1696 dropdownText.innerText = tabs.innerText.trim(); 1697 } 1698 } 1699 } 1700 } 1701 1702 function removeActive() { 1703 var tabpanels = that._elements["tabpanel"]; 1704 var tabs = that._elements["tab"]; 1705 1706 if (tabpanels) { 1707 if (Array.isArray(tabpanels)) { 1708 for (var i = 0; i < tabpanels.length; i++) { 1709 1710 tabpanels[i].classList.remove(selectors.active.tabpanel); 1711 tabpanels[i].setAttribute("aria-hidden", true); 1712 tabs[i].classList.remove(selectors.active.tab); 1713 tabs[i].setAttribute("aria-selected", false); 1714 tabs[i].setAttribute("tabindex", "-1"); 1715 1716 } 1717 } else { 1718 // only one tab 1719 tabpanels.classList.remove(selectors.active.tabpanel); 1720 tabs.classList.remove(selectors.active.tab); 1721 } 1722 } 1723 } 1724 1725 /** 1726 * Focuses the element and prevents scrolling the element into view 1727 * 1728 * @param {HTMLElement} element Element to focus 1729 */ 1730 function focusWithoutScroll(element) { 1731 var x = window.scrollX || window.pageXOffset; 1732 var y = window.scrollY || window.pageYOffset; 1733 element.focus(); 1734 window.scrollTo(x, y); 1735 } 1736 1737 /** 1738 * Navigates to the tab at the provided index 1739 * 1740 * @private 1741 * @param {Number} index The index of the tab to navigate to 1742 */ 1743 function navigate(index) { 1744 that._active = index; 1745 refreshActive(); 1746 } 1747 1748 /** 1749 * Navigates to the item at the provided index and ensures the active tab gains focus 1750 * 1751 * @private 1752 * @param {Number} index The index of the item to navigate to 1753 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 1754 */ 1755 function navigateAndFocusTab(index, keepHash) { 1756 var exActive = that._active; 1757 if (!keepHash && containerUtils) { 1758 containerUtils.updateUrlHash(that, "tab", index); 1759 } 1760 navigate(index); 1761 focusWithoutScroll(that._elements["tab"][index]); 1762 1763 if (dataLayerEnabled) { 1764 if (window.customTracking && typeof window.customTracking.trackingFlagGetter === 'function') { 1765 if(window.customTracking.trackingFlagGetter()){ 1766 var activeItem = getDataLayerId(that._elements.tabpanel[index]); 1767 var exActiveItem = getDataLayerId(that._elements.tabpanel[exActive]); 1768 1769 dataLayer.push({ 1770 event: "cmp:show", 1771 eventInfo: { 1772 path: "component." + activeItem 1773 } 1774 }); 1775 1776 dataLayer.push({ 1777 event: "cmp:hide", 1778 eventInfo: { 1779 path: "component." + exActiveItem 1780 } 1781 }); 1782 1783 var tabsId = that._elements.self.id; 1784 var uploadPayload = { component: {} }; 1785 uploadPayload.component[tabsId] = { shownItems: [activeItem] }; 1786 1787 var removePayload = { component: {} }; 1788 removePayload.component[tabsId] = { shownItems: undefined }; 1789 1790 dataLayer.push(removePayload); 1791 dataLayer.push(uploadPayload); 1792 } 1793 } 1794 } 1795 } 1796 } 1797 1798 /**
1799 * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes 1800 * 1801 * @private 1802 * @param {HTMLElement} element The Tabs element to read options data from 1803 * @returns {Object} The options read from the component data attributes 1804 */ 1805 function readData(element) { 1806 var data = element.dataset; 1807 var options = []; 1808 var capitalized = IS; 1809 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 1810 var reserved = ["is", "hook" + capitalized]; 1811 1812 for (var key in data) { 1813 if (Object.prototype.hasOwnProperty.call(data, key)) { 1814 var value = data[key]; 1815 1816 if (key.indexOf(NS) === 0) { 1817 key = key.slice(NS.length); 1818 key = key.charAt(0).toLowerCase() + key.substring(1); 1819 1820 if (reserved.indexOf(key) === -1) { 1821 options[key] = value; 1822 } 1823 } 1824 } 1825 } 1826 1827 return options; 1828 } 1829 1830 /** 1831 * Parses the dataLayer string and returns the ID 1832 * 1833 * @private 1834 * @param {HTMLElement} item the accordion item 1835 * @returns {String} dataLayerId or undefined 1836 */ 1837 function getDataLayerId(item) { 1838 if (item) { 1839 if (item.dataset.cmpDataLayer) { 1840 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 1841 } else { 1842 return item.id; 1843 } 1844 } 1845 return null; 1846 } 1847 1848 /** 1849 * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. 1850 * 1851 * @private 1852 */ 1853 function onDocumentReady() { 1854 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 1855 if (dataLayerEnabled) { 1856 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 1857 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 1858 } 1859 1860 var elements = document.querySelectorAll(selectors.self); 1861 for (var i = 0; i < elements.length; i++) { 1862 new Tabs({ element: elements[i], options: readData(elements[i]) }); 1863 } 1864 1865 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 1866 var body = document.querySelector("body"); 1867 var observer = new MutationObserver(function(mutations) { 1868 mutations.forEach(function(mutation) { 1869 // needed for IE 1870 var nodesArray = [].slice.call(mutation.addedNodes); 1871 if (nodesArray.length > 0) { 1872 nodesArray.forEach(function(addedNode) { 1873 if (addedNode.querySelectorAll) { 1874 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 1875 elementsArray.forEach(function(element) { 1876 new Tabs({ element: element, options: readData(element) }); 1877 }); 1878 } 1879 }); 1880 } 1881 }); 1882 }); 1883 1884 observer.observe(body, { 1885 subtree: true, 1886 childList: true, 1887 characterData: true 1888 }); 1889 } 1890 1891 if (document.readyState !== "loading") { 1892 onDocumentReady(); 1893 } else { 1894 document.addEventListener("DOMContentLoaded", onDocumentReady); 1895 } 1896 1897 if (containerUtils) { 1898 window.addEventListener("load", containerUtils.scrollToAnchor, false); 1899 } 1900 1901 }()); 1902/******************************************************************************* 1903 * Copyright 2018 Adobe 1904 * 1905 * Licensed under the Apache License, Version 2.0 (the "License"); 1906 * you may not use this file except in compliance with the License. 1907 * You may obtain a copy of the License at 1908 * 1909 * http://www.apache.org/licenses/LICENSE2.0 1910 * 1911 * Unless required by applicable law or agreed to in writing, software 1912 * distributed under the License is distributed on an "AS IS" BASIS, 1913 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1914 * See the License for the specific language governing permissions and 1915 * limitations under the License. 1916 ******************************************************************************/ 1917 1918/** 1919 * Element.matches() 1920 * https://developer.mozilla.org/enUS/docs/Web/API/Element/matches#Polyfill 1921 */ 1922if (!Element.prototype.matches) { 1923 Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector; 1924} 1925 1926// eslint-disable-next-line valid-jsdoc 1927/** 1928 * Element.closest() 1929 * https://developer.mozilla.org/enUS/docs/Web/API/Element/closest#Polyfill 1930 */ 1931if (!Element.prototype.closest) { 1932 Element.prototype.closest = function(s) { 1933 "use strict"; 1934 var el = this; 1935 if (!document.documentElement.contains(el)) { 1936 return null; 1937 } 1938 do { 1939 if (el.matches(s)) { 1940 return el; 1941 } 1942 el = el.parentElement || el.parentNode; 1943 } while (el !== null && el.nodeType === 1); 1944 return null; 1945 }; 1946} 1947 1948/******************************************************************************* 1949 * Copyright 2018 Adobe 1950 * 1951 * Licensed under the Apache License, Version 2.0 (the "License");
1952 * you may not use this file except in compliance with the License. 1953 * You may obtain a copy of the License at 1954 * 1955 * http://www.apache.org/licenses/LICENSE-2.0 1956 * 1957 * Unless required by applicable law or agreed to in writing, software 1958 * distributed under the License is distributed on an "AS IS" BASIS, 1959 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1960 * See the License for the specific language governing permissions and 1961 * limitations under the License. 1962 ******************************************************************************/ 1963/* global 1964 CQ 1965 */ 1966(function() { 1967 "use strict"; 1968 1969 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 1970 if (!containerUtils) { 1971 // eslint-disable-next-line no-console 1972 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."); 1973 } 1974 var dataLayerEnabled; 1975 var dataLayer; 1976 var dataLayerName; 1977 1978 var NS = "cmp"; 1979 var IS = "tabs"; 1980 1981 var keyCodes = { 1982 END: 35, 1983 HOME: 36, 1984 ARROW_LEFT: 37, 1985 ARROW_UP: 38, 1986 ARROW_RIGHT: 39, 1987 ARROW_DOWN: 40 1988 }; 1989 1990 var selectors = { 1991 self: "[data-" + NS + '-is="' + IS + '"]', 1992 active: { 1993 tab: "cmp-tabs__tab--active", 1994 tabpanel: "cmp-tabs__tabpanel--active" 1995 } 1996 }; 1997 1998 /** 1999 * Tabs Configuration 2000 * 2001 * @typedef {Object} TabsConfig Represents a Tabs configuration 2002 * @property {HTMLElement} element The HTMLElement representing the Tabs 2003 * @property {Object} options The Tabs options 2004 */ 2005 2006 /** 2007 * Tabs 2008 * 2009 * @class Tabs 2010 * @classdesc An interactive Tabs component for navigating a list of tabs 2011 * @param {TabsConfig} config The Tabs configuration 2012 */ 2013 function Tabs(config) { 2014 var that = this; 2015 2016 if (config && config.element) { 2017 init(config); 2018 } 2019 2020 /** 2021 * Initializes the Tabs 2022 * 2023 * @private 2024 * @param {TabsConfig} config The Tabs configuration 2025 */ 2026 function init(config) { 2027 that._config = config; 2028 2029 // prevents multiple initialization 2030 config.element.removeAttribute("data-" + NS + "-is"); 2031 2032 cacheElements(config.element); 2033 that._active = getActiveIndex(that._elements["tab"]); 2034 2035 if (that._elements.tabpanel) { 2036 refreshActive(); 2037 bindEvents(); 2038 scrollToDeepLinkIdInTabs(); 2039 } 2040 2041 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 2042 /* 2043 * Editor message handling: 2044 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 2045 * - check that the message data panel container type is correct and that the id (path) matches this specific Tabs component 2046 * - if so, route the "navigate" operation to enact a navigation of the Tabs based on index data 2047 */ 2048 CQ.CoreComponents.MESSAGE_CHANNEL = CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 2049 CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 2050 if (message.data && message.data.type === "cmp-tabs" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 2051 if (message.data.operation === "navigate") { 2052 navigate(message.data.index); 2053 } 2054 } 2055 }); 2056 } 2057 } 2058 2059 /** 2060 * Displays the panel containing the element that corresponds to the deep link in the URI fragment 2061 * and scrolls the browser to this element. 2062 */ 2063 function scrollToDeepLinkIdInTabs() { 2064 if (containerUtils) { 2065 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "tab", "tabpanel"); 2066 if (deepLinkItemIdx > -1) { 2067 var deepLinkItem = that._elements["tab"][deepLinkItemIdx]; 2068 if (deepLinkItem && that._elements["tab"][that._active].id !== deepLinkItem.id) { 2069 navigateAndFocusTab(deepLinkItemIdx, true); 2070 } 2071 var hashId = window.location.hash.substring(1); 2072 if (hashId) { 2073 var hashItem = document.querySelector("[id='" + hashId + "']"); 2074 if (hashItem) { 2075 hashItem.scrollIntoView(); 2076 } 2077 } 2078 } 2079 } 2080 } 2081 2082 /** 2083 * Returns the index of the active tab, if no tab is active returns 0 2084 * 2085 * @param {Array} tabs Tab elements 2086 * @returns {Number} Index of the active tab, 0 if none is active 2087 */ 2088 function getActiveIndex(tabs) { 2089 if (tabs) { 2090 for (var i = 0; i < tabs.length; i++) { 2091 if (tabs[i].classList.contains(selectors.active.tab)) { 2092 return i; 2093 } 2094 } 2095 } 2096 return 0; 2097 } 2098 2099 /** 2100 * Caches the Tabs elements as defined via the {@code data-tabs-hook="ELEMENT_NAME"} markup API 2101 * 2102 * @private 2103 * @param {HTMLElement} wrapper The Tabs wrapper element 2104 */ 2105 function cacheElements(wrapper) { 2106 that._elements = {}; 2107 that._elements.self = wrapper; 2108 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 2109 2110 for (var i = 0; i < hooks.length; i++) { 2111 var hook = hooks[i]; 2112 if (hook.closest("." + NS + "-" + IS) === that._elements.self) { // only process own tab elements 2113 var capitalized = IS; 2114 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2115 var key = hook.dataset[NS + "Hook" + capitalized]; 2116 if (that._elements[key]) { 2117 if (!Array.isArray(that._elements[key])) { 2118 var tmp = that._elements[key]; 2119 that._elements[key] = [tmp]; 2120 } 2121 that._elements[key].push(hook); 2122 } else { 2123 that._elements[key] = hook; 2124 } 2125 } 2126 } 2127 } 2128 2129 /** 2130 * Binds Tabs event handling 2131 * 2132 * @private 2133 */ 2134 function bindEvents() { 2135 window.addEventListener("hashchange", scrollToDeepLinkIdInTabs, false); 2136 var tabs = that._elements["tab"]; 2137 if (tabs) { 2138 for (var i = 0; i < tabs.length; i++) { 2139 (function(index) { 2140 tabs[i].addEventListener("click", function(event) { 2141 navigateAndFocusTab(index); 2142 }); 2143 tabs[i].addEventListener("keydown", function(event) { 2144 onKeyDown(event); 2145 }); 2146 })(i); 2147 } 2148 } 2149 } 2150 2151 /** 2152 * Handles tab keydown events 2153 * 2154 * @private 2155 * @param {Object} event The keydown event 2156 */ 2157 function onKeyDown(event) { 2158 var index = that._active; 2159 var lastIndex = that._elements["tab"].length - 1; 2160 2161 switch (event.keyCode) { 2162 case keyCodes.ARROW_LEFT: 2163 case keyCodes.ARROW_UP: 2164 event.preventDefault(); 2165 if (index > 0) { 2166 navigateAndFocusTab(index - 1); 2167 } 2168 break; 2169 case keyCodes.ARROW_RIGHT: 2170 case keyCodes.ARROW_DOWN: 2171 event.preventDefault(); 2172 if (index < lastIndex) { 2173 navigateAndFocusTab(index + 1); 2174 } 2175 break; 2176 case keyCodes.HOME: 2177 event.preventDefault(); 2178 navigateAndFocusTab(0); 2179 break; 2180 case keyCodes.END: 2181 event.preventDefault(); 2182 navigateAndFocusTab(lastIndex); 2183 break; 2184 default: 2185 return; 2186 } 2187 } 2188 2189 /** 2190 * Refreshes the tab markup based on the current {@code Tabs#_active} index 2191 * 2192 * @private 2193 */ 2194 function refreshActive() { 2195 var tabpanels = that._elements["tabpanel"]; 2196 var tabs = that._elements["tab"]; 2197 2198 if (tabpanels) { 2199 if (Array.isArray(tabpanels)) { 2200 for (var i = 0; i < tabpanels.length; i++) { 2201 if (i === parseInt(that._active)) { 2202 tabpanels[i].classList.add(selectors.active.tabpanel); 2203 tabpanels[i].removeAttribute("aria-hidden"); 2204 tabs[i].classList.add(selectors.active.tab); 2205 tabs[i].setAttribute("aria-selected", true); 2206 tabs[i].setAttribute("tabindex", "0"); 2207 } else { 2208 tabpanels[i].classList.remove(selectors.active.tabpanel); 2209 tabpanels[i].setAttribute("aria-hidden", true); 2210 tabs[i].classList.remove(selectors.active.tab); 2211 tabs[i].setAttribute("aria-selected", false); 2212 tabs[i].setAttribute("tabindex", "-1"); 2213 } 2214 } 2215 } else { 2216 // only one tab 2217 tabpanels.classList.add(selectors.active.tabpanel); 2218 tabs.classList.add(selectors.active.tab); 2219 } 2220 } 2221 } 2222 2223 /** 2224 * Focuses the element and prevents scrolling the element into view 2225 * 2226 * @param {HTMLElement} element Element to focus 2227 */ 2228 function focusWithoutScroll(element) { 2229 var x = window.scrollX || window.pageXOffset; 2230 var y = window.scrollY || window.pageYOffset; 2231 element.focus(); 2232 window.scrollTo(x, y); 2233 } 2234 2235 /** 2236 * Navigates to the tab at the provided index 2237 * 2238 * @private 2239 * @param {Number} index The index of the tab to navigate to 2240 */ 2241 function navigate(index) { 2242 that._active = index; 2243 refreshActive(); 2244 } 2245 2246 /** 2247 * Navigates to the item at the provided index and ensures the active tab gains focus 2248 * 2249 * @private 2250 * @param {Number} index The index of the item to navigate to 2251 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 2252 */ 2253 function navigateAndFocusTab(index, keepHash) { 2254 var exActive = that._active; 2255 if (!keepHash && containerUtils) { 2256 containerUtils.updateUrlHash(that, "tab", index); 2257 } 2258 navigate(index); 2259 focusWithoutScroll(that._elements["tab"][index]); 2260 2261 if (dataLayerEnabled) { 2262 2263 var activeItem = getDataLayerId(that._elements.tabpanel[index]); 2264 var exActiveItem = getDataLayerId(that._elements.tabpanel[exActive]); 2265 2266 dataLayer.push({ 2267 event: "cmp:show", 2268 eventInfo: { 2269 path: "component." + activeItem 2270 } 2271 }); 2272 2273 dataLayer.push({ 2274 event: "cmp:hide", 2275 eventInfo: { 2276 path: "component." + exActiveItem 2277 } 2278 }); 2279 2280 var tabsId = that._elements.self.id; 2281 var uploadPayload = { component: {} }; 2282 uploadPayload.component[tabsId] = { shownItems: [activeItem] }; 2283 2284 var removePayload = { component: {} }; 2285 removePayload.component[tabsId] = { shownItems: undefined }; 2286 2287 dataLayer.push(removePayload); 2288 dataLayer.push(uploadPayload); 2289 } 2290 } 2291 } 2292 2293 /**
2294 * Reads options data from the Tabs wrapper element, defined via {@code data-cmp-*} data attributes 2295 * 2296 * @private 2297 * @param {HTMLElement} element The Tabs element to read options data from 2298 * @returns {Object} The options read from the component data attributes 2299 */ 2300 function readData(element) { 2301 var data = element.dataset; 2302 var options = []; 2303 var capitalized = IS; 2304 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2305 var reserved = ["is", "hook" + capitalized]; 2306 2307 for (var key in data) { 2308 if (Object.prototype.hasOwnProperty.call(data, key)) { 2309 var value = data[key]; 2310 2311 if (key.indexOf(NS) === 0) { 2312 key = key.slice(NS.length); 2313 key = key.charAt(0).toLowerCase() + key.substring(1); 2314 2315 if (reserved.indexOf(key) === -1) { 2316 options[key] = value; 2317 } 2318 } 2319 } 2320 } 2321 2322 return options; 2323 } 2324 2325 /** 2326 * Parses the dataLayer string and returns the ID 2327 * 2328 * @private 2329 * @param {HTMLElement} item the accordion item 2330 * @returns {String} dataLayerId or undefined 2331 */ 2332 function getDataLayerId(item) { 2333 if (item) { 2334 if (item.dataset.cmpDataLayer) { 2335 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 2336 } else { 2337 return item.id; 2338 } 2339 } 2340 return null; 2341 } 2342 2343 /** 2344 * Document ready handler and DOM mutation observers. Initializes Tabs components as necessary. 2345 * 2346 * @private 2347 */ 2348 function onDocumentReady() { 2349 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 2350 if (dataLayerEnabled) { 2351 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 2352 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 2353 } 2354 2355 var elements = document.querySelectorAll(selectors.self); 2356 for (var i = 0; i < elements.length; i++) { 2357 new Tabs({ element: elements[i], options: readData(elements[i]) }); 2358 } 2359 2360 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 2361 var body = document.querySelector("body"); 2362 var observer = new MutationObserver(function(mutations) { 2363 mutations.forEach(function(mutation) { 2364 // needed for IE 2365 var nodesArray = [].slice.call(mutation.addedNodes); 2366 if (nodesArray.length > 0) { 2367 nodesArray.forEach(function(addedNode) { 2368 if (addedNode.querySelectorAll) { 2369 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 2370 elementsArray.forEach(function(element) { 2371 new Tabs({ element: element, options: readData(element) }); 2372 }); 2373 } 2374 }); 2375 } 2376 }); 2377 }); 2378 2379 observer.observe(body, { 2380 subtree: true, 2381 childList: true, 2382 characterData: true 2383 }); 2384 } 2385 2386 if (document.readyState !== "loading") { 2387 onDocumentReady(); 2388 } else { 2389 document.addEventListener("DOMContentLoaded", onDocumentReady); 2390 } 2391 2392 if (containerUtils) { 2393 window.addEventListener("load", containerUtils.scrollToAnchor, false); 2394 } 2395 2396}()); 2397 2398/******************************************************************************* 2399 * Copyright 2022 Adobe 2400 * 2401 * Licensed under the Apache License, Version 2.0 (the "License"); 2402 * you may not use this file except in compliance with the License. 2403 * You may obtain a copy of the License at 2404 * 2405 * http://www.apache.org/licenses/LICENSE-2.0 2406 * 2407 * Unless required by applicable law or agreed to in writing, software 2408 * distributed under the License is distributed on an "AS IS" BASIS, 2409 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2410 * See the License for the specific language governing permissions and 2411 * limitations under the License. 2412 ******************************************************************************/ 2413(function(document) { 2414 "use strict"; 2415 2416 window.CMP = window.CMP || {}; 2417 window.CMP.utils = (function() { 2418 var NS = "cmp"; 2419 2420 /**
2421 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 2422 * 2423 * @param {HTMLElement} element The component element to read options data from 2424 * @param {String} is The component identifier 2425 * @returns {String[]} The options read from the component data attributes 2426 */ 2427 var readData = function(element, is) { 2428 var data = element.dataset; 2429 var options = []; 2430 var capitalized = is; 2431 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2432 var reserved = ["is", "hook" + capitalized]; 2433 2434 for (var key in data) { 2435 if (Object.prototype.hasOwnProperty.call(data, key)) { 2436 var value = data[key]; 2437 2438 if (key.indexOf(NS) === 0) { 2439 key = key.slice(NS.length); 2440 key = key.charAt(0).toLowerCase() + key.substring(1); 2441 2442 if (reserved.indexOf(key) === -1) { 2443 options[key] = value; 2444 } 2445 } 2446 } 2447 } 2448 return options; 2449 }; 2450 2451 /** 2452 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 2453 * @param {String[]} options the options to transform 2454 * @param {Object} properties object of properties of property functions 2455 * @returns {Object} transformed properties 2456 */ 2457 var setupProperties = function(options, properties) { 2458 var transformedProperties = {}; 2459 2460 for (var key in properties) { 2461 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2462 var property = properties[key]; 2463 if (options && options[key] != null) { 2464 if (property && typeof property.transform === "function") { 2465 transformedProperties[key] = property.transform(options[key]); 2466 } else { 2467 transformedProperties[key] = options[key]; 2468 } 2469 } else { 2470 transformedProperties[key] = properties[key]["default"]; 2471 } 2472 } 2473 } 2474 return transformedProperties; 2475 }; 2476 2477 2478 return { 2479 readData: readData, 2480 setupProperties: setupProperties 2481 }; 2482 }()); 2483}(window.document)); 2484 2485/******************************************************************************* 2486 * Copyright 2018 Adobe 2487 * 2488 * Licensed under the Apache License, Version 2.0 (the "License"); 2489 * you may not use this file except in compliance with the License. 2490 * You may obtain a copy of the License at 2491 * 2492 * http://www.apache.org/licenses/LICENSE-2.0 2493 * 2494 * Unless required by applicable law or agreed to in writing, software 2495 * distributed under the License is distributed on an "AS IS" BASIS, 2496 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2497 * See the License for the specific language governing permissions and 2498 * limitations under the License. 2499 ******************************************************************************/ 2500(function() { 2501 "use strict"; 2502 2503 var containerUtils = window.CQ && window.CQ.CoreComponents && window.CQ.CoreComponents.container && window.CQ.CoreComponents.container.utils ? window.CQ.CoreComponents.container.utils : undefined; 2504 if (!containerUtils) { 2505 // eslint-disable-next-line no-console 2506 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."); 2507 } 2508 var dataLayerEnabled; 2509 var dataLayer; 2510 var dataLayerName; 2511 2512 var NS = "cmp"; 2513 var IS = "carousel"; 2514 2515 var keyCodes = { 2516 SPACE: 32, 2517 END: 35, 2518 HOME: 36, 2519 ARROW_LEFT: 37, 2520 ARROW_UP: 38, 2521 ARROW_RIGHT: 39, 2522 ARROW_DOWN: 40 2523 }; 2524 2525 var selectors = { 2526 self: "[data-" + NS + '-is="' + IS + '"]' 2527 }; 2528 2529 var properties = { 2530 /** 2531 * Determines whether the Carousel will automatically transition between slides 2532 * 2533 * @memberof Carousel
2534 * @type {Boolean} 2535 * @default false 2536 */ 2537 "autoplay": { 2538 "default": false, 2539 "transform": function(value) { 2540 return !(value === null || typeof value === "undefined"); 2541 } 2542 }, 2543 /** 2544 * Duration (in milliseconds) before automatically transitioning to the next slide 2545 * 2546 * @memberof Carousel 2547 * @type {Number} 2548 * @default 5000 2549 */ 2550 "delay": { 2551 "default": 5000, 2552 "transform": function(value) { 2553 value = parseFloat(value); 2554 return !isNaN(value) ? value : null; 2555 } 2556 }, 2557 /** 2558 * Determines whether automatic pause on hovering the carousel is disabled 2559 * 2560 * @memberof Carousel 2561 * @type {Boolean} 2562 * @default false 2563 */ 2564 "autopauseDisabled": { 2565 "default": false, 2566 "transform": function(value) { 2567 return !(value === null || typeof value === "undefined"); 2568 } 2569 } 2570 }; 2571 2572 /** 2573 * Carousel Configuration 2574 * 2575 * @typedef {Object} CarouselConfig Represents a Carousel configuration 2576 * @property {HTMLElement} element The HTMLElement representing the Carousel 2577 * @property {*[]} options The Carousel options 2578 */ 2579 2580 /** 2581 * Carousel 2582 * 2583 * @class Carousel 2584 * @classdesc An interactive Carousel component for navigating a list of generic items 2585 * @param {CarouselConfig} config The Carousel configuration 2586 */ 2587 function Carousel(config) { 2588 var that = this; 2589 2590 if (config && config.element) { 2591 init(config); 2592 } 2593 2594 /** 2595 * Initializes the Carousel 2596 * 2597 * @private 2598 * @param {CarouselConfig} config The Carousel configuration 2599 */ 2600 function init(config) { 2601 that._config = config; 2602 2603 // prevents multiple initialization 2604 config.element.removeAttribute("data-" + NS + "-is"); 2605 2606 setupProperties(config.options); 2607 cacheElements(config.element); 2608 2609 that._active = 0; 2610 that._paused = false; 2611 2612 if (that._elements.item) { 2613 initializeActive(); 2614 bindEvents(); 2615 resetAutoplayInterval(); 2616 refreshPlayPauseActions(); 2617 scrollToDeepLinkIdInCarousel(); 2618 } 2619 2620 // TODO: This section is only relevant in edit mode and should move to the editor clientLib 2621 if (window.Granite && window.Granite.author && window.Granite.author.MessageChannel) { 2622 /* 2623 * Editor message handling: 2624 * - subscribe to "cmp.panelcontainer" message requests sent by the editor frame 2625 * - check that the message data panel container type is correct and that the id (path) matches this specific Carousel component 2626 * - if so, route the "navigate" operation to enact a navigation of the Carousel based on index data 2627 */ 2628 window.CQ = window.CQ || {}; 2629 window.CQ.CoreComponents = window.CQ.CoreComponents || {}; 2630 window.CQ.CoreComponents.MESSAGE_CHANNEL = window.CQ.CoreComponents.MESSAGE_CHANNEL || new window.Granite.author.MessageChannel("cqauthor", window); 2631 window.CQ.CoreComponents.MESSAGE_CHANNEL.subscribeRequestMessage("cmp.panelcontainer", function(message) { 2632 if (message.data && message.data.type === "cmp-carousel" && message.data.id === that._elements.self.dataset["cmpPanelcontainerId"]) { 2633 if (message.data.operation === "navigate") { 2634 navigate(message.data.index); 2635 } 2636 } 2637 }); 2638 } 2639 } 2640 2641 /** 2642 * Displays the slide containing the element that corresponds to the deep link in the URI fragment 2643 * and scrolls the browser to this element. 2644 */ 2645 function scrollToDeepLinkIdInCarousel() { 2646 if (containerUtils) { 2647 var deepLinkItemIdx = containerUtils.getDeepLinkItemIdx(that, "item", "item"); 2648 if (deepLinkItemIdx > -1) { 2649 var deepLinkItem = that._elements["item"][deepLinkItemIdx]; 2650 if (deepLinkItem && that._elements["item"][that._active].id !== deepLinkItem.id) { 2651 navigateAndFocusIndicator(deepLinkItemIdx, true); 2652 // pause the carousel auto-rotation 2653 pause(); 2654 } 2655 var hashId = window.location.hash.substring(1); 2656 if (hashId) { 2657 var hashItem = document.querySelector("[id='" + hashId + "']"); 2658 if (hashItem) { 2659 hashItem.scrollIntoView(); 2660 } 2661 } 2662 } 2663 } 2664 } 2665 2666 /** 2667 * Caches the Carousel elements as defined via the {@code data-carousel-hook="ELEMENT_NAME"} markup API 2668 * 2669 * @private 2670 * @param {HTMLElement} wrapper The Carousel wrapper element 2671 */ 2672 function cacheElements(wrapper) { 2673 that._elements = {}; 2674 that._elements.self = wrapper; 2675 var hooks = that._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 2676 2677 for (var i = 0; i < hooks.length; i++) { 2678 var hook = hooks[i]; 2679 var capitalized = IS; 2680 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 2681 var key = hook.dataset[NS + "Hook" + capitalized]; 2682 if (that._elements[key]) { 2683 if (!Array.isArray(that._elements[key])) { 2684 var tmp = that._elements[key]; 2685 that._elements[key] = [tmp]; 2686 } 2687 that._elements[key].push(hook); 2688 } else { 2689 that._elements[key] = hook; 2690 } 2691 } 2692 } 2693 2694 /** 2695 * Sets up properties for the Carousel based on the passed options. 2696 * 2697 * @private 2698 * @param {Object} options The Carousel options 2699 */ 2700 function setupProperties(options) { 2701 that._properties = {}; 2702 2703 for (var key in properties) { 2704 if (Object.prototype.hasOwnProperty.call(properties, key)) { 2705 var property = properties[key]; 2706 var value = null; 2707 2708 if (options && options[key] != null) { 2709 value = options[key]; 2710 2711 // transform the provided option 2712 if (property && typeof property.transform === "function") { 2713 value = property.transform(value); 2714 } 2715 } 2716 2717 if (value === null) { 2718 // value still null, take the property default 2719 value = properties[key]["default"]; 2720 } 2721 2722 that._properties[key] = value; 2723 } 2724 } 2725 } 2726 2727 /** 2728 * Binds Carousel event handling 2729 * 2730 * @private 2731 */ 2732 function bindEvents() { 2733 window.addEventListener("hashchange", scrollToDeepLinkIdInCarousel, false); 2734 if (that._elements["previous"]) { 2735 that._elements["previous"].addEventListener("click", function() { 2736 var index = getPreviousIndex(); 2737 navigate(index); 2738 if (dataLayerEnabled) { 2739 dataLayer.push({ 2740 event: "cmp:show", 2741 eventInfo: { 2742 path: "component." + getDataLayerId(that._elements.item[index]) 2743 } 2744 }); 2745 } 2746 }); 2747 } 2748 2749 if (that._elements["next"]) { 2750 that._elements["next"].addEventListener("click", function() { 2751 var index = getNextIndex(); 2752 navigate(index); 2753 if (dataLayerEnabled) { 2754 dataLayer.push({ 2755 event: "cmp:show", 2756 eventInfo: { 2757 path: "component." + getDataLayerId(that._elements.item[index]) 2758 } 2759 }); 2760 } 2761 }); 2762 } 2763 2764 var indicators = that._elements["indicator"]; 2765 if (indicators) { 2766 for (var i = 0; i < indicators.length; i++) { 2767 (function(index) { 2768 indicators[i].addEventListener("click", function(event) { 2769 navigateAndFocusIndicator(index); 2770 // pause the carousel auto-rotation 2771 pause(); 2772 }); 2773 })(i); 2774 } 2775 } 2776 2777 if (that._elements["pause"]) { 2778 if (that._properties.autoplay) { 2779 that._elements["pause"].addEventListener("click", onPauseClick); 2780 } 2781 } 2782 2783 if (that._elements["play"]) { 2784 if (that._properties.autoplay) { 2785 that._elements["play"].addEventListener("click", onPlayClick); 2786 } 2787 } 2788 2789 that._elements.self.addEventListener("keydown", onKeyDown); 2790 2791 if (!that._properties.autopauseDisabled) { 2792 that._elements.self.addEventListener("mouseenter", onMouseEnter); 2793 that._elements.self.addEventListener("mouseleave", onMouseLeave); 2794 } 2795 2796 // for accessibility we pause animation when a element get focused 2797 var items = that._elements["item"]; 2798 if (items) { 2799 for (var j = 0; j < items.length; j++) { 2800 items[j].addEventListener("focusin", onMouseEnter); 2801 items[j].addEventListener("focusout", onMouseLeave); 2802 } 2803 } 2804 } 2805 2806 /**
2807 * Handles carousel keydown events 2808 * 2809 * @private 2810 * @param {Object} event The keydown event 2811 */ 2812 function onKeyDown(event) { 2813 var index = that._active; 2814 var lastIndex = that._elements["indicator"].length - 1; 2815 2816 switch (event.keyCode) { 2817 case keyCodes.ARROW_LEFT: 2818 case keyCodes.ARROW_UP: 2819 event.preventDefault(); 2820 if (index > 0) { 2821 navigateAndFocusIndicator(index - 1); 2822 } 2823 break; 2824 case keyCodes.ARROW_RIGHT: 2825 case keyCodes.ARROW_DOWN: 2826 event.preventDefault(); 2827 if (index < lastIndex) { 2828 navigateAndFocusIndicator(index + 1); 2829 } 2830 break; 2831 case keyCodes.HOME: 2832 event.preventDefault(); 2833 navigateAndFocusIndicator(0); 2834 break; 2835 case keyCodes.END: 2836 event.preventDefault(); 2837 navigateAndFocusIndicator(lastIndex); 2838 break; 2839 case keyCodes.SPACE: 2840 if (that._properties.autoplay && (event.target !== that._elements["previous"] && event.target !== that._elements["next"])) { 2841 event.preventDefault(); 2842 if (!that._paused) { 2843 pause(); 2844 } else { 2845 play(); 2846 } 2847 } 2848 if (event.target === that._elements["pause"]) { 2849 that._elements["play"].focus(); 2850 } 2851 if (event.target === that._elements["play"]) { 2852 that._elements["pause"].focus(); 2853 } 2854 break; 2855 default: 2856 return; 2857 } 2858 } 2859 2860 /** 2861 * Handles carousel mouseenter events 2862 * 2863 * @private 2864 * @param {Object} event The mouseenter event 2865 */ 2866 function onMouseEnter(event) { 2867 clearAutoplayInterval(); 2868 } 2869 2870 /** 2871 * Handles carousel mouseleave events 2872 * 2873 * @private 2874 * @param {Object} event The mouseleave event 2875 */ 2876 function onMouseLeave(event) { 2877 resetAutoplayInterval(); 2878 } 2879 2880 /** 2881 * Handles pause element click events 2882 * 2883 * @private 2884 * @param {Object} event The click event 2885 */ 2886 function onPauseClick(event) { 2887 pause(); 2888 that._elements["play"].focus(); 2889 } 2890 2891 /** 2892 * Handles play element click events 2893 * 2894 * @private 2895 * @param {Object} event The click event 2896 */ 2897 function onPlayClick() { 2898 play(); 2899 that._elements["pause"].focus(); 2900 } 2901 2902 /** 2903 * Pauses the playing of the Carousel. Sets {@code Carousel#_paused} marker. 2904 * Only relevant when autoplay is enabled 2905 * 2906 * @private 2907 */ 2908 function pause() { 2909 that._paused = true; 2910 clearAutoplayInterval(); 2911 refreshPlayPauseActions(); 2912 } 2913 2914 /** 2915 * Enables the playing of the Carousel. Sets {@code Carousel#_paused} marker. 2916 * Only relevant when autoplay is enabled 2917 * 2918 * @private 2919 */ 2920 function play() { 2921 that._paused = false; 2922 2923 // If the Carousel is hovered, don't begin auto transitioning until the next mouse leave event 2924 var hovered = false; 2925 if (that._elements.self.parentElement) { 2926 hovered = that._elements.self.parentElement.querySelector(":hover") === that._elements.self; 2927 } 2928 if (that._properties.autopauseDisabled || !hovered) { 2929 resetAutoplayInterval(); 2930 } 2931 2932 refreshPlayPauseActions(); 2933 } 2934 2935 /** 2936 * Refreshes the play/pause action markup based on the {@code Carousel#_paused} state 2937 * 2938 * @private 2939 */ 2940 function refreshPlayPauseActions() {
2941 setActionDisabled(that._elements["pause"], that._paused); 2942 setActionDisabled(that._elements["play"], !that._paused); 2943 } 2944 2945 /** 2946 * Initialize {@code Carousel#_active} based on the active item of the carousel. 2947 */ 2948 function initializeActive() { 2949 var items = that._elements["item"]; 2950 if (items && Array.isArray(items)) { 2951 for (var i = 0; i < items.length; i++) { 2952 if (items[i].classList.contains("cmp-carousel__item--active")) { 2953 that._active = i; 2954 break; 2955 } 2956 } 2957 } 2958 } 2959 2960 /** 2961 * Refreshes the item markup based on the current {@code Carousel#_active} index 2962 * 2963 * @private 2964 */ 2965 function refreshActive() { 2966 var items = that._elements["item"]; 2967 var indicators = that._elements["indicator"]; 2968 2969 if (items) { 2970 if (Array.isArray(items)) { 2971 for (var i = 0; i < items.length; i++) { 2972 if (i === parseInt(that._active)) { 2973 items[i].classList.add("cmp-carousel__item--active"); 2974 items[i].removeAttribute("aria-hidden"); 2975 indicators[i].classList.add("cmp-carousel__indicator--active"); 2976 indicators[i].setAttribute("aria-selected", true); 2977 indicators[i].setAttribute("tabindex", "0"); 2978 } else { 2979 items[i].classList.remove("cmp-carousel__item--active"); 2980 items[i].setAttribute("aria-hidden", true); 2981 indicators[i].classList.remove("cmp-carousel__indicator--active"); 2982 indicators[i].setAttribute("aria-selected", false); 2983 indicators[i].setAttribute("tabindex", "-1"); 2984 } 2985 } 2986 } else { 2987 // only one item 2988 items.classList.add("cmp-carousel__item--active"); 2989 indicators.classList.add("cmp-carousel__indicator--active"); 2990 } 2991 } 2992 } 2993 2994 /** 2995 * Focuses the element and prevents scrolling the element into view 2996 * 2997 * @param {HTMLElement} element Element to focus 2998 */ 2999 function focusWithoutScroll(element) { 3000 var x = window.scrollX || window.pageXOffset; 3001 var y = window.scrollY || window.pageYOffset; 3002 element.focus(); 3003 window.scrollTo(x, y); 3004 } 3005 3006 /** 3007 * Retrieves the next active index, with looping 3008 * 3009 * @private 3010 * @returns {Number} Index of the next carousel item 3011 */ 3012 function getNextIndex() { 3013 return that._active === (that._elements["item"].length - 1) ? 0 : that._active + 1; 3014 } 3015 3016 /** 3017 * Retrieves the previous active index, with looping 3018 * 3019 * @private 3020 * @returns {Number} Index of the previous carousel item 3021 */ 3022 function getPreviousIndex() { 3023 return that._active === 0 ? (that._elements["item"].length - 1) : that._active - 1; 3024 } 3025 3026 /** 3027 * Navigates to the item at the provided index 3028 * 3029 * @private 3030 * @param {Number} index The index of the item to navigate to 3031 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 3032 */ 3033 function navigate(index, keepHash) { 3034 if (index < 0 || index > (that._elements["item"].length - 1)) { 3035 return; 3036 } 3037 3038 that._active = index; 3039 refreshActive(); 3040 3041 if (!keepHash && containerUtils) { 3042 containerUtils.updateUrlHash(that, "item", index); 3043 } 3044 3045 if (dataLayerEnabled) { 3046 var carouselId = that._elements.self.id; 3047 var activeItem = getDataLayerId(that._elements.item[index]); 3048 var updatePayload = { component: {} }; 3049 updatePayload.component[carouselId] = { shownItems: [activeItem] }; 3050 3051 var removePayload = { component: {} }; 3052 removePayload.component[carouselId] = { shownItems: undefined }; 3053 3054 dataLayer.push(removePayload); 3055 dataLayer.push(updatePayload); 3056 } 3057 3058 // reset the autoplay transition interval following navigation, if not already hovering the carousel
3059 if (that._elements.self.parentElement) { 3060 if (that._elements.self.parentElement.querySelector(":hover") !== that._elements.self) { 3061 resetAutoplayInterval(); 3062 } 3063 } 3064 } 3065 3066 /** 3067 * Navigates to the item at the provided index and ensures the active indicator gains focus 3068 * 3069 * @private 3070 * @param {Number} index The index of the item to navigate to 3071 * @param {Boolean} keepHash true to keep the hash in the URL, false to update it 3072 */ 3073 function navigateAndFocusIndicator(index, keepHash) { 3074 navigate(index, keepHash); 3075 focusWithoutScroll(that._elements["indicator"][index]); 3076 3077 if (dataLayerEnabled) { 3078 dataLayer.push({ 3079 event: "cmp:show", 3080 eventInfo: { 3081 path: "component." + getDataLayerId(that._elements.item[index]) 3082 } 3083 }); 3084 } 3085 } 3086 3087 /** 3088 * Starts/resets automatic slide transition interval 3089 * 3090 * @private 3091 */ 3092 function resetAutoplayInterval() { 3093 if (that._paused || !that._properties.autoplay) { 3094 return; 3095 } 3096 clearAutoplayInterval(); 3097 that._autoplayIntervalId = window.setInterval(function() { 3098 if (document.visibilityState && document.hidden) { 3099 return; 3100 } 3101 var indicators = that._elements["indicators"]; 3102 if (indicators !== document.activeElement && indicators.contains(document.activeElement)) { 3103 // if an indicator has focus, ensure we switch focus following navigation 3104 navigateAndFocusIndicator(getNextIndex(), true); 3105 } else { 3106 navigate(getNextIndex(), true); 3107 } 3108 }, that._properties.delay); 3109 } 3110 3111 /** 3112 * Clears/pauses automatic slide transition interval 3113 * 3114 * @private 3115 */ 3116 function clearAutoplayInterval() { 3117 window.clearInterval(that._autoplayIntervalId); 3118 that._autoplayIntervalId = null; 3119 } 3120 3121 /** 3122 * Sets the disabled state for an action and toggles the appropriate CSS classes 3123 * 3124 * @private 3125 * @param {HTMLElement} action Action to disable 3126 * @param {Boolean} [disable] {@code true} to disable, {@code false} to enable 3127 */ 3128 function setActionDisabled(action, disable) { 3129 if (!action) { 3130 return; 3131 } 3132 if (disable !== false) { 3133 action.disabled = true; 3134 action.classList.add("cmp-carousel__action--disabled"); 3135 } else { 3136 action.disabled = false; 3137 action.classList.remove("cmp-carousel__action--disabled"); 3138 } 3139 } 3140 } 3141 3142 /** 3143 * Parses the dataLayer string and returns the ID 3144 * 3145 * @private 3146 * @param {HTMLElement} item the accordion item 3147 * @returns {String} dataLayerId or undefined 3148 */ 3149 function getDataLayerId(item) { 3150 if (item) { 3151 if (item.dataset.cmpDataLayer) { 3152 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 3153 } else { 3154 return item.id; 3155 } 3156 } 3157 return null; 3158 } 3159 3160 /** 3161 * Document ready handler and DOM mutation observers. Initializes Carousel components as necessary. 3162 * 3163 * @private 3164 */ 3165 function onDocumentReady() { 3166 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 3167 if (dataLayerEnabled) { 3168 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 3169 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 3170 } 3171 3172 var elements = document.querySelectorAll(selectors.self); 3173 for (var i = 0; i < elements.length; i++) { 3174 new Carousel({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 3175 } 3176 3177 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 3178 var body = document.querySelector("body"); 3179 var observer = new MutationObserver(function(mutations) { 3180 mutations.forEach(function(mutation) { 3181 // needed for IE 3182 var nodesArray = [].slice.call(mutation.addedNodes); 3183 if (nodesArray.length > 0) { 3184 nodesArray.forEach(function(addedNode) { 3185 if (addedNode.querySelectorAll) { 3186 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 3187 elementsArray.forEach(function(element) { 3188 new Carousel({ element: element, options: CMP.utils.readData(element, IS) }); 3189 }); 3190 } 3191 }); 3192 } 3193 }); 3194 }); 3195 3196 observer.observe(body, { 3197 subtree: true, 3198 childList: true, 3199 characterData: true 3200 }); 3201 } 3202 3203 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 3204 document.addEventListener("DOMContentLoaded", resolve); 3205 }); 3206 Promise.all([documentReady]).then(onDocumentReady); 3207 3208 if (containerUtils) { 3209 window.addEventListener("load", containerUtils.scrollToAnchor, false); 3210 } 3211 3212}()); 3213 3214/******************************************************************************* 3215 * Copyright 2022 Adobe 3216 * 3217 * Licensed under the Apache License, Version 2.0 (the "License");
3218 * you may not use this file except in compliance with the License. 3219 * You may obtain a copy of the License at 3220 * 3221 * http://www.apache.org/licenses/LICENSE-2.0 3222 * 3223 * Unless required by applicable law or agreed to in writing, software 3224 * distributed under the License is distributed on an "AS IS" BASIS, 3225 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3226 * See the License for the specific language governing permissions and 3227 * limitations under the License. 3228 ******************************************************************************/ 3229(function(document) { 3230 "use strict"; 3231 3232 window.CMP = window.CMP || {}; 3233 window.CMP.utils = (function() { 3234 var NS = "cmp"; 3235 3236 /** 3237 * Reads options data from the Component wrapper element, defined via {@code data-cmp-*} data attributes 3238 * 3239 * @param {HTMLElement} element The component element to read options data from 3240 * @param {String} is The component identifier 3241 * @returns {String[]} The options read from the component data attributes 3242 */ 3243 var readData = function(element, is) { 3244 var data = element.dataset; 3245 var options = []; 3246 var capitalized = is; 3247 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 3248 var reserved = ["is", "hook" + capitalized]; 3249 3250 for (var key in data) { 3251 if (Object.prototype.hasOwnProperty.call(data, key)) { 3252 var value = data[key]; 3253 3254 if (key.indexOf(NS) === 0) { 3255 key = key.slice(NS.length); 3256 key = key.charAt(0).toLowerCase() + key.substring(1); 3257 3258 if (reserved.indexOf(key) === -1) { 3259 options[key] = value; 3260 } 3261 } 3262 } 3263 } 3264 return options; 3265 }; 3266 3267 /** 3268 * Set up the final properties of a component by evaluating the transform function or fall back to the default value on demand 3269 * @param {String[]} options the options to transform 3270 * @param {Object} properties object of properties of property functions 3271 * @returns {Object} transformed properties 3272 */ 3273 var setupProperties = function(options, properties) { 3274 var transformedProperties = {}; 3275 3276 for (var key in properties) { 3277 if (Object.prototype.hasOwnProperty.call(properties, key)) { 3278 var property = properties[key]; 3279 if (options && options[key] != null) { 3280 if (property && typeof property.transform === "function") { 3281 transformedProperties[key] = property.transform(options[key]); 3282 } else { 3283 transformedProperties[key] = options[key]; 3284 } 3285 } else { 3286 transformedProperties[key] = properties[key]["default"]; 3287 } 3288 } 3289 } 3290 return transformedProperties; 3291 }; 3292 3293 3294 return { 3295 readData: readData, 3296 setupProperties: setupProperties 3297 }; 3298 }()); 3299}(window.document)); 3300 3301/******************************************************************************* 3302 * Copyright 2022 Adobe 3303 * 3304 * Licensed under the Apache License, Version 2.0 (the "License"); 3305 * you may not use this file except in compliance with the License. 3306 * You may obtain a copy of the License at 3307 * 3308 * http://www.apache.org/licenses/LICENSE-2.0 3309 * 3310 * Unless required by applicable law or agreed to in writing, software 3311 * distributed under the License is distributed on an "AS IS" BASIS, 3312 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3313 * See the License for the specific language governing permissions and 3314 * limitations under the License. 3315 ******************************************************************************/ 3316(function(document) { 3317 "use strict"; 3318 3319 window.CMP = window.CMP || {}; 3320 window.CMP.image = window.CMP.image || {}; 3321 window.CMP.image.dynamicMedia = (function() { 3322 var autoSmartCrops = {}; 3323 var SRC_URI_TEMPLATE_WIDTH_VAR = "{.width}"; 3324 var SRC_URI_TEMPLATE_DPR_VAR = "{dpr}";
3325 var SRC_URI_DPR_OFF = "dpr=off"; 3326 var SRC_URI_DPR_ON = "dpr=on,{dpr}"; 3327 var dpr = window.devicePixelRatio || 1; 3328 var config = { 3329 minWidth: 20 3330 }; 3331 3332 /** 3333 * get auto smart crops from dm 3334 * @param {String} src the src uri 3335 * @returns {{}} the smart crop json object 3336 */ 3337 var getAutoSmartCrops = function(src) { 3338 var request = new XMLHttpRequest(); 3339 var url = src.split(SRC_URI_TEMPLATE_WIDTH_VAR)[0] + "?req=set,json"; 3340 request.open("GET", url, false); 3341 request.onload = function() { 3342 if (request.status >= 200 && request.status < 400) { 3343 // success status 3344 var responseText = request.responseText; 3345 var rePayload = new RegExp(/^(?:\/\*jsonp\*\/)?\s*([^()]+)\(([\s\S]+),\s*"[0-9]*"\);?$/gmi); 3346 var rePayloadJSON = new RegExp(/^{[\s\S]*}$/gmi); 3347 var resPayload = rePayload.exec(responseText); 3348 var payload; 3349 if (resPayload) { 3350 var payloadStr = resPayload[2]; 3351 if (rePayloadJSON.test(payloadStr)) { 3352 payload = JSON.parse(payloadStr); 3353 } 3354 3355 } 3356 // check "relation" - only in case of smartcrop preset 3357 if (payload && payload.set.relation && payload.set.relation.length > 0) { 3358 for (var i = 0; i < payload.set.relation.length; i++) { 3359 autoSmartCrops[parseInt(payload.set.relation[i].userdata.SmartCropWidth)] = 3360 ":" + payload.set.relation[i].userdata.SmartCropDef; 3361 } 3362 } 3363 } else { 3364 // error status 3365 } 3366 }; 3367 request.send(); 3368 return autoSmartCrops; 3369 }; 3370 3371 /** 3372 * Build and return the srcset value based on the available auto smart crops 3373 * @param {String} src the src uri 3374 * @param {Object} smartCrops the smart crops object 3375 * @returns {String} the srcset 3376 */ 3377 var getSrcSet = function(src, smartCrops) { 3378 var srcset; 3379 var keys = Object.keys(smartCrops); 3380 if (keys.length > 0) { 3381 srcset = []; 3382 for (var key in autoSmartCrops) { 3383 srcset.push(src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, smartCrops[key]) + " " + key + "w"); 3384 } 3385 } 3386 return srcset.join(","); 3387 }; 3388 3389 /** 3390 * Get the optimal width based on the available sizes 3391 * @param {[Number]} sizes the available sizes 3392 * @param {Number} width the element width 3393 * @returns {String} the optimal width 3394 */ 3395 function getOptimalWidth(sizes, width) { 3396 var len = sizes.length; 3397 var key = 0; 3398 3399 while ((key < len - 1) && (sizes[key] < width)) { 3400 key++; 3401 } 3402 3403 return sizes[key] !== undefined ? sizes[key].toString() : width; 3404 } 3405 3406 /** 3407 * Get the width of an element or parent element if the width is smaller than the minimum width 3408 * @param {HTMLElement} component the image component 3409 * @param {HTMLElement | Node} parent the parent element 3410 * @returns {Number} the width of the element 3411 */ 3412 var getWidth = function(component, parent) { 3413 var width = component.offsetWidth; 3414 while (width < config.minWidth && parent && !component._autoWidth) { 3415 width = parent.offsetWidth; 3416 parent = parent.parentNode; 3417 } 3418 return width; 3419 }; 3420 3421 /** 3422 * Set the src and srcset attribute for a Dynamic Media Image which auto smart crops enabled. 3423 * @param {HTMLElement} component the image component 3424 * @param {{}} properties the component properties 3425 */ 3426 var setDMAttributes = function(component, properties) { 3427 // for v3 we first have to turn the dpr on
3428 var src = properties.src.replace(SRC_URI_DPR_OFF, SRC_URI_DPR_ON); 3429 src = src.replace(SRC_URI_TEMPLATE_DPR_VAR, dpr); 3430 var smartCrops = {}; 3431 var width; 3432 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 3433 smartCrops = getAutoSmartCrops(src); 3434 } 3435 var hasWidths = (properties.widths && properties.widths.length > 0) || Object.keys(smartCrops).length > 0; 3436 if (hasWidths) { 3437 var image = component.querySelector("img"); 3438 var elemWidth = getWidth(component, component.parentNode); 3439 if (properties["smartcroprendition"] === "SmartCrop:Auto") { 3440 image.setAttribute("srcset", CMP.image.dynamicMedia.getSrcSet(src, smartCrops)); 3441 width = getOptimalWidth(Object.keys(smartCrops, elemWidth)); 3442 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, smartCrops[width])); 3443 } else { 3444 width = getOptimalWidth(properties.widths, elemWidth); 3445 image.setAttribute("src", CMP.image.dynamicMedia.getSrc(src, width)); 3446 } 3447 } 3448 }; 3449 3450 /** 3451 * Get the src attribute based on the optimal width 3452 * @param {String} src the src uri 3453 * @param {String} width the element width 3454 * @returns {String} the final src attribute 3455 */ 3456 var getSrc = function(src, width) { 3457 if (src.indexOf(SRC_URI_TEMPLATE_WIDTH_VAR) > -1) { 3458 src = src.replace(SRC_URI_TEMPLATE_WIDTH_VAR, width); 3459 } 3460 return src; 3461 }; 3462 3463 3464 return { 3465 getAutoSmartCrops: getAutoSmartCrops, 3466 getSrcSet: getSrcSet, 3467 getSrc: getSrc, 3468 setDMAttributes: setDMAttributes, 3469 getWidth: getWidth 3470 }; 3471 }()); 3472 document.dispatchEvent(new CustomEvent("core.wcm.components.commons.site.image.dynamic-media.loaded")); 3473}(window.document)); 3474 3475/******************************************************************************* 3476 * Copyright 2022 Adobe 3477 * 3478 * Licensed under the Apache License, Version 2.0 (the "License"); 3479 * you may not use this file except in compliance with the License. 3480 * You may obtain a copy of the License at 3481 * 3482 * http://www.apache.org/licenses/LICENSE-2.0 3483 * 3484 * Unless required by applicable law or agreed to in writing, software 3485 * distributed under the License is distributed on an "AS IS" BASIS, 3486 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3487 * See the License for the specific language governing permissions and 3488 * limitations under the License. 3489 ******************************************************************************/ 3490(function(document) { 3491 3492 "use strict"; 3493 3494 window.CMP = window.CMP || {}; 3495 window.CMP.image = window.CMP.image || {}; 3496 window.CMP.image.v3 = (function() { 3497 var IS = "image"; 3498 3499 var selectors = { 3500 self: "[data-cmp-hook-image='imageV3']" 3501 }; 3502 3503 var properties = { 3504 "widths": { 3505 "default": [], 3506 "transform": function(value) { 3507 var widths = []; 3508 value.split(",").forEach(function(item) { 3509 item = parseFloat(item); 3510 if (!isNaN(item)) { 3511 widths.push(item); 3512 } 3513 }); 3514 return widths; 3515 } 3516 }, 3517 "dmimage": { 3518 "default": false, 3519 "transform": function(value) { 3520 return !(value === null || typeof value === "undefined"); 3521 } 3522 }, 3523 "src": { 3524 "transform": function(value) { 3525 return decodeURIComponent(value); 3526 } 3527 }, 3528 "smartcroprendition": "" 3529 }; 3530 3531 function Image(config) { 3532 var that = this; 3533 /** 3534 * Init the image if the image is from dynamic media 3535 * @param {HTMLElement} component the image component 3536 */ 3537 that.initImage = function(component) { 3538 var options = CMP.utils.readData(component, IS); 3539 that._properties = CMP.utils.setupProperties(options, properties); 3540 if (that._properties.dmimage) { 3541 CMP.image.dynamicMedia.setDMAttributes(component, that._properties); 3542 } 3543 }; 3544 that.initImage(config.element); 3545 } 3546 3547 return { 3548 init: function() { 3549 var elements = document.querySelectorAll(selectors.self); 3550 for (var i = 0; i < elements.length; i++) { 3551 new Image({ element: elements[i], options: CMP.utils.readData(elements[i], IS) }); 3552 } 3553 3554 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 3555 var body = document.querySelector("body"); 3556 var observer = new MutationObserver(function(mutations) { 3557 mutations.forEach(function(mutation) { 3558 // needed for IE 3559 var nodesArray = [].slice.call(mutation.addedNodes); 3560 if (nodesArray.length > 0) { 3561 nodesArray.forEach(function(addedNode) { 3562 if (addedNode.querySelectorAll) { 3563 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 3564 elementsArray.forEach(function(element) { 3565 new Image({ element: element, options: CMP.utils.readData(element, IS) }); 3566 }); 3567 } 3568 }); 3569 } 3570 }); 3571 }); 3572 3573 observer.observe(body, { 3574 subtree: true, 3575 childList: true, 3576 characterData: true 3577 }); 3578 } 3579 }; 3580 }()); 3581 3582 var documentReady = document.readyState !== "loading" ? Promise.resolve() : new Promise(function(resolve) { 3583 document.addEventListener("DOMContentLoaded", resolve); 3584 }); 3585 Promise.all([documentReady]).then(window.CMP.image.v3.init); 3586}(window.document)); 3587 3588/******************************************************************************* 3589 * Copyright 2022 Adobe 3590 * 3591 * Licensed under the Apache License, Version 2.0 (the "License");
3592 * you may not use this file except in compliance with the License. 3593 * You may obtain a copy of the License at 3594 * 3595 * http://www.apache.org/licenses/LICENSE-2.0 3596 * 3597 * Unless required by applicable law or agreed to in writing, software 3598 * distributed under the License is distributed on an "AS IS" BASIS, 3599 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 3600 * See the License for the specific language governing permissions and 3601 * limitations under the License. 3602 ******************************************************************************/ 3603(function() { 3604 "use strict"; 3605 3606 var NS = "cmp"; 3607 var IS = "search"; 3608 3609 var DELAY = 300; // time before fetching new results when the user is typing a search string 3610 var LOADING_DISPLAY_DELAY = 300; // minimum time during which the loading indicator is displayed 3611 var PARAM_RESULTS_OFFSET = "resultsOffset"; 3612 3613 var keyCodes = { 3614 TAB: 9, 3615 ENTER: 13, 3616 ESCAPE: 27, 3617 ARROW_UP: 38, 3618 ARROW_DOWN: 40 3619 }; 3620 3621 var selectors = { 3622 self: "[data-" + NS + '-is="' + IS + '"]', 3623 item: { 3624 self: "[data-" + NS + "-hook-" + IS + '="item"]', 3625 title: "[data-" + NS + "-hook-" + IS + '="itemTitle"]', 3626 focused: "." + NS + "-search__item--is-focused" 3627 } 3628 }; 3629 3630 var properties = { 3631 /** 3632 * The minimum required length of the search term before results are fetched. 3633 * 3634 * @memberof Search 3635 * @type {Number} 3636 * @default 3 3637 */ 3638 minLength: { 3639 "default": 3, 3640 transform: function(value) { 3641 value = parseFloat(value); 3642 return isNaN(value) ? null : value; 3643 } 3644 }, 3645 /** 3646 * The maximal number of results fetched by a search request. 3647 * 3648 * @memberof Search 3649 * @type {Number} 3650 * @default 10 3651 */ 3652 resultsSize: { 3653 "default": 10, 3654 transform: function(value) { 3655 value = parseFloat(value); 3656 return isNaN(value) ? null : value; 3657 } 3658 } 3659 }; 3660 3661 var idCount = 0; 3662 3663 function readData(element) { 3664 var data = element.dataset; 3665 var options = []; 3666 var capitalized = IS; 3667 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 3668 var reserved = ["is", "hook" + capitalized]; 3669 3670 for (var key in data) { 3671 if (Object.prototype.hasOwnProperty.call(data, key)) { 3672 var value = data[key]; 3673 3674 if (key.indexOf(NS) === 0) { 3675 key = key.slice(NS.length); 3676 key = key.charAt(0).toLowerCase() + key.substring(1); 3677 3678 if (reserved.indexOf(key) === -1) { 3679 options[key] = value; 3680 } 3681 } 3682 } 3683 } 3684 3685 return options; 3686 } 3687 3688 function toggleShow(element, show) { 3689 if (element) { 3690 if (show !== false) { 3691 element.style.display = "block"; 3692 element.setAttribute("aria-hidden", false); 3693 } else { 3694 element.style.display = "none"; 3695 element.setAttribute("aria-hidden", true); 3696 } 3697 } 3698 } 3699 3700 function serialize(form) { 3701 var query = []; 3702 if (form && form.elements) { 3703 for (var i = 0; i < form.elements.length; i++) { 3704 var node = form.elements[i]; 3705 if (!node.disabled && node.name) { 3706 var param = [node.name, encodeURIComponent(node.value)]; 3707 query.push(param.join("=")); 3708 } 3709 } 3710 } 3711 return query.join("&"); 3712 } 3713 3714 function mark(node, regex) { 3715 if (!node || !regex) { 3716 return; 3717 } 3718 3719 // text nodes 3720 if (node.nodeType === 3) { 3721 var nodeValue = node.nodeValue; 3722 var match = regex.exec(nodeValue); 3723 3724 if (nodeValue && match) { 3725 var element = document.createElement("mark"); 3726 element.className = NS + "-search__item-mark"; 3727 element.appendChild(document.createTextNode(match[0])); 3728 3729 var after = node.splitText(match.index); 3730 after.nodeValue = after.nodeValue.substring(match[0].length); 3731 node.parentNode.insertBefore(element, after); 3732 } 3733 } else if (node.hasChildNodes()) { 3734 for (var i = 0; i < node.childNodes.length; i++) { 3735 // recurse 3736 mark(node.childNodes[i], regex); 3737 } 3738 } 3739 } 3740 3741 // useful for Accessibility, helping users with low vision and users with cognitive disabilities to identify the change in results 3742 function updateSearchResultsStatusMessageElement(searchElementId, totalResults) { 3743 var searchResultsStatusMessage = document.querySelector("#" + searchElementId + "> .cmp_search__info"); 3744 searchResultsStatusMessage.style.visibility = "visible"; 3745 var searchResultsFoundMessage = totalResults === 1 ? totalResults + " result" : totalResults + " results"; 3746 var searchResultsNotFoundMessage = "No results"; 3747 searchResultsStatusMessage.innerText = totalResults > 0 ? searchResultsFoundMessage : searchResultsNotFoundMessage; 3748 } 3749 3750 function Search(config) { 3751 if (config.element) { 3752 // prevents multiple initialization 3753 config.element.removeAttribute("data-" + NS + "-is"); 3754 } 3755 3756 this._cacheElements(config.element); 3757 this._setupProperties(config.options); 3758 3759 this._action = this._elements.form.getAttribute("action"); 3760 this._resultsOffset = 0; 3761 this._hasMoreResults = true; 3762 3763 this._elements.input.addEventListener("input", this._onInput.bind(this)); 3764 this._elements.input.addEventListener("focus", this._onInput.bind(this)); 3765 this._elements.input.addEventListener("keydown", this._onKeydown.bind(this)); 3766 this._elements.clear.addEventListener("click", this._onClearClick.bind(this)); 3767 document.addEventListener("click", this._onDocumentClick.bind(this)); 3768 this._elements.results.addEventListener("scroll", this._onScroll.bind(this)); 3769 3770 this._makeAccessible(); 3771 } 3772 3773 Search.prototype._displayResults = function() { 3774 if (this._elements.input.value.length === 0) { 3775 toggleShow(this._elements.clear, false); 3776 this._cancelResults(); 3777 } else if (this._elements.input.value.length < this._properties.minLength) { 3778 toggleShow(this._elements.clear, true); 3779 } else { 3780 this._updateResults(); 3781 toggleShow(this._elements.clear, true); 3782 } 3783 }; 3784 3785 Search.prototype._onScroll = function(event) { 3786 // fetch new results when the results to be scrolled down are less than the visible results 3787 if (this._elements.results.scrollTop + 2 * this._elements.results.clientHeight >= this._elements.results.scrollHeight) { 3788 this._resultsOffset += this._properties.resultsSize; 3789 this._displayResults(); 3790 } 3791 }; 3792 3793 Search.prototype._onInput = function(event) { 3794 var self = this; 3795 self._cancelResults(); 3796 // start searching when the search term reaches the minimum length 3797 this._timeout = setTimeout(function() { 3798 self._displayResults(); 3799 }, DELAY); 3800 }; 3801 3802 Search.prototype._onKeydown = function(event) { 3803 var self = this; 3804 3805 switch (event.keyCode) { 3806 case keyCodes.TAB: 3807 if (self._resultsOpen()) { 3808 toggleShow(self._elements.results, false); 3809 self._elements.input.setAttribute("aria-expanded", "false"); 3810 this._hideSearchResultsStatusMessage(); 3811 } 3812 break;
3813 case keyCodes.ENTER: 3814 event.preventDefault(); 3815 if (self._resultsOpen()) { 3816 var focused = self._elements.results.querySelector(selectors.item.focused); 3817 if (focused) { 3818 focused.click(); 3819 } 3820 } 3821 break; 3822 case keyCodes.ESCAPE: 3823 self._cancelResults(); 3824 break; 3825 case keyCodes.ARROW_UP: 3826 if (self._resultsOpen()) { 3827 event.preventDefault(); 3828 self._stepResultFocus(true); 3829 } 3830 break; 3831 case keyCodes.ARROW_DOWN: 3832 if (self._resultsOpen()) { 3833 event.preventDefault(); 3834 self._stepResultFocus(); 3835 } else { 3836 // test the input and if necessary fetch and display the results 3837 self._onInput(); 3838 } 3839 break; 3840 default: 3841 return; 3842 } 3843 }; 3844 3845 Search.prototype._onClearClick = function(event) { 3846 event.preventDefault(); 3847 this._elements.input.value = ""; 3848 toggleShow(this._elements.clear, false); 3849 toggleShow(this._elements.results, false); 3850 this._elements.input.setAttribute("aria-expanded", "false"); 3851 this._hideSearchResultsStatusMessage(); 3852 }; 3853 3854 Search.prototype._onDocumentClick = function(event) { 3855 var inputContainsTarget = this._elements.input.contains(event.target); 3856 var resultsContainTarget = this._elements.results.contains(event.target); 3857 3858 if (!(inputContainsTarget || resultsContainTarget)) { 3859 toggleShow(this._elements.results, false); 3860 this._elements.input.setAttribute("aria-expanded", "false"); 3861 this._hideSearchResultsStatusMessage(); 3862 } 3863 }; 3864 3865 Search.prototype._resultsOpen = function() { 3866 return this._elements.results.style.display !== "none"; 3867 }; 3868 3869 Search.prototype._makeAccessible = function() { 3870 var id = NS + "-search-results-" + idCount; 3871 this._elements.input.setAttribute("aria-owns", id); 3872 this._elements.results.id = id; 3873 idCount++; 3874 }; 3875 3876 Search.prototype._generateItems = function(data, results) { 3877 var self = this; 3878 3879 data.forEach(function(item) { 3880 var el = document.createElement("span"); 3881 el.innerHTML = self._elements.itemTemplate.innerHTML; 3882 el.querySelectorAll(selectors.item.title)[0].appendChild(document.createTextNode(item.title)); 3883 el.querySelectorAll(selectors.item.self)[0].setAttribute("href", self._safeHref(item.url)); 3884 results.innerHTML += el.innerHTML; 3885 }); 3886 }; 3887 3888 Search.prototype._safeHref = function(href) { 3889 var a = document.createElement("a"); 3890 a.href = href; 3891 return a.pathname; 3892 }; 3893 3894 Search.prototype._markResults = function() { 3895 var nodeList = this._elements.results.querySelectorAll(selectors.item.self); 3896 var escapedTerm = this._elements.input.value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 3897 var regex = new RegExp("(" + escapedTerm + ")", "gi"); 3898 3899 for (var i = this._resultsOffset - 1; i < nodeList.length; ++i) { 3900 var result = nodeList[i]; 3901 mark(result, regex); 3902 } 3903 }; 3904 3905 Search.prototype._stepResultFocus = function(reverse) { 3906 var results = this._elements.results.querySelectorAll(selectors.item.self); 3907 var focused = this._elements.results.querySelector(selectors.item.focused); 3908 var newFocused; 3909 var index = Array.prototype.indexOf.call(results, focused); 3910 var focusedCssClass = NS + "-search__item--is-focused"; 3911 3912 if (results.length > 0) { 3913 3914 if (!reverse) { 3915 // highlight the next result 3916 if (index < 0) { 3917 results[0].classList.add(focusedCssClass); 3918 results[0].setAttribute("aria-selected", "true"); 3919 } else if (index + 1 < results.length) { 3920 results[index].classList.remove(focusedCssClass); 3921 results[index].setAttribute("aria-selected", "false"); 3922 results[index + 1].classList.add(focusedCssClass); 3923 results[index + 1].setAttribute("aria-selected", "true"); 3924 } 3925 3926 // if the last visible result is partially hidden, scroll up until it's completely visible 3927 newFocused = this._elements.results.querySelector(selectors.item.focused); 3928 if (newFocused) { 3929 var bottomHiddenHeight = newFocused.offsetTop + newFocused.offsetHeight - this._elements.results.scrollTop - this._elements.results.clientHeight; 3930 if (bottomHiddenHeight > 0) { 3931 this._elements.results.scrollTop += bottomHiddenHeight; 3932 } else { 3933 this._onScroll(); 3934 } 3935 } 3936 3937 } else { 3938 // highlight the previous result 3939 if (index >= 1) { 3940 results[index].classList.remove(focusedCssClass); 3941 results[index].setAttribute("aria-selected", "false"); 3942 results[index - 1].classList.add(focusedCssClass); 3943 results[index - 1].setAttribute("aria-selected", "true"); 3944 } 3945 3946 // if the first visible result is partially hidden, scroll down until it's completely visible 3947 newFocused = this._elements.results.querySelector(selectors.item.focused); 3948 if (newFocused) { 3949 var topHiddenHeight = this._elements.results.scrollTop - newFocused.offsetTop; 3950 if (topHiddenHeight > 0) { 3951 this._elements.results.scrollTop -= topHiddenHeight; 3952 } 3953 } 3954 } 3955 } 3956 }; 3957 3958 Search.prototype._updateResults = function() { 3959 var self = this; 3960 if (self._hasMoreResults) { 3961 var request = new XMLHttpRequest(); 3962 var url = self._action + "?" + serialize(self._elements.form) + "&" + PARAM_RESULTS_OFFSET + "=" + self._resultsOffset; 3963 3964 request.open("GET", url, true); 3965 request.onload = function() { 3966 // when the results are loaded: hide the loading indicator and display the search icon after a minimum period 3967 setTimeout(function() { 3968 toggleShow(self._elements.loadingIndicator, false); 3969 toggleShow(self._elements.icon, true); 3970 }, LOADING_DISPLAY_DELAY); 3971 if (request.status >= 200 && request.status < 400) { 3972 // success status 3973 var data = JSON.parse(request.responseText); 3974 updateSearchResultsStatusMessageElement(self._elements.self.id, data.length); 3975 if (data.length > 0) { 3976 self._generateItems(data, self._elements.results); 3977 self._markResults(); 3978 toggleShow(self._elements.results, true); 3979 self._elements.input.setAttribute("aria-expanded", "true"); 3980 } else { 3981 self._hasMoreResults = false;
3982 } 3983 // the total number of results is not a multiple of the fetched results: 3984 // -> we reached the end of the query 3985 if (self._elements.results.querySelectorAll(selectors.item.self).length % self._properties.resultsSize > 0) { 3986 self._hasMoreResults = false; 3987 } 3988 } else { 3989 // error status 3990 } 3991 }; 3992 // when the results are loading: display the loading indicator and hide the search icon 3993 toggleShow(self._elements.loadingIndicator, true); 3994 toggleShow(self._elements.icon, false); 3995 request.send(); 3996 } 3997 }; 3998 3999 Search.prototype._cancelResults = function() { 4000 clearTimeout(this._timeout); 4001 this._elements.results.scrollTop = 0; 4002 this._resultsOffset = 0; 4003 this._hasMoreResults = true; 4004 this._elements.results.innerHTML = ""; 4005 this._elements.input.setAttribute("aria-expanded", "false"); 4006 this._hideSearchResultsStatusMessage(); 4007 }; 4008 4009 Search.prototype._hideSearchResultsStatusMessage = function() { 4010 var searchResultsStatusMessage = document.querySelector("#" + this._elements.self.id + "> .cmp_search__info"); 4011 searchResultsStatusMessage.style.visibility = "hidden"; 4012 }; 4013 4014 Search.prototype._cacheElements = function(wrapper) { 4015 this._elements = {}; 4016 this._elements.self = wrapper; 4017 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS + "]"); 4018 4019 for (var i = 0; i < hooks.length; i++) { 4020 var hook = hooks[i]; 4021 var capitalized = IS; 4022 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 4023 var key = hook.dataset[NS + "Hook" + capitalized]; 4024 this._elements[key] = hook; 4025 } 4026 }; 4027 4028 Search.prototype._setupProperties = function(options) { 4029 this._properties = {}; 4030 4031 for (var key in properties) { 4032 if (Object.prototype.hasOwnProperty.call(properties, key)) { 4033 var property = properties[key]; 4034 if (options && options[key] != null) { 4035 if (property && typeof property.transform === "function") { 4036 this._properties[key] = property.transform(options[key]); 4037 } else { 4038 this._properties[key] = options[key]; 4039 } 4040 } else { 4041 this._properties[key] = properties[key]["default"]; 4042 } 4043 } 4044 } 4045 }; 4046 4047 function onDocumentReady() { 4048 var elements = document.querySelectorAll(selectors.self); 4049 for (var i = 0; i < elements.length; i++) { 4050 new Search({ element: elements[i], options: readData(elements[i]) }); 4051 } 4052 4053 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 4054 var body = document.querySelector("body"); 4055 var observer = new MutationObserver(function(mutations) { 4056 mutations.forEach(function(mutation) { 4057 // needed for IE 4058 var nodesArray = [].slice.call(mutation.addedNodes); 4059 if (nodesArray.length > 0) { 4060 nodesArray.forEach(function(addedNode) { 4061 if (addedNode.querySelectorAll) { 4062 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 4063 elementsArray.forEach(function(element) { 4064 new Search({ element: element, options: readData(element) }); 4065 }); 4066 } 4067 }); 4068 } 4069 }); 4070 }); 4071 4072 observer.observe(body, { 4073 subtree: true, 4074 childList: true, 4075 characterData: true 4076 }); 4077 } 4078 4079 if (document.readyState !== "loading") { 4080 onDocumentReady(); 4081 } else { 4082 document.addEventListener("DOMContentLoaded", onDocumentReady); 4083 } 4084 4085})(); 4086 4087/******************************************************************************* 4088 * Copyright 2016 Adobe 4089 * 4090 * Licensed under the Apache License, Version 2.0 (the "License");
4091 * you may not use this file except in compliance with the License. 4092 * You may obtain a copy of the License at 4093 * 4094 * http://www.apache.org/licenses/LICENSE-2.0 4095 * 4096 * Unless required by applicable law or agreed to in writing, software 4097 * distributed under the License is distributed on an "AS IS" BASIS, 4098 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4099 * See the License for the specific language governing permissions and 4100 * limitations under the License. 4101 ******************************************************************************/ 4102(function() { 4103 "use strict"; 4104 4105 var NS = "cmp"; 4106 var IS = "formText"; 4107 var IS_DASH = "form-text"; 4108 var SELF_SELECTOR = "[data-" + NS + '-is="' + IS + '"]'; 4109 4110 var selectors = { 4111 self: SELF_SELECTOR, 4112 validationMessage: SELF_SELECTOR + " .cmp-form-text__validation-message" 4113 }; 4114 4115 var displayValidationMessage = false; 4116 4117 var properties = { 4118 /** 4119 * A validation message to display if there is a type mismatch between the user input and expected input. 4120 * 4121 * @type {String} 4122 */ 4123 constraintMessage: "", 4124 /** 4125 * A validation message to display if no input is supplied, but input is expected for the field. 4126 * 4127 * @type {String} 4128 */ 4129 requiredMessage: "" 4130 }; 4131 4132 function readData(element) { 4133 var data = element.dataset; 4134 var options = []; 4135 var capitalized = IS; 4136 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 4137 var reserved = ["is", "hook" + capitalized]; 4138 4139 for (var key in data) { 4140 if (Object.prototype.hasOwnProperty.call(data, key)) { 4141 var value = data[key]; 4142 4143 if (key.indexOf(NS) === 0) { 4144 key = key.slice(NS.length); 4145 key = key.charAt(0).toLowerCase() + key.substring(1); 4146 4147 if (reserved.indexOf(key) === -1) { 4148 options[key] = value; 4149 } 4150 } 4151 } 4152 } 4153 4154 return options; 4155 } 4156 4157 function FormText(config) { 4158 if (config.element) { 4159 // prevents multiple initialization 4160 config.element.removeAttribute("data-" + NS + "-is"); 4161 } 4162 4163 this._cacheElements(config.element); 4164 this._setupProperties(config.options); 4165 4166 this._elements.input.addEventListener("invalid", this._onInvalid.bind(this)); 4167 this._elements.input.addEventListener("input", this._onInput.bind(this)); 4168 if (displayValidationMessage) { 4169 this._elements.input.checkValidity(); 4170 } 4171 } 4172 4173 FormText.prototype._onInvalid = function(event) { 4174 event.target.setCustomValidity(""); 4175 if (event.target.validity.typeMismatch) { 4176 if (this._properties.constraintMessage) { 4177 event.target.setCustomValidity(this._properties.constraintMessage); 4178 } 4179 } else if (event.target.validity.valueMissing) { 4180 if (this._properties.requiredMessage) { 4181 event.target.setCustomValidity(this._properties.requiredMessage); 4182 } 4183 } 4184 if (displayValidationMessage) { 4185 var validationMessage = event.target.parentElement.querySelector(".cmp-form-text__validation-message"); 4186 if (validationMessage) { 4187 validationMessage.innerText = event.target.validationMessage; 4188 } 4189 } 4190 }; 4191 4192 FormText.prototype._onInput = function(event) { 4193 event.target.setCustomValidity(""); 4194 if (displayValidationMessage) { 4195 event.target.checkValidity(); 4196 } 4197 }; 4198 4199 FormText.prototype._cacheElements = function(wrapper) { 4200 this._elements = {}; 4201 this._elements.self = wrapper; 4202 var hooks = this._elements.self.querySelectorAll("[data-" + NS + "-hook-" + IS_DASH + "]"); 4203 for (var i = 0; i < hooks.length; i++) { 4204 var hook = hooks[i]; 4205 var capitalized = IS; 4206 capitalized = capitalized.charAt(0).toUpperCase() + capitalized.slice(1); 4207 var key = hook.dataset[NS + "Hook" + capitalized]; 4208 this._elements[key] = hook; 4209 } 4210 }; 4211 4212 FormText.prototype._setupProperties = function(options) { 4213 this._properties = {}; 4214 4215 for (var key in properties) { 4216 if (Object.prototype.hasOwnProperty.call(properties, key)) { 4217 var property = properties[key]; 4218 if (options && options[key] != null) { 4219 if (property && typeof property.transform === "function") { 4220 this._properties[key] = property.transform(options[key]); 4221 } else { 4222 this._properties[key] = options[key]; 4223 } 4224 } else { 4225 this._properties[key] = properties[key]["default"]; 4226 } 4227 } 4228 } 4229 }; 4230 4231 function onDocumentReady() {
4232 var validationMessages = document.querySelectorAll(selectors.validationMessage); 4233 if (validationMessages && validationMessages.length > 0) { 4234 displayValidationMessage = true; 4235 } 4236 4237 var elements = document.querySelectorAll(selectors.self); 4238 for (var i = 0; i < elements.length; i++) { 4239 new FormText({ element: elements[i], options: readData(elements[i]) }); 4240 } 4241 4242 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 4243 var body = document.querySelector("body"); 4244 var observer = new MutationObserver(function(mutations) { 4245 mutations.forEach(function(mutation) { 4246 // needed for IE 4247 var nodesArray = [].slice.call(mutation.addedNodes); 4248 if (nodesArray.length > 0) { 4249 nodesArray.forEach(function(addedNode) { 4250 if (addedNode.querySelectorAll) { 4251 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 4252 elementsArray.forEach(function(element) { 4253 new FormText({ element: element, options: readData(element) }); 4254 }); 4255 } 4256 }); 4257 } 4258 }); 4259 }); 4260 4261 observer.observe(body, { 4262 subtree: true, 4263 childList: true, 4264 characterData: true 4265 }); 4266 } 4267 4268 if (document.readyState !== "loading") { 4269 onDocumentReady(); 4270 } else { 4271 document.addEventListener("DOMContentLoaded", onDocumentReady); 4272 } 4273 4274})(); 4275 4276/******************************************************************************* 4277 * Copyright 2020 Adobe 4278 * 4279 * Licensed under the Apache License, Version 2.0 (the "License"); 4280 * you may not use this file except in compliance with the License. 4281 * You may obtain a copy of the License at 4282 * 4283 * http://www.apache.org/licenses/LICENSE-2.0 4284 * 4285 * Unless required by applicable law or agreed to in writing, software 4286 * distributed under the License is distributed on an "AS IS" BASIS, 4287 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4288 * See the License for the specific language governing permissions and 4289 * limitations under the License. 4290 ******************************************************************************/ 4291(function() { 4292 "use strict"; 4293 4294 var NS = "cmp"; 4295 var IS = "pdfviewer"; 4296 var SDK_URL = "https://acrobatservices.adobe.com/view-sdk/viewer.js"; 4297 var SDK_READY_EVENT = "adobe_dc_view_sdk.ready"; 4298 4299 var selectors = { 4300 self: "[data-" + NS + '-is="' + IS + '"]', 4301 sdkScript: 'script[src="' + SDK_URL + '"]' 4302 }; 4303 4304 function initSDK() { 4305 var sdkIncluded = document.querySelectorAll(selectors.sdkScript).length > 0; 4306 if (!window.adobe_dc_view_sdk && !sdkIncluded) { 4307 var dcv = document.createElement("script"); 4308 dcv.type = "text/javascript"; 4309 dcv.src = SDK_URL; 4310 document.body.appendChild(dcv); 4311 } 4312 } 4313 4314 function previewPdf(component) { 4315 // prevents multiple initialization 4316 component.removeAttribute("data-" + NS + "-is"); 4317 4318 // add the view sdk to the page 4319 initSDK(); 4320 4321 // manage the preview 4322 if (component.dataset && component.id) { 4323 if (window.AdobeDC && window.AdobeDC.View) { 4324 dcView(component); 4325 } else { 4326 document.addEventListener(SDK_READY_EVENT, function() { 4327 dcView(component); 4328 }); 4329 } 4330 } 4331 } 4332 4333 function dcView(component) { 4334 var adobeDCView = new window.AdobeDC.View({ 4335 clientId: component.dataset.cmpClientId, 4336 divId: component.id + "-content", 4337 reportSuiteId: component.dataset.cmpReportSuiteId 4338 }); 4339 adobeDCView.previewFile({ 4340 content: { location: { url: component.dataset.cmpDocumentPath } }, 4341 metaData: { fileName: component.dataset.cmpDocumentFileName } 4342 }, JSON.parse(component.dataset.cmpViewerConfigJson)); 4343 } 4344 4345 /** 4346 * Document ready handler and DOM mutation observers. Initializes Accordion components as necessary. 4347 * 4348 * @private 4349 */ 4350 function onDocumentReady() { 4351 var elements = document.querySelectorAll(selectors.self); 4352 for (var i = 0; i < elements.length; i++) { 4353 previewPdf(elements[i]); 4354 } 4355 4356 var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver; 4357 var body = document.querySelector("body"); 4358 var observer = new MutationObserver(function(mutations) { 4359 mutations.forEach(function(mutation) { 4360 // needed for IE 4361 var nodesArray = [].slice.call(mutation.addedNodes); 4362 if (nodesArray.length > 0) { 4363 nodesArray.forEach(function(addedNode) { 4364 if (addedNode.querySelectorAll) { 4365 var elementsArray = [].slice.call(addedNode.querySelectorAll(selectors.self)); 4366 elementsArray.forEach(function(element) { 4367 previewPdf(element); 4368 }); 4369 } 4370 }); 4371 } 4372 }); 4373 }); 4374 4375 observer.observe(body, { 4376 subtree: true, 4377 childList: true, 4378 characterData: true 4379 }); 4380 4381 } 4382 4383 if (document.readyState !== "loading") { 4384 onDocumentReady(); 4385 } else { 4386 document.addEventListener("DOMContentLoaded", onDocumentReady); 4387 } 4388}()); 4389 4390/******************************************************************************* 4391 * Copyright 2022 Adobe 4392 * 4393 * Licensed under the Apache License, Version 2.0 (the "License");
4394 * you may not use this file except in compliance with the License. 4395 * You may obtain a copy of the License at 4396 * 4397 * http://www.apache.org/licenses/LICENSE-2.0 4398 * 4399 * Unless required by applicable law or agreed to in writing, software 4400 * distributed under the License is distributed on an "AS IS" BASIS, 4401 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4402 * See the License for the specific language governing permissions and 4403 * limitations under the License. 4404 ******************************************************************************/ 4405(function() { 4406 "use strict"; 4407 4408 var dataLayerEnabled; 4409 var dataLayer; 4410 var dataLayerName; 4411 4412 /** 4413 * Adds Click Event Listener to the main <div> of the Text Components 4414 */ 4415 function addClickEventListenerToTextComponents() { 4416 var componentMainDivs = document.getElementsByClassName("cmp-text"); 4417 4418 for (var i = 0; i < componentMainDivs.length; i++) { 4419 componentMainDivs[i].addEventListener("click", addClickedLinkToDataLayer); 4420 } 4421 } 4422 4423 /** 4424 * Adds clicked link contained by the Text Component to Data Layer 4425 * 4426 * @private 4427 * @param {Object} event The Click event 4428 */ 4429 function addClickedLinkToDataLayer(event) { 4430 var element = event.currentTarget; 4431 var componentId = getDataLayerId(element); 4432 4433 if (event.target.tagName === "A") { 4434 dataLayer.push({ 4435 event: "cmp:click", 4436 eventInfo: { 4437 path: "component." + componentId, 4438 link: event.target.getAttribute("href") 4439 } 4440 }); 4441 } 4442 } 4443 4444 /** 4445 * Parses the dataLayer string and returns the ID 4446 * 4447 * @private 4448 * @param {HTMLElement} item, the Text item 4449 * @returns {String} dataLayerId or undefined 4450 */ 4451 function getDataLayerId(item) { 4452 if (item) { 4453 if (item.dataset.cmpDataLayer) { 4454 return Object.keys(JSON.parse(item.dataset.cmpDataLayer))[0]; 4455 } else { 4456 return item.id; 4457 } 4458 } 4459 return null; 4460 } 4461 4462 function onDocumentReady() { 4463 dataLayerEnabled = document.body.hasAttribute("data-cmp-data-layer-enabled"); 4464 if (dataLayerEnabled) { 4465 dataLayerName = document.body.getAttribute("data-cmp-data-layer-name") || "adobeDataLayer"; 4466 dataLayer = window[dataLayerName] = window[dataLayerName] || []; 4467 4468 addClickEventListenerToTextComponents(); 4469 } 4470 } 4471 4472 if (document.readyState !== "loading") { 4473 onDocumentReady(); 4474 } else { 4475 document.addEventListener("DOMContentLoaded", onDocumentReady); 4476 } 4477})(); 4478 4479/******************************************************************************* 4480 * Copyright 2022 Adobe 4481 * 4482 * Licensed under the Apache License, Version 2.0 (the "License"); 4483 * you may not use this file except in compliance with the License. 4484 * You may obtain a copy of the License at 4485 * 4486 * http://www.apache.org/licenses/LICENSE-2.0 4487 * 4488 * Unless required by applicable law or agreed to in writing, software 4489 * distributed under the License is distributed on an "AS IS" BASIS, 4490 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4491 * See the License for the specific language governing permissions and 4492 * limitations under the License. 4493 ******************************************************************************/ 4494(function() { 4495 "use strict"; 4496 4497 var linkAccessibilityClass = "cmp-link__screen-reader-only"; 4498 var selectors = { 4499 linkAccessibility: "." + linkAccessibilityClass, 4500 linkAccessibilityEnabled: "[data-cmp-link-accessibility-enabled]", 4501 linkAccessibilityText: "[data-cmp-link-accessibility-text]" 4502 }; 4503 4504 function getLinkAccessibilityText() { 4505 var linkAccessibilityEnabled = document.querySelectorAll(selectors.linkAccessibilityEnabled); 4506 if (!linkAccessibilityEnabled[0]) { 4507 return; 4508 } 4509 var linkAccessibilityTextElements = document.querySelectorAll(selectors.linkAccessibilityText); 4510 if (!linkAccessibilityTextElements[0]) { 4511 return; 4512 } 4513 return linkAccessibilityTextElements[0].dataset.cmpLinkAccessibilityText; 4514 } 4515 4516 function onDocumentReady() { 4517 var linkAccessibilityText = getLinkAccessibilityText(); 4518 if (linkAccessibilityText) { 4519 document.querySelectorAll("a[target='_blank']").forEach(function(link) { 4520 if (!link.querySelector(selectors.linkAccessibility)) { 4521 var linkAccessibilityElement = document.createElement("span"); 4522 linkAccessibilityElement.classList.add(linkAccessibilityClass); 4523 linkAccessibilityElement.innerText = linkAccessibilityText; 4524 link.insertAdjacentElement("beforeend", linkAccessibilityElement); 4525 } 4526 }); 4527 } 4528 } 4529 4530 if (document.readyState !== "loading") { 4531 onDocumentReady(); 4532 } else { 4533 document.addEventListener("DOMContentLoaded", onDocumentReady); 4534 } 4535 4536}()); 4537
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.