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