1(function (app, common, core) { 2 'use strict'; 3 4 var SHIFT_BACKWARD_CLS = 'shift-backward'; 5 var SHIFT_FORWARD_CLS = 'shift-forward'; 6 7 /** 8 * Takes a collection of cards rendered in a row and configures them so hovering on 9 * a card will apply classes to the cards either side indicating that they should 10 * shift to the left or right depending on whether they sit before or after the focused card 11 * 12 * @param {HTMLCollection} cards - list of card elements 13 * @param {Object} config - config object 14 * @constructor 15 */ 16 common.CardShift = function (cards) { 17 var config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 18 var _self = this; 19 _self.cards = cards; 20 21 /** 22 * This value (pixels) the the minimum width that the classes will be applied 23 * If the viewport is any narrower than this value then the classes will not be applied 24 */ 25 _self.minViewportThreshold = config.minViewportThreshold || app.breakpoints.tablet; 26 _self.setEventListeners(); 27 }; 28 29 /** 30 * Sets event listeners on interactive elements 31 */ 32 common.CardShift.prototype.setEventListeners = function () { 33 var _self = this; 34 [].forEach.call(_self.cards, function (item, i) { 35 item.addEventListener('mouseenter', function () { 36 if (window.matchMedia("(min-width: ".concat(_self.minViewportThreshold, "px)")).matches) { 37 _self.applyShiftClasses(i); 38 } 39 }); 40 item.addEventListener('mouseleave', function () { 41 if (window.matchMedia("(min-width: ".concat(_self.minViewportThreshold, "px)")).matches) { 42 _self.removeShiftClasses(); 43 } 44 }); 45 }); 46 47 // window.addEventListener( 'resize', _debounce( this.getCarouselWidth.bind( this ) ) ); 48 }; 49 common.CardShift.prototype.applyShiftClasses = function (hoverItemIndex) { 50 var _self = this; 51 [].forEach.call(_self.cards, function (item, i) { 52 if (i < hoverItemIndex) { 53 core.style.addClass(item, SHIFT_BACKWARD_CLS); 54 } else if (i > hoverItemIndex) { 55 core.style.addClass(item, SHIFT_FORWARD_CLS); 56 } 57 }); 58 }; 59 common.CardShift.prototype.removeShiftClasses = function () { 60 var _self = this; 61 [].forEach.call(_self.cards, function (item) { 62 core.style.removeClass(item, SHIFT_FORWARD_CLS); 63 core.style.removeClass(item, SHIFT_BACKWARD_CLS); 64 }); 65 }; 66})(PULSE.app, PULSE.app.common, PULSE.core); 67(function (app, common, core) { 68 'use strict'; 69 70 var DISABLED_CLASS = 'is-disabled'; 71 var ACTIVE_CLASS = 'is-active'; 72 var HIDE_CLASS = 'u-hide'; 73 var disable = function disable(element) { 74 core.style.addClass(element, DISABLED_CLASS); 75 }; 76 var enable = function enable(element) { 77 core.style.removeClass(element, DISABLED_CLASS); 78 }; 79 var WIDE_ITEMS = 4; 80 var DESKTOP_ITEMS = 4; 81 var TABLET_ITEMS = 3; 82 var PHABLET_ITEMS = 2; 83 var MOBILE_ITEMS = 1; 84 var OFFSET_BUFFER = 20; 85 var HALF_SECOND = 500; 86 var DELAY = 3000; 87 88 /** 89 * Constructor for common.Carousel 90 * This constructor, when initiated on a widget will active carousel functionality 91 * On mobile, a manual scroll functionality will be set instead 92 * @param {HTMLElement} widget - parent widget 93 * @param {Object} config - configuration object 94 * @constructor 95 */ 96 common.Carousel = function (widget, config) { 97 var _self = this; 98 _self.widget = widget; 99 _self.config = config; 100 var carouselDataWidgetKey = config.carouselDataWidgetKey || 'carousel'; 101 _self.carousel = _self.widget.querySelector("[data-widget=\"".concat(carouselDataWidgetKey, "\"]")); 102 _self.pagination = _self.widget.querySelector(config.pagination || '.js-pagination'); 103 _self.slideContainer = _self.widget.querySelector(config.slideContainerClass || '.js-slide-container'); 104 _self.items = _self.widget.querySelectorAll(config.itemClass || '.js-item'); 105 _self.previousArrow = _self.widget.querySelector(config.previousArrow || '.js-previous'); 106 _self.nextArrow = _self.widget.querySelector(config.nextArrow || '.js-next'); 107 _self.mobileScroll = _self.widget.querySelector(config.mobileScroll || '.js-mobile-scroll'); 108 _self.mobileNextButton = _self.widget.querySelector(config.mobileNextButton || '.js-sli
108der-mobile-next'); 109 _self.mobilePreviousButton = _self.widget.querySelector(config.mobilePreviousButton || '.js-slider-mobile-previous'); 110 _self.scroll = config.scroll || false; 111 _self.forceCarouselMode = config.forceCarouselMode || false; 112 _self.contentType = widget.dataset.contentType; 113 _self.numItems = _self.items.length; 114 _self.breakpoint = { 115 wide: config.breakpointWide || app.breakpoints.wide, 116 desktop: config.breakpointDesktop || app.breakpoints.desktopMin, 117 tablet: config.breakpointTablet || app.breakpoints.tabletMin, 118 phablet: config.breakpointPhablet || app.breakpoints.phabletMin, 119 mobile: config.breakpointMobile || app.breakpoints.mobileMin 120 }; 121 _self.pageItems = { 122 wide: config.pageItemsWide || WIDE_ITEMS, 123 desktop: config.pageItemsDesktop || DESKTOP_ITEMS, 124 tablet: config.pageItemsTablet || TABLET_ITEMS, 125 phablet: config.pageItemsPhablet || PHABLET_ITEMS, 126 mobile: config.pageItemsMobile || MOBILE_ITEMS 127 }; 128 _self.highlightItem = _self.widget.querySelector('[ data-highlight-item ]'); 129 _self.highlightIndex = _self.highlightItem ? Number(_self.highlightItem.getAttribute('data-index')) : null; 130 if (!core.style.hasClass(_self.widget, 'no-carousel')) { 131 core.event.windowResize.add({ 132 method: _self.configureCarousel.bind(_self) 133 }); 134 _self.configureCarousel(); 135 _self.setEventListeners(); 136 } 137 }; 138 139 /** 140 * Sets event listeners on interactive elements 141 */ 142 common.Carousel.prototype.setEventListeners = function () { 143 var _self = this; 144 _self.previousArrow.addEventListener('click', function () { 145 _self.slideToPagePrevious(); 146 }); 147 _self.nextArrow.addEventListener('click', function () { 148 _self.slideToPageNext(); 149 }); 150 if (_self.mobileNextButton) { 151 _self.mobileNextButton.addEventListener('click', function () { 152 core.style.removeClass(_self.mobilePreviousButton, DISABLED_CLASS); 153 _self.moveOffsetNext(); 154 }); 155 } 156 if (_self.mobilePreviousButton) { 157 _self.mobilePreviousButton.addEventListener('click', function () { 158 _self.moveOffsetPrevious(); 159 }); 160 } 161 _self.slideContainer.addEventListener('scroll', function () { 162 _self.checkScrollPosition(); 163 }); 164 }; 165 166 /** 167 * Calculate screensize, number of items to a page, and item width 168 * Re-render pagination and reset slider to page 1 169 * Apply specific inline styles to handle type of scroll functionality (manual or carousel) 170 */ 171 common.Carousel.prototype.configureCarousel = function () { 172 var _self = this; 173 if (_self.forceCarouselMode) { 174 _self.isCarouselMode = true; 175 } else { 176 _self.isCarouselMode = window.matchMedia('(min-width: ' + _self.breakpoint.tablet + 'px)').matches ? true : false; 177 } 178 var firstItem = _self.items[0]; 179 var itemWidth = window.getComputedStyle(firstItem).width; 180 _self.itemWidth = Number(itemWidth.split('px')[0]); 181 var marginRight = window.getComputedStyle(firstItem).marginRight; 182 _self.gutterWidth = Number(marginRight.split('px')[0]); 183 _self.sliderWidth = _self.carousel.offsetWidth; 184 _self.itemsPerPage = _self.getItemsPerPage(); 185 _self.isSinglePage = _self.numItems <= _self.itemsPerPage ? true : false; 186 _self.numPages = Math.ceil(_self.numItems / _self.itemsPerPage); 187 _self.pageIndex = 0; 188 _self.isFirstPage = true; 189 _self.interval; 190 if (_self.pagination) { 191 _self.renderPagination(); 192 } 193 if (_self.isCarouselMode) { 194 _self.isSinglePage ? _self.hideControls() : _self.showControls(); 195 if (!_self.isSinglePage) { 196 _self.setSliderPosition(); // don't run if less than 4 items 197 } 198 } else { 199 _self.hideControls(); 200 _self.setAllItemsActive(); 201 _self.slideContainer.style.left = '0px'; 202 } 203 if (_self.highlightIndex && !_self.isSinglePage && _self.isCarouselMode) { 204 _self.jumpToHighlightPage(); 205 } 206 if (_self.scroll && !_self.isSinglePage) { 207 if (!_self.interval) { 208 _self.interval = setInterval(function () { 209 if (_self.pageIndex !== _self.numPages - 1) { 210 _self.slideToPageNext(); 211 } else { 212 _self.pageIndex = 0; 213 _self.setSliderPosition(); 214 } 215 }, DELAY); 216 } 217 } 218 }; 219 220 /** 221 * Slide back a page 222 */ 223 common.Carousel.prototype.slideToPagePrevious = function () { 224 var _self = this; 225 if (_self.pageIndex > 0) { 226 _self.pageIndex--; 227 _self.setSliderPosition(); 228 } 229 }; 230 231 /** 232 * Slide forward a page 233 */ 234 common.Carousel.prototype.slideToPageNext = function () { 235 var _self = this; 236 if (_self.pageIndex < _self.numPages - 1) { 237 _self.pageIndex++; 238 _self.setSliderPosition(); 239 } 240 }; 241 242 /** 243 * Scroll forward one tile by increasing the offsetLeft 244 */ 245 common.Carousel.prototype.moveOffsetNext = function moveOffsetNext() { 246 var _self = this; 247 _self.slideContainer.scrollBy({ 248 left: _self.itemWidth + _self.gutterWidth, 249 behavior: 'smooth' 250 }); 251 _self.checkScrollPosition(); 252 }; 253 254 /** 255 * Scroll back one tile by decreasing the offsetLeft 256 */ 257 common.Carousel.prototype.moveOffsetPrevious = function moveOffsetPrevious() { 258 var _self = this; 259 _self.slideContainer.scrollBy({ 260 left: (_self.itemWidth + _self.gutterWidth) * -1, 261 behavior: 'smooth' 262 }); 263 _self.checkRightScrollPosition(); 264 }; 265 266 /** 267 * Checks whether the container is scrolled to the end,
268 * if it is we disable the button and if not we enable it. 269 */ 270 common.Carousel.prototype.checkScrollPosition = function checkScrollPosition() { 271 var _self = this; 272 setTimeout(function () { 273 if (_self.slideContainer.scrollLeft + _self.slideContainer.offsetWidth > _self.slideContainer.scrollWidth - OFFSET_BUFFER) { 274 _self.disableScrollButton(); 275 } else { 276 _self.enableScrollButton(); 277 } 278 }, HALF_SECOND); 279 }; 280 281 /** 282 * Checks whether the container is scrolled to the start 283 * if it is we disable the button and if not we enable it. 284 */ 285 common.Carousel.prototype.checkRightScrollPosition = function checkScrollPosition() { 286 var _self = this; 287 setTimeout(function () { 288 if (_self.slideContainer.scrollLeft === 0) { 289 _self.disableLeftScrollButton(); 290 } else { 291 _self.enableLeftScrollButton(); 292 } 293 }, HALF_SECOND); 294 }; 295 296 /** 297 * Adds the disabled class to the mobile scroll button 298 */ 299 common.Carousel.prototype.disableScrollButton = function disableScrollButton() { 300 var _self = this; 301 if (_self.mobileNextButton) { 302 core.style.addClass(_self.mobileNextButton, DISABLED_CLASS); 303 } 304 }; 305 306 /** 307 * Removes the disabled class from the mobile scroll button 308 */ 309 common.Carousel.prototype.enableScrollButton = function enableScrollButton() { 310 var _self = this; 311 if (_self.mobileNextButton) { 312 core.style.removeClass(_self.mobileNextButton, DISABLED_CLASS); 313 } 314 }; 315 316 /** 317 * Adds the disabled class to the lhs mobile scroll button 318 */ 319 common.Carousel.prototype.disableLeftScrollButton = function disableScrollButton() { 320 var _self = this; 321 if (_self.mobilePreviousButton) { 322 core.style.addClass(_self.mobilePreviousButton, DISABLED_CLASS); 323 } 324 }; 325 326 /** 327 * Removes the disabled class from the lhs mobile scroll button 328 */ 329 common.Carousel.prototype.enableLeftScrollButton = function enableScrollButton() { 330 var _self = this; 331 if (_self.mobilePreviousButton) { 332 core.style.removeClass(_self.mobilePreviousButton, DISABLED_CLASS); 333 } 334 }; 335 336 /** 337 * Slide to a given page index 338 * @param {Integer} index - page index 339 */ 340 common.Carousel.prototype.slideToPageIndex = function (index) { 341 var _self = this; 342 if (_self.isSinglePage) { 343 return; // exit early if only single page 344 } 345 _self.pageIndex = Number(index); 346 _self.setSliderPosition(); 347 }; 348 349 /** 350 * Take current page index and calculate new slider position 351 */ 352 common.Carousel.prototype.setSliderPosition = function () { 353 var _self = this; 354 _self.isFirstPage = _self.pageIndex === 0; 355 _self.isLastPage = _self.pageIndex === _self.numPages - 1; 356 var modulo = _self.numItems % _self.itemsPerPage; 357 var lastPageItems = modulo ? modulo : _self.itemsPerPage; 358 var regularPage = (_self.itemWidth + _self.gutterWidth) * _self.itemsPerPage; 359 var finalPage = (_self.itemWidth + _self.gutterWidth) * lastPageItems; 360 var slidePosition; 361 if (!_self.isLastPage) { 362 slidePosition = regularPage * _self.pageIndex; 363 } else { 364 // slide only the remaining items to avoid whitespace 365 slidePosition = regularPage * (_self.pageIndex - 1) + finalPage; 366 } 367 368 // pass value to slide container to trigger slide 369 _self.slideContainer.style.left = -slidePosition + 'px'; 370 371 // show/hide slide buttons when no longer relevant 372 _self.isFirstPage ? disable(_self.previousArrow) : enable(_self.previousArrow); 373 _self.isLastPage ? disable(_self.nextArrow) : enable(_self.nextArrow); 374 if (_self.pagination) { 375 _self.highlightCurrentPage(_self.pageIndex); 376 } 377 if (_self.isCarouselMode) { 378 _self.setActivePageItems(); 379 } else { 380 _self.setAllItemsActive(); 381 } 382 }; 383 384 /** 385 * Set all items to active state 386 */ 387 common.Carousel.prototype.setAllItemsActive = function () { 388 var _self = this; 389 Array.prototype.forEach.call(_self.items, function (item) { 390 item.setAttribute('data-inactive', 'false'); 391 }); 392 }; 393 394 /** 395 * Set all items to inactive except those on the currently active page 396 */ 397 common.Carousel.prototype.setActivePageItems = function () { 398 var _self = this; 399 if (_self.isLastPage) { 400 Array.prototype.forEach.call(_self.items, function (item, i) { 401 if (i < _self.items.length - _self.itemsPerPage) { 402 item.setAttribute('data-inactive', 'true'); 403 } else { 404 item.setAttribute('data-inactive', 'false'); 405 } 406 }); 407 return; 408 } 409 Array.prototype.forEach.call(_self.items, function (item, i) { 410 var itemIndex = _self.pageIndex * _self.itemsPerPage; 411 var minIndex = itemIndex; 412 var maxIndex = itemIndex + _self.itemsPerPage; 413 if (i < minIndex || i >= maxIndex) { 414 item.setAttribute('data-inactive', 'true'); 415 } else { 416 item.setAttribute('data-inactive', 'false'); 417 } 418 }); 419 }; 420 421 /** 422 * Calculate the number of page items according to the current screen size 423 * @return {Integer} - number of items to a page 424 */ 425 common.Carousel.prototype.getItemsPerPage = function () { 426 var _self = this; 427 var desktopMax = '(max-width: ' + _self.breakpoint.desktop + 'px)'; 428 var desktopMin = '(min-width: ' + _self.breakpoint.desktop + 'px)'; 429 var tabletMax = '(max-width: ' + _self.breakpoint.tablet + 'px)'; 430 var tabletMin = '(min-width: ' + _self.breakpoint.tablet + 'px)'; 431 var phabletMax = '(max-width: ' + _self.breakpoint.phablet + 'px)'; 432 var phabletMin = '(min-width: ' + _self.breakpoint.phablet + 'px)'; 433 var mobileMax = '(max-width: ' + _self.breakpoint.mobile + 'px)'; 434 var mobileMin = '(min-width: ' + _self.breakpoint.mobile + 'px)'; 435 var itemsPerPage; 436 if (window.matchMedia(desktopMin).matches) { 437 itemsPerPage = _self.pageItems.wide; 438 } else if (window.matchMedia(tabletMin).matches && window.matchMedia(desktopMax).matches) { 439 itemsPerPage = _self.pageItems.desktop; 440 } else if (window.matchMedia(phabletMin).matches && window.matchMedia(tabletMax).matches) { 441 itemsPerPage = _self.pageItems.tablet; 442 } else if (window.matchMedia(mobileMin).matches && window.matchMedia(phabletMax).matches) { 443 itemsPerPage = _self.pageItems.phablet; 444 } else if (window.matchMedia(mobileMax).matches) { 445 itemsPerPage = _self.pageItems.mobile; 446 } 447 return itemsPerPage; 448 }; 449 450 /** 451 * Reset and render pagination common 452 * Add event listeners to each page selector 453 */ 454 common.Carousel.prototype.renderPagination = function () { 455 var _self = this; 456 _self.pagination.innerHTML = ''; // clear pagination on re-render 457 458 for (var i = 0; i < _self.numPages; i++) { 459 var pageElement = document.createElement('span'); 460 core.style.addClass(pageElement, 'page');
461 core.style.addClass(pageElement, 'js-page'); 462 pageElement.setAttribute('data-page-index', i); 463 var indicator = document.createElement('span'); 464 core.style.addClass(indicator, 'page__indicator'); 465 pageElement.appendChild(indicator); 466 pageElement.addEventListener('click', function () { 467 var page = this.getAttribute('data-page-index'); 468 _self.slideToPageIndex(page); 469 }); 470 _self.pagination.appendChild(pageElement); 471 } 472 }; 473 474 /** 475 * Add active class to current page indicator in pagination common 476 * @param {Integer} index - page index 477 */ 478 common.Carousel.prototype.highlightCurrentPage = function (index) { 479 var _self = this; 480 var pages = _self.pagination.querySelectorAll(_self.config.pageClass); 481 Array.prototype.forEach.call(pages, function (page) { 482 core.style.removeClass(page, ACTIVE_CLASS); 483 }); 484 var activePage = _self.pagination.querySelector('[data-page-index="' + index + '"]'); 485 core.style.addClass(activePage, ACTIVE_CLASS); 486 }; 487 488 /** 489 * Hide left and right navigation buttons, as well as pagination 490 */ 491 common.Carousel.prototype.hideControls = function () { 492 var _self = this; 493 core.style.addClass(_self.previousArrow, HIDE_CLASS); 494 core.style.addClass(_self.nextArrow, HIDE_CLASS); 495 if (_self.pagination) { 496 core.style.addClass(_self.pagination, HIDE_CLASS); 497 } 498 }; 499 500 /** 501 * Show left and right navigation buttons, as well as pagination 502 */ 503 common.Carousel.prototype.showControls = function () { 504 var _self = this; 505 core.style.removeClass(_self.previousArrow, HIDE_CLASS); 506 core.style.removeClass(_self.nextArrow, HIDE_CLASS); 507 if (_self.pagination) { 508 core.style.removeClass(_self.pagination, HIDE_CLASS); 509 } 510 }; 511 common.Carousel.prototype.jumpToHighlightPage = function () { 512 var _self = this; 513 var highlightPageIndex = 0; 514 if (_self.highlightIndex) { 515 highlightPageIndex = Math.floor(_self.highlightIndex / _self.itemsPerPage); 516 } 517 _self.slideToPageIndex(highlightPageIndex); 518 }; 519})(PULSE.app, PULSE.app.common, PULSE.core); 520(function (app, common, core) { 521 'use strict'; 522 523 var COOKIE_EXPIRY = 182; // eslint-disable-line no-magic-numbers 524 var COOKIES_EVENTS = common.constants.COOKIES_EVENTS; 525 var COOKIE_GROUPS = ['cc_advertising', 'cc_analytics']; 526 527 /** 528 * Cookies main function instantiated by app.CookieNotice. 529 * Gets Cookies Consent from local storage and fires corresponding event 530 * @constructor 531 * @see app.CookieNotice 532 */ 533 common.Cookies = function Cookies() { 534 var cookieConsentData = core.localStorage.getStorage('cookies-consent', true); 535 cookieConsentData = cookieConsentData ? JSON.parse(cookieConsentData) : null; 536 if (!cookieConsentData) { 537 var newConsentData = _getDefaultConstentCookie(); 538 core.localStorage.setStorage('cookies-consent', JSON.stringify(newConsentData), COOKIE_EXPIRY, true); 539 common.fireEvent(COOKIES_EVENTS.CONSENT_PENDING); 540 } else if (cookieConsentData && cookieConsentData.consentAll === 'pending') { 541 common.fireEvent(COOKIES_EVENTS.CONSENT_PENDING, cookieConsentData); 542 } else if (cookieConsentData && cookieConsentData.consentAll === 'granted') { 543 common.fireEvent(COOKIES_EVENTS.CONSENT_GRANTED, cookieConsentData); 544 } else if (cookieConsentData && cookieConsentData.consentAll === 'reviewed') { 545 common.fireEvent(COOKIES_EVENTS.CONSENT_REVIEWED, cookieConsentData); 546 } 547 }; 548 549 /** 550 * Registers Cookie Consent by saving it into local storage 551 * under cookie with name of: "cookies-consent" 552 * @param {String} name the name of the cookie to register 553 * @param {String} acceptanceValue the value of the cookie to register 554 */ 555 common.Cookies.prototype.registerCookieConsent = function registerCookieConsent(name, acceptanceValue) { 556 var cookieConsentData = core.localStorage.getStorage('cookies-consent', true); 557 cookieConsentData = cookieConsentData ? JSON.parse(cookieConsentData) : {}; 558 cookieConsentData[name] = acceptanceValue; 559 core.localStorage.setStorage('cookies-consent', JSON.stringify(cookieConsentData), COOKIE_EXPIRY, true); 560 }; 561 562 /** 563 * Gets specific Cookie Consent by retrieveing it from local storage from cookie with name of: "cookies-consent" 564 * @param {String} name the name of the cookie to retrieve 565 */ 566 common.Cookies.prototype.getCookieConsent = function getCookieConsent(name) { 567 var cookieConsentData = core.localStorage.getStorage('cookies-consent', true); 568 cookieConsentData = cookieConsentData ? JSON.parse(cookieConsentData) : null;
569 return cookieConsentData ? cookieConsentData[name] : 'rejected'; 570 }; 571 var _getDefaultConstentCookie = function _getDefaultConstentCookie() { 572 var defaultConsent = { 573 consentAll: 'pending' 574 }; 575 COOKIE_GROUPS.forEach(function (group) { 576 defaultConsent[group] = 'rejected'; 577 }); 578 return defaultConsent; 579 }; 580})(PULSE.app, PULSE.app.common, PULSE.core); 581if (!PULSE) { 582 var PULSE = {}; 583} 584if (!PULSE.CLIENT) { 585 PULSE.CLIENT = {}; 586} 587if (!PULSE.CLIENT.UI) { 588 PULSE.CLIENT.UI = {}; 589} 590 591/** 592 * Class for a dropdown menu 593 * @param {Object} options Object with the following parameters: 594 * dropdownId : The id for the dropdown menu 595 * itemsContainer : The container which holds the dropdown menu items. 596 * itemContainer : Defines a class/element within the itemsContainer which holds each item in the 597 * dropdown menu (e.g. 'li' or 'div' or a class). 598 * currentContainer : The container for the current chosen option of the dropdown menu - clicking 599 * it starts the dropdown. 600 * dropdownContainer : The container which holds the entire dropdown - used for closing the dropdown 601 * menu when the mouse is clicked outside the area. 602 * currentId : OPTIONAL - Defines the initial selected dropdown item 603 * removeSelected : OPTIONAL - Boolean to determine whether the selected item should be hidden from 604 * the dropdown list. 605 * addClass : OPTIONAL - Defines the class _self list elements take if they are selected 606 * changeCurrent : OPTIONAL - Boolean to determine whether to change the current container text with 607 * the selected one. 608 * addCurrent : OPTIONAL - An additional string to append to the current container when a new item is 609 * selected 610 * notUsable : OPTIONAL - Defines whether the dropdown menu should not be usable 611 * notClose : OPTIONAL - Boolean if the dropdown should not close on an item click 612 * closeSpan : OPTIONAL - Boolean to determine whether to hide/show a nearby span, when a dropdown is usable/unsuable 613 * containerOpenClass : OPTIONAL - toggle a class on the container when the dropdown is in an open state 614 * 615 * 616 */ 617PULSE.CLIENT.UI.DropdownMenu = function (container, options) { 618 if (!options) { 619 return; 620 } 621 this.options = options; 622 this.$container = $(container); 623 this.dropdownId = options.dropdownId; 624 this.itemContainer = options.itemContainer; 625 this.$itemsContainer = this.$container.find(options.itemsContainer); 626 this.$itemList = this.$itemsContainer.find(this.itemContainer); //li or div 627 this.$currentContainer = this.$container.find(options.currentContainer); 628 this.$dropdownContainer = this.$container.find(options.dropdownContainer); 629 this.currentId = options.currentId; 630 this.removeSelected = options.removeSelected; 631 this.addClass = options.addClass; 632 this.changeCurrent = options.changeCurrent; 633 this.previousCurrent = this.$currentContainer.text(); 634 this.notUsable = options.notUsable; 635 this.notClose = options.notClose; 636 this.addCurrent = options.addCurrent; 637 this.closeSpan = options.closeSpan; 638 this.containerOpenClass = options.containerOpenClass || false; 639 640 /** 641 * container and dropdown container may be the same 642 */ 643 if (!this.$dropdownContainer.length) { 644 this.$dropdownContainer = this.$container; 645 } 646 if (typeof this.currentId === 'undefined') { 647 this.currentId = -1; 648 } 649 if (options.notUsable) { 650 this.setNotUsable(); 651 } 652 this.setListeners(); 653 this.hideDropdown(); 654 if (this.currentId > -1) { 655 if (this.$itemList.length > this.currentId) { 656 this.$itemList[this.currentId].click(); 657 } 658 } 659}; 660 661/** 662 * Defines the listeners for the dropdown menu 663 */ 664PULSE.CLIENT.UI.DropdownMenu.prototype.setListeners = function () { 665 var _self = this; 666 667 // Displays the dropdown menu when the current choice is clicked. 668 // Closes it if the dropdown menu is already open 669 this.$currentContainer.on('click', function (evt) { 670 if (evt) { 671 evt.preventDefault(); 672 } 673 if (!_self.notUsable) { 674 if (_self.$itemsContainer.css('display') === 'none') { 675 _self.showDropdown(); 676 } else { 677 _self.hideDropdown(); 678 } 679 } 680 }); 681 682 // For each item in the list, selects the index and closes the dropdown when one it is selected. 683 // Optionally, can also remove the item from the dropdown list if it is selected, 684 // add a class to the selected item or change the currently selected items. 685 // The list will send an event trigger once an item has been selected, along with data with: 686 // - index : Index of the selected item in the item list 687 // - text : Text within the selected item in the dropdown list 688 // - id : The id of the dropdown menu 689 690 $(_self.options.itemsContainer, _self.$container).on('click', _self.itemContainer, function (evt) { 691 /** 692 * find the index relative to the itemlist, the actual list in dom
693 * may have various titles and dividers inside it. 694 */ 695 var index = parseInt(_self.$itemList.index(this)); 696 evt.preventDefault(); 697 if (_self.removeSelected || _self.addClass) { 698 for (var i = 0; i < _self.$itemList.length; i++) { 699 if (i === index) { 700 if (_self.removeSelected) { 701 $(_self.$itemList[i]).hide(); 702 } 703 if (_self.addClass) { 704 $(_self.$itemList[i]).addClass(_self.addClass); 705 } 706 } else { 707 if (_self.removeSelected) { 708 $(_self.$itemList[i]).show(); 709 } 710 if (_self.addClass) { 711 $(_self.$itemList[i]).removeClass(_self.addClass); 712 } 713 } 714 } 715 } 716 var contain = _self.$currentContainer.find(_self.$itemList); 717 if (contain.length < 1) { 718 if (!_self.notClose) { 719 _self.hideDropdown(); 720 } 721 } 722 _self.currentId = index; 723 var selectedText = $(_self.$itemList[index]).text(); 724 _self.previousCurrent = _self.$currentContainer.text(); 725 if (_self.changeCurrent) { 726 if (_self.addCurrent) { 727 _self.$currentContainer.html(selectedText + _self.addCurrent); 728 } else { 729 _self.$currentContainer.text(selectedText); 730 } 731 } 732 _self.$dropdownContainer.trigger('itemSelected', { 733 index: index, 734 text: selectedText, 735 id: _self.dropdownId 736 }); 737 }); 738 739 // Hides the dropdown menu when a click is recognised outside the dropdown container. 740 // The menu is not hidden if the current item container is clicked. 741 $(document).mousedown(function (evt) { 742 var container = _self.$dropdownContainer; 743 _self.count = 0; 744 if (!container.is(evt.target) && container.has(evt.target).length === 0 && !_self.$currentContainer.is(evt.target)) { 745 if (!_self.notClose) { 746 _self.hideDropdown(); 747 } 748 } 749 }); 750}; 751PULSE.CLIENT.UI.DropdownMenu.prototype.showDropdown = function () { 752 this.$container.addClass(this.containerOpenClass); 753 this.$itemsContainer.show(); 754}; 755PULSE.CLIENT.UI.DropdownMenu.prototype.hideDropdown = function () { 756 this.$container.removeClass(this.containerOpenClass); 757 this.$itemsContainer.hide(); 758}; 759PULSE.CLIENT.UI.DropdownMenu.prototype.refreshCurrent = function (currentIndex) { 760 var _self = this; 761 762 /** 763 * re-evaluate the items list 764 */ 765 _self.$itemList = this.$itemsContainer.find(this.itemContainer); 766 _self.currentId = currentIndex || 0; 767 var selectedText = $(_self.$itemList[_self.currentId]).text(); 768 _self.previousCurrent = _self.$currentContainer.text(); 769 if (_self.changeCurrent) { 770 if (_self.addCurrent) { 771 _self.$currentContainer.html(selectedText + _self.addCurrent); 772 } else { 773 _self.$currentContainer.text(selectedText); 774 } 775 } 776 _self.$dropdownContainer.trigger('itemSelected', { 777 index: currentIndex || _self.currentId, 778 text: selectedText, 779 id: _self.dropdownId 780 }); 781}; 782 783/** 784 * Returns the item container of the currently selected menu item 785 * @return {Object} Item Container 786 */ 787PULSE.CLIENT.UI.DropdownMenu.prototype.getSelected = function () { 788 return this.$itemsContainer.children()[this.currentId]; 789}; 790 791/** 792 * Refreshes the click listeners and item list if the dropdown menu has been altered 793 */ 794PULSE.CLIENT.UI.DropdownMenu.prototype.refreshItems = function () { 795 this.$itemList = this.$itemsContainer.find(this.itemContainer); 796 for (var i = 0; i < this.$itemList.length; i++) { 797 if ($(this.$itemList[i]).text() === $(this.$currentContainer).text()) { 798 $(this.$itemList[i]).addClass('selected'); 799 } 800 } 801}; 802PULSE.CLIENT.UI.DropdownMenu.prototype.setUsable = function () { 803 if (this.closeSpan) { 804 var parent = this.$currentContainer.parent(), 805 span = $(parent).find(this.closeSpan); 806 if (span) { 807 $(span).show(); 808 } 809 } 810 this.notUsable = false; 811}; 812PULSE.CLIENT.UI.DropdownMenu.prototype.setNotUsable = function () { 813 if (this.closeSpan) { 814 var parent = this.$currentContainer.parent(), 815 span = $(parent).find(this.closeSpan); 816 if (span) { 817 $(span).hide(); 818 } 819 } 820 this.notUsable = true; 821}; 822PULSE.CLIENT.UI.DropdownMenu.prototype.isUsable = function () { 823 return !this.notUsable; 824}; 825PULSE.CLIENT.UI.DropdownMenu.prototype.getPrevious = function () { 826 return this.previousCurrent; 827}; 828if (!PULSE) { 829 var PULSE = {}; 830} 831if (!PULSE.CLIENT) { 832 PULSE.CLIENT = {}; 833} 834if (!PULSE.CLIENT.UI) { 835 PULSE.CLIENT.UI = {}; 836} 837PULSE.CLIENT.UI.ListScroller = function (options) { 838 var _self = this; 839 var DEFAULT_SCROLL_LENGTH = 2; 840 if (!options) { 841 return; 842 } 843 this.$itemList = $(options.listContainer); 844 this.itemListWidth = parseInt(this.$itemList.css('width').replace('px', '')); 845 this.$wrapper = $(options.wrapper);
846 this.wrapperWidth = parseInt(this.$wrapper.css('width').replace('px', '')); 847 this.$upScroll = $(options.upScrollButton); 848 this.$downScroll = $(options.downScrollButton); 849 this.maxHeight = options.maxHeight || this.$wrapper.css('max-height'); 850 this.maxWidth = options.maxWidth || this.$wrapper.css('max-width'); 851 this.scrollLength = options.scrollLength || DEFAULT_SCROLL_LENGTH; 852 this.scrollSize = options.scrollSize; 853 this.orientation = options.orientation || 'vertical'; 854 this.alignment = options.alignment || 'right'; 855 this.animating = false; 856 this.cssTrans = options.cssTrans || false; 857 $('body').on('transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd', this.$itemList, function () { 858 _self.animating = false; 859 _self.refreshScrolling(); 860 }); 861 if (this.orientation === 'vertical') { 862 this.$upScroll.click(function (evt) { 863 evt.preventDefault(); 864 if (_self.animating) { 865 return; 866 } 867 _self.animating = true; 868 var topOffset = Math.abs(parseInt(_self.$itemList.css('top'), 10)), 869 listHeight = _self.$itemList.height(), 870 scrollHeight = listHeight / _self.$itemList.children().length * _self.scrollLength; 871 var offset = topOffset < scrollHeight ? topOffset : scrollHeight; 872 _self.$itemList.animate({ 873 top: '+=' + offset 874 }, 'fast', 'easeInSine', function () { 875 _self.animating = false; 876 _self.refreshScrolling(); 877 }); 878 }); 879 this.$downScroll.click(function (evt) { 880 evt.preventDefault(); 881 if (_self.animating) { 882 return; 883 } 884 _self.animating = true; 885 var topOffset = Math.abs(parseInt(_self.$itemList.css('top'), 10)), 886 containerHeight = _self.$wrapper.height(), 887 listHeight = _self.$itemList.height(), 888 remainder = listHeight - containerHeight - topOffset, 889 scrollHeight = listHeight / _self.$itemList.children().length * _self.scrollLength; 890 var offset = remainder < scrollHeight ? remainder : scrollHeight; 891 _self.$itemList.animate({ 892 top: '-=' + offset 893 }, 'fast', 'easeOutSine', function () { 894 _self.animating = false; 895 _self.refreshScrolling(); 896 }); 897 }); 898 } else { 899 this.$upScroll.click(function (evt) { 900 evt.preventDefault(); 901 if (_self.animating) { 902 return; 903 } 904 _self.animating = true; 905 var topOffset = Math.abs(parseInt(_self.$itemList.css(_self.alignment), 10)), 906 listHeight = _self.$itemList.innerWidth(), 907 scrollLength = listHeight / _self.$itemList.children().length * _self.scrollLength; 908 var offset = topOffset < scrollLength ? topOffset : scrollLength; 909 offset = _self.scrollSize ? _self.scrollSize : offset; 910 if (_self.alignment === 'right') { 911 if ($('html').hasClass('csstransitions') && _self.cssTrans) { 912 _self.$itemList.css('right', '+=' + offset); 913 } else { 914 _self.$itemList.animate({ 915 right: '+=' + offset 916 }, 'fast', 'easeInSine', function () { 917 _self.animating = false; 918 _self.refreshScrolling(); 919 }); 920 } 921 } else if ($('html').hasClass('csstransitions') && _self.cssTrans) { 922 var curPos = parseInt(_self.$itemList.css('left').replace('px', '')); 923 var newOffSet = parseInt(curPos + offset); 924 if (newOffSet > 0) { 925 _self.$itemList.css('left', '0px'); 926 } else { 927 _self.$itemList.css('left', '+=' + offset); 928 } 929 } else { 930 _self.$itemList.animate({ 931 left: '+=' + offset 932 }, 'fast', 'easeInSine', function () { 933 _self.animating = false; 934 _self.refreshScrolling(); 935 }); 936 } 937 }); 938 this.$downScroll.click(function (evt) { 939 evt.preventDefault(); 940 if (_self.animating) { 941 return; 942 } 943 _self.animating = true; 944 var topOffset = Math.abs(parseInt(_self.$itemList.css(_self.alignment), 10)), 945 containerHeight = _self.maxWidth || _self.$wrapper.width(), 946 listHeight = _self.$itemList.innerWidth(), 947 remainder = listHeight - containerHeight - topOffset, 948 scrollLength = listHeight / _self.$itemList.children().length * _self.scrollLength; 949 var offset = remainder < scrollLength ? remainder : scrollLength; 950 offset = _self.scrollSize ? _self.scrollSize : offset; 951 if (_self.alignment === 'right') { 952 if ($('html').hasClass('csstransitions') && _self.cssTrans) { 953 _self.$itemList.css('right', '+=' + offset); 954 } else { 955 _self.$itemList.animate({ 956 right: '-=' + offset 957 }, 'fast', 'easeOutSine', function () { 958 _self.animating = false;
959 _self.refreshScrolling(); 960 }); 961 } 962 } else if ($('html').hasClass('csstransitions') && _self.cssTrans) { 963 var maxScroll = -Math.abs(_self.itemListWidth - _self.wrapperWidth); 964 var curPos = parseInt(_self.$itemList.css('left').replace('px', '')); 965 var newOffSet = parseInt(curPos - offset); 966 if (newOffSet <= maxScroll) { 967 offset = curPos - maxScroll; 968 _self.animating = false; 969 } 970 _self.$itemList.css('left', '-=' + offset); 971 } else { 972 _self.$itemList.animate({ 973 left: '-=' + offset 974 }, 'fast', 'easeOutSine', function () { 975 _self.animating = false; 976 _self.refreshScrolling(); 977 }); 978 } 979 }); 980 } 981}; 982PULSE.CLIENT.UI.ListScroller.prototype.refreshScrolling = function () { 983 var BUFFER = 30; 984 if (this.orientation === 'vertical') { 985 var maxHeight = parseInt(this.maxHeight, 10), 986 containerHeight = this.$wrapper.height(), 987 listHeight = this.$itemList.height(); 988 if (containerHeight < maxHeight) { 989 this.$upScroll.hide(); 990 this.$downScroll.hide(); 991 } else { 992 var topOffset = parseInt(this.$itemList.css('top'), 10); 993 if (topOffset + BUFFER < 0) { 994 this.$upScroll.fadeIn(); 995 } else { 996 this.$upScroll.hide(); 997 } 998 if (-topOffset + containerHeight + BUFFER < listHeight) { 999 this.$downScroll.fadeIn(); 1000 } else { 1001 this.$downScroll.hide(); 1002 } 1003 } 1004 } else { 1005 var maxWidth = parseInt(this.maxWidth, 10), 1006 containerWidth = this.$wrapper.width(), 1007 listWidth = 0; 1008 this.$itemList.children().each(function () { 1009 listWidth += $(this).outerWidth(true); 1010 }); 1011 if (listWidth === 0) { 1012 listWidth = this.$itemList.width(); 1013 } 1014 if (containerWidth < maxWidth) { 1015 this.$upScroll.hide(); 1016 this.$downScroll.hide(); 1017 } else { 1018 //Assume offset is left to begin with 1019 var offset = parseInt(this.$itemList.css('left'), 10); 1020 if (this.alignment === 'right') { 1021 offset = parseInt(this.$itemList.css('right'), 10); 1022 } 1023 if (offset + BUFFER < 0) { 1024 this.$upScroll.fadeIn(); 1025 } else { 1026 this.$upScroll.hide(); 1027 } 1028 if (-offset + containerWidth + BUFFER < listWidth) { 1029 this.$downScroll.fadeIn(); 1030 } else { 1031 this.$downScroll.hide(); 1032 } 1033 } 1034 } 1035}; 1036$(function () { 1037 /* Sticky main nav */ 1038 $('.mainNavContainer').stickyHeader({ 1039 cssClass: 'stuckMenu', 1040 fixedClass: 'isStuck', 1041 container: '.mainNav' 1042 }); 1043 $('.main-nav').stickyHeader({ 1044 cssClass: 'stuckMenu', 1045 fixedClass: 'isStuck', 1046 container: '.main-nav__list' 1047 }); 1048 $('.socialBox.inTheTitle').click(function () { 1049 $('.socialBox .shareOptions.grey').toggle(); 1050 }); 1051 1052 /* General filter dropdowns */ 1053 $('body').on('click', '.filterDropdown', function (evt) { 1054 $('.filterDropdown').not($(this)).removeClass('open'); 1055 if ($(this).hasClass('open')) { 1056 $(this).removeClass('open'); 1057 $(this).removeClass('active'); 1058 } else { 1059 evt.stopPropagation(); 1060 $(this).addClass('open'); 1061 $(this).addClass('active'); 1062 } 1063 evt.preventDefault(); 1064 }); 1065 $('html').click(function (evt) { 1066 $('.filterDropdown').each(function () { 1067 if ($(this).hasClass('open')) { 1068 $(this).removeClass('open'); 1069 $(this).removeClass('active'); 1070 } 1071 }); 1072 if ((!$('.corporateMastheadContent').is(evt.target) && $('.corporateMastheadContent').has(evt.target).length) === 0) { 1073 $('.corporateMastheadContent').hide(); 1074 $('.worldRugbyMenuBtn').removeClass('active'); 1075 } 1076 }); 1077 1078 /** 1079 * Hide all modals when clicking on the body 1080 */ 1081 $('body').on('click', '.modal, .modalOverlay', function () { 1082 $('.modal').removeClass('md-show'); 1083 $('.modalOverlay').css('visibility', 'hidden'); 1084 }); 1085 1086 /** 1087 * Don't hide modals when clicking on the modals themselves 1088 */ 1089 $('body').on('click', '.md-content', function (evt) { 1090 evt.stopPropagation(); 1091 evt.preventDefault(); 1092 }); 1093 $('body').on('click', '.modal .md-content .close', function (evt) { 1094 $('.modal').removeClass('md-show'); 1095 $('.modalOverlay').css('visibility', 'hidden'); 1096 evt.preventDefault(); 1097 }); 1098 $('.fixtureContainer .matchBtns .more').click(function () { 1099 if ($(this).hasClass('dropdownOpen')) { 1100 $(this).removeClass('dropdownOpen'); 1101 } else { 1102 $('.dropdownOpen').removeClass('dropdownOpen'); 1103 $(this).addClass('dropdownOpen'); 1104 } 1105 }); 1106 $('.worldRugbyMenuBtn').click(function (evt) { 1107 $('.corporateMastheadContent').toggle(); 1108 $(this).toggleClass('active'); 1109 evt.stopPropagation(); 1110 evt.preventDefault(); 1111 }); 1112 1113 /* Back To Top */ 1114 $('#backToTop').click(function (evt) { 1115 window.scrollTo({ 1116 left: 0, 1117 top: 0, 1118 behavior: 'smooth' 1119 }); 1120 evt.preventDefault(); 1121 }); 1122}); 1123/* eslint-disable */ 1124if (!PULSE) { 1125 var PULSE = {}; 1126} 1127if (!PULSE.CLIENT) { 1128 PULSE.CLIENT = {}; 1129} 1130if (!PULSE.CLIENT.UI) { 1131 PULSE.CLIENT.UI = {}; 1132} 1133 1134/** 1135 * Logic for the pagination of content 1136 * Options: 1137 * container : container for the widget 1138 * forwardContainer : The forward button to get the next page of content. 1139 * backwardContainer : The backwards button to get the previous page of content. 1140 * currentNumberContainer : Container which contains the current page number 1141 * maxNumberContainer : Container which contains the maximum number of pages 1142 * template : The template for the retrived data 1143 * displayMoreContainer : The container to display the new content 1144 * 1145 * numEntries : The number of entries found for the data 1146 * pageSize : The number of entries per page 1147 * maxEntries : The maximum number of entries to display 1148 * 1149 */ 1150PULSE.CLIENT.UI.Pagination = function (options) { 1151 if (!options) { 1152 return; 1153 } 1154 this.$container = $(options.container); 1155 this.$forwardContainer = this.$container.find(options.forwardContainer); 1156 this.$backwardContainer = this.$container.find(options.backwardContainer); 1157 this.$currentNumberContainer = this.$container.find(options.currentNumberContainer); 1158 this.$maxNumberContainer = this.$container.find(options.maxNumberContainer); 1159 this.$displayMoreContainer = this.$container.find(options.displayMoreContainer); 1160 this.template = options.template; 1161 this.options = options; 1162 this.numEntries = parseInt(this.$container.data('num-entries')); 1163 if (options.numEntries) { 1164 this.numEntries = options.numEntries; 1165 } 1166 this.pageSize = parseInt(this.$container.data('page-size')); 1167 if (options.pageSize) { 1168 this.pageSize = options.pageSize; 1169 } 1170 this.maxEntries = parseInt(this.$container.data('max')); 1171 if (options.maxEntries) { 1172 this.maxEntries = options.maxEntries; 1173 } 1174 if (options.urlPaginated) { 1175 this.urlPaginationExpr = options.urlPaginated; 1176 } 1177 this.contentType = this.$container.data('content-type'); 1178 this.pageData = {}; //Caches the pages 1179 1180 this.paginationId = options.paginationId; 1181 this.maxPages = Math.ceil(this.numEntries / this.pageSize); 1182 if (this.maxPages < 1) { 1183 this.maxPages = 1; 1184 } 1185 this.$maxNumberContainer.html(this.maxPages); 1186 this.contentTags = this.$container.data('tags'); 1187 this.references = this.$container.data('references'); 1188 if (this.contentType === 'news') { 1189 this.contentType = 'text'; 1190 } 1191 this.urlGen = PULSE.CLIENT.RUGBY.getUrlGenerator({ 1192 type: this.contentType 1193 }); 1194 this.dataManager = PULSE.CLIENT.getJqueryDataManager(); 1195 this.pageNum = 0; 1196 this.$backwardContainer.addClass('inactive'); 1197 if (this.maxPages < 2) { 1198 this.$forwardContainer.addClass('inactive'); 1199 } 1200 this.setListeners(); 1201}; 1202PULSE.CLIENT.UI.Pagination.prototype.checkUrl = function () { 1203 if (this.options.urlPaginated && parseInt(this.getPageFromUrl(this.options.urlPaginated)) > 1) { 1204 this.setPage(this.getPageFromUrl(this.options.urlPaginated)); 1205 } 1206 if (this.options.urlPaginated) { 1207 var _self = this; 1208 window.onpopstate = function () { 1209 _self.setPage(_self.getPageFromUrl(_self.options.urlPaginated)); 1210 _self.$currentNumberContainer.val(parseInt(_self.getPageFromUrl(_self.options.urlPaginated))); 1211 }; 1212 } 1213}; 1214PULSE.CLIENT.UI.Pagination.prototype.getPageFromUrl = function (evaluatingExpr) { 1215 //return "/\/.+\//".test(string); 1216 if (this.isRegExp(evaluatingExpr)) { 1217 //url pagination via regular expession 1218 return evaluatingExpr.exec(window.location.pathname)[1]; 1219 } 1220 1221 //url pagination via parameter 1222 var params = PULSE.CLIENT.Util.parseUrlParameters(); 1223 if (params[evaluatingExpr]) { 1224 return params[evaluatingExpr]; 1225 } 1226 1227 //what if we pop state back to start page 1228 //with no parameter (but widget is url paginated) 1229 return 1; 1230}; 1231PULSE.CLIENT.UI.Pagination.prototype.isRegExp = function (string) { 1232 return /\/.+\//.test(string); 1233}; 1234PULSE.CLIENT.UI.Pagination.prototype.setListeners = function () { 1235 var that = this; 1236 this.$backwardContainer.on('click', function (evt) { 1237 evt.preventDefault(); 1238 if (that.pageNum > 0) { 1239 that.pageNum = that.pageNum - 1; 1240 //that.$currentNumberContainer.html( that.pageNum + 1 ); 1241 that.$currentNumberContainer.val(that.pageNum + 1); 1242 that.total = that.total - that.pastAdd - that.pageSize; 1243 if (!(that.pageNum in that.pageData)) { 1244 that.loading(); 1245 // Notify widget to get more content 1246 PULSE.CLIENT.notify('content/get', { 1247 id: that.paginationId, 1248 success: true 1249 }); 1250 } else { 1251 that.publishData(); 1252 } 1253 if (that.pageNum == 0) { 1254 that.$backwardContainer.addClass('inactive'); 1255 } 1256 that.$forwardContainer.removeClass('inactive'); 1257 if (that.options.urlPaginated) { 1258 that.updateUrlParam(that.pageNum, that.pageNum + 1); 1259 } 1260 } 1261 }); 1262 this.$forwardContainer.on('click', function (evt) { 1263 evt.preventDefault(); 1264 if (that.pageNum < that.maxPages - 1) { 1265 that.pageNum = that.pageNum + 1; 1266 //that.$currentNumberContainer.html( that.pageNum + 1 ); 1267 that.$currentNumberContainer.val(that.pageNum + 1); 1268 if (!(that.pageNum in that.pageData)) { 1269 that.loading(); 1270 // Notify widget to get more content 1271 PULSE.CLIENT.notify('content/get', { 1272 id: that.paginationId, 1273 success: true 1274 }); 1275 } else { 1276 that.publishData(); 1277 } 1278 if (that.pageNum === that.maxPages - 1) { 1279 that.$forwardContainer.addClass('inactive'); 1280 } 1281 that.$backwardContainer.removeClass('inactive'); 1282 if (that.options.urlPaginated) { 1283 that.updateUrlParam(that.pageNum, that.pageNum - 1); 1284 } 1285 } 1286 }); 1287 this.$currentNumberContainer.on('change', function (evt) { 1288 evt.preventDefault(); 1289 var currentValue = that.$currentNumberContainer.val(); 1290 if (that.options.urlPaginated) { 1291 that.updateUrlParam(parseInt(that.$currentNumberContainer.val()) - 1, that.pageNum); 1292 } 1293 that.setPage(currentValue); 1294 }); 1295}; 1296PULSE.CLIENT.UI.Pagination.prototype.updateUrlParam = function (next, previous) { 1297 next > this.maxPages ? next = this.maxPages : next++; 1298 previous < 0 ? previous = 0 : previous++; 1299 if (window.location.href.indexOf(this.options.urlPaginated + '=') !== -1) { 1300 var oldstring = this.options.urlPaginated + '=' + previous; 1301 var newstring = this.options.urlPaginated + '=' + next; 1302 var newurl = window.location.href.replace(oldstring, newstring); 1303 window.history.pushState({}, 'Rugby World Cup 2015', newurl); 1304 } else if (window.location.href.indexOf('&') !== -1) { 1305 //append at end of multiple params 1306 var newurl = window.location.href + '&' + this.options.urlPaginated + '=' + next; 1307 window.history.pushState({}, 'Rugby World Cup 2015', newurl); 1308 } else { 1309 var newurl = window.location.href + '?' + this.options.urlPaginated + '=' + next; 1310 window.history.pushState({}, 'Rugby World Cup 2015', newurl); 1311 } 1312}; 1313PULSE.CLIENT.UI.Pagination.prototype.setPage = function (currentValue) { 1314 var isInteger = /^\+?(0|[1-9]\d*)$/.test(currentValue); 1315 if (isInteger) { 1316 var val = parseInt(currentValue); 1317 if (val < 1) { 1318 val = 1; 1319 this.$currentNumberContainer.val(val); 1320 } 1321 if (val > this.maxPages) { 1322 val = this.maxPages; 1323 this.$currentNumberContainer.val(val); 1324 } 1325 this.pageNum = val - 1; 1326 if (!(this.pageNum in this.pageData)) { 1327 this.loading(); 1328 // Notify widget to get more content 1329 PULSE.CLIENT.notify('content/get', { 1330 id: this.paginationId, 1331 success: true 1332 }); 1333 } else { 1334 this.publishData(); 1335 } 1336 if (this.pageNum === this.maxPages - 1) { 1337 this.$forwardContainer.addClass('inactive'); 1338 } else { 1339 this.$forwardContainer.removeClass('inactive'); 1340 } 1341 if (this.pageNum < 1) { 1342 this.$backwardContainer.addClass('inactive'); 1343 } else { 1344 this.$backwardContainer.removeClass('inactive'); 1345 } 1346 } else { 1347 this.$currentNumberContainer.val(this.pageNum + 1); 1348 } 1349}; 1350PULSE.CLIENT.UI.Pagination.prototype.setData = function (data) { 1351 this.pageData[this.pageNum] = data; 1352 this.setPageInfo(data); 1353 this.publishData(); 1354}; 1355PULSE.CLIENT.UI.Pagination.prototype.setPageInfo = function (data) { 1356 if (data && data.pageInfo) { 1357 var pageInfo = data.pageInfo; 1358 if (typeof pageInfo.numEntries !== 'undefined') { 1359 this.numEntries = pageInfo.numEntries; 1360 } 1361 if (typeof pageInfo.pageSize !== 'undefined') { 1362 this.pageSize = pageInfo.pageSize; 1363 } 1364 if (typeof pageInfo.numPages !== 'undefined') { 1365 this.maxPages = pageInfo.numPages; 1366 } 1367 1368 // This is base 0 1369 if (typeof pageInfo.page !== 'undefined') { 1370 this.pageNum = pageInfo.page; 1371 } 1372 this.refreshPagination(); 1373 } 1374}; 1375PULSE.CLIENT.UI.Pagination.prototype.refreshPagination = function () { 1376 this.maxPages = Math.ceil(this.numEntries / this.pageSize); 1377 this.$maxNumberContainer.html(this.maxPages); 1378 this.$currentNumberContainer.val(this.maxPages > 0 ? this.pageNum + 1 : this.pageNum); 1379 this.$backwardContainer.addClass('inactive'); 1380 if (this.pageNum === this.maxPages - 1 || this.maxPages < 1) { 1381 this.$forwardContainer.addClass('inactive'); 1382 } else { 1383 this.$forwardContainer.removeClass('inactive'); 1384 } 1385 if (this.pageNum < 1) { 1386 this.$backwardContainer.addClass('inactive'); 1387 } else { 1388 this.$backwardContainer.removeClass('inactive'); 1389 } 1390}; 1391PULSE.CLIENT.UI.Pagination.prototype.publishData = function () { 1392 var data = this.pageData[this.pageNum]; 1393 this.$displayMoreContainer.html(''); 1394 if (typeof data.isImageVersion === 'undefined') { 1395 data.isImageVersion = false;
1396 } 1397 for (var i = 0; i < data.content.length; i++) { 1398 PULSE.CLIENT.Template.append(this.template, this.$displayMoreContainer, { 1399 content: data.content[i], 1400 pageSize: this.pageSize, 1401 isImageVersion: data.isImageVersion 1402 }); 1403 } 1404 var offset = this.$displayMoreContainer.offset(); 1405 if (offset && this.pageNum !== 0) { 1406 var top = offset.top; 1407 top = top - 60; 1408 if (top < 0) { 1409 top = 0; 1410 } 1411 window.scrollTo({ 1412 left: 0, 1413 top: top, 1414 behavior: 'smooth' 1415 }); 1416 } 1417 PULSE.CLIENT.notify('content/update', { 1418 id: this.paginationId, 1419 success: true 1420 }); 1421}; 1422PULSE.CLIENT.UI.Pagination.prototype.getPageSize = function () { 1423 return this.pageSize; 1424}; 1425PULSE.CLIENT.UI.Pagination.prototype.getPageNum = function () { 1426 return this.pageNum; 1427}; 1428PULSE.CLIENT.UI.Pagination.prototype.getNumEntries = function () { 1429 return this.maxPages; 1430}; 1431PULSE.CLIENT.UI.Pagination.prototype.resetPaginationUi = function () { 1432 this.maxPages = Math.ceil(this.numEntries / this.pageSize); 1433 this.$maxNumberContainer.html(this.maxPages); 1434 this.$currentNumberContainer.val(1); 1435 this.$backwardContainer.addClass('inactive'); 1436 if (this.maxPages < 2) { 1437 this.$forwardContainer.addClass('inactive'); 1438 this.$container.find('.pagination').hide(); 1439 } else { 1440 this.$forwardContainer.removeClass('inactive'); 1441 this.$container.find('.pagination').show(); 1442 } 1443 if (this.maxPages < 1) { 1444 this.$maxNumberContainer.html('1'); 1445 } 1446}; 1447PULSE.CLIENT.UI.Pagination.prototype.setNumEntries = function (entries) { 1448 this.numEntries = entries; 1449 1450 //this.$currentNumberContainer.html( this.pageNum + 1 ); 1451 this.$currentNumberContainer.val(this.pageNum + 1); 1452 this.maxPages = Math.ceil(this.numEntries / this.pageSize); 1453 this.$maxNumberContainer.html(this.maxPages); 1454 this.$backwardContainer.removeClass('inactive'); 1455 if (this.maxPages < 2) { 1456 this.$container.find('.pagination').hide(); 1457 this.$forwardContainer.addClass('inactive'); 1458 } else { 1459 this.$container.find('.pagination').show(); 1460 this.$forwardContainer.removeClass('inactive'); 1461 } 1462 1463 //this.$backwardContainer.addClass( 'inactive' ); 1464 if (this.pageNum > this.maxPages - 1) { 1465 this.pageNum = this.maxPages - 1; 1466 this.$forwardContainer.addClass('inactive'); 1467 //this.$currentNumberContainer.html( this.pageNum + 1 ); 1468 this.$currentNumberContainer.val(this.pageNum + 1); 1469 } else if (this.pageNum === this.maxPages - 1) { 1470 this.$forwardContainer.addClass('inactive'); 1471 } 1472 if (this.pageNum < 1) { 1473 this.$backwardContainer.addClass('inactive'); 1474 } 1475 if (entries < 1) { 1476 this.$maxNumberContainer.html('1'); 1477 //this.$currentNumberContainer.html( '1' ); 1478 this.$currentNumberContainer.val(1); 1479 this.$forwardContainer.addClass('inactive'); 1480 this.$backwardContainer.addClass('inactive'); 1481 } 1482}; 1483PULSE.CLIENT.UI.Pagination.prototype.loading = function () { 1484 this.$displayMoreContainer.html('<div class="spinner"><div style="height : 25px; width : 25px"></div></div>'); 1485}; 1486/* eslint-enable */ 1487if (!PULSE) { 1488 var PULSE = {}; 1489} 1490if (!PULSE.CLIENT) { 1491 PULSE.CLIENT = {}; 1492} 1493if (!PULSE.CLIENT.UI) { 1494 PULSE.CLIENT.UI = {}; 1495} 1496 1497/** 1498 * Logic for the PlaylistSlider of content 1499 * Options: 1500 * container : container for the widget 1501 * forwardContainer : The forward button to get the next page of content. 1502 * backwardContainer : The backwards button to get the previous page of content. 1503 * template : The template for the retrived data 1504 * displayMoreContainer : The container to display the new content 1505 */ 1506PULSE.CLIENT.UI.PlaylistSlider = function (options) { 1507 if (!options) { 1508 return; 1509 } 1510 var playListStrings; 1511 this.$container = $(options.container); 1512 this.$forwardContainer = this.$container.find(options.forwardContainer); 1513 this.$backwardContainer = this.$container.find(options.backwardContainer); 1514 this.$displayMoreContainer = this.$container.find(options.displayMoreContainer); 1515 this.template = options.template; 1516 this.numEntries = parseInt(this.$container.data('num-entries')); 1517 this.pageLimit = parseInt(this.$container.data('show-more')); 1518 var playlistItems = this.$container.data('playlist-items'); 1519 if (playlistItems) { 1520 playListStrings = this.$container.data('playlist-items').split(';'); 1521 this.playlistItems = []; 1522 1523 //Put all the content playlist data into an object 1524 for (var i = 0; i < playListStrings.length; i++) { 1525 var commaSplit = playListStrings[i].split('-'); 1526 this.playlistItems.push({ 1527 id: commaSplit[0].replace(/,/g, ''), 1528 type: commaSplit[1] 1529 }); 1530 } 1531 this.itemData = {}; //Caches the content data for the playlist 1532 1533 // Check to see how many playist items there are and only display the forward arrow 1534 // if there are more to find 1535 if (this.numEntries < this.pageLimit) { 1536 this.pageLimit = this.numEntries; 1537 this.$forwardContainer.hide(); 1538 } 1539 1540 // Set the first and last index of the playlist to show 1541 this.firstIndex = 0; 1542 this.lastIndex = this.pageLimit; 1543 this.getCount = 0; 1544 this.urlGen = PULSE.CLIENT.RUGBY.getUrlGenerator(); 1545 this.dataManager = PULSE.CLIENT.getJqueryDataManager(); 1546 this.$backwardContainer.hide(); 1547 this.setListeners(); 1548 } 1549}; 1550 1551/** 1552 * Checks to see if all the media content has been cached in itemData 1553 * @return {Boolean} haveData 1554 */ 1555PULSE.CLIENT.UI.PlaylistSlider.prototype.checkData = function () { 1556 var _self = this; 1557 if (!(_self.getItems[_self.getCount].id in _self.itemData)) { 1558 PULSE.CLIENT.notify('playlist/get', { 1559 id: _self.getItems[_self.getCount].id, 1560 type: _self.getItems[_self.getCount].type, 1561 success: true 1562 }); 1563 } else { 1564 _self.publishData(_self.itemData[_self.getItems[_self.getCount].id]); 1565 } 1566}; 1567PULSE.CLIENT.UI.PlaylistSlider.prototype.setListeners = function () { 1568 var _self = this; 1569 1570 //Handles backwards clicks 1571 this.$backwardContainer.on('click', function (evt) { 1572 evt.preventDefault(); 1573 if (_self.firstIndex > 0) { 1574 _self.firstIndex = _self.firstIndex - 1; 1575 _self.lastIndex = _self.lastIndex - 1; 1576 _self.getPlaylistItems(); 1577 _self.$displayMoreContainer.html(''); 1578 _self.getCount = 0; 1579 //If not all data is cached then request data for the missing content 1580 //Otherwise publish the data 1581 _self.checkData(); 1582 if (_self.firstIndex === 0) { 1583 _self.$backwardContainer.hide(); 1584 } 1585 _self.$forwardContainer.show(); 1586 } 1587 }); 1588 1589 // Handles forward clicks 1590 this.$forwardContainer.on('click', function (evt) { 1591 evt.preventDefault(); 1592 if (_self.lastIndex < _self.numEntries) { 1593 _self.lastIndex = _self.lastIndex + 1; 1594 _self.firstIndex = _self.firstIndex + 1; 1595 _self.getItems = []; 1596 _self.getPlaylistItems(); 1597 1598 //If not all data is cached then request data for the missing content 1599 //Otherwise publish the data 1600 1601 _self.$displayMoreContainer.html(''); 1602 _self.getCount = 0; 1603 _self.checkData(); 1604 if (_self.lastIndex === _self.numEntries) { 1605 _self.$forwardContainer.hide(); 1606 } 1607 _self.$backwardContainer.show(); 1608 } 1609 }); 1610}; 1611 1612/** 1613 * Finds the content which will be viewed on the slider 1614 */ 1615PULSE.CLIENT.UI.PlaylistSlider.prototype.getPlaylistItems = function () { 1616 var _self = this; 1617 _self.getItems = []; 1618 for (var i = _self.firstIndex; i < _self.lastIndex; i++) { 1619 _self.getItems.push(_self.playlistItems[i]); 1620 } 1621}; 1622 1623/** 1624 * Adds content found to the cache and publishes it if all current 1625 * content has been cached 1626 * @param {Object} data Data for one piece of content 1627 */ 1628PULSE.CLIENT.UI.PlaylistSlider.prototype.setData = function (data) { 1629 var _self = this; 1630 _self.itemData[data.id] = data; 1631 _self.publishData(data); 1632}; 1633 1634/** 1635 * Publishes the cached content data to the slider 1636 */ 1637PULSE.CLIENT.UI.PlaylistSlider.prototype.publishData = function (data) { 1638 var _self = this; 1639 if (!data.error) { 1640 PULSE.CLIENT.Template.append(this.template, this.$displayMoreContainer, { 1641 content: data 1642 }); 1643 PULSE.CLIENT.notify('content/update', { 1644 id: _self.span, 1645 success: true 1646 }); 1647 } 1648 this.getCount = this.getCount + 1; 1649 if (this.getCount < this.getItems.length) { 1650 this.checkData(); 1651 } 1652}; 1653(function (common, core) { 1654 /** 1655 * draw the loader from the template, ensuring translation is used 1656 * 1657 * @param {common.ScrollLoader} _self contextual reference to the object that is calling the function 1658 */ 1659 var drawLoader = function drawLoader(_self) { 1660 var loader = '<div class="loader" data-scroll-loader style="min-height:10px;" ><div class="spinner"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div></div>'; 1661 1662 //append to container and keep a reference 1663 _self.element = core.dom.appendDomString(_self.container, loader); 1664 }; 1665 1666 /** 1667 * set the scroller listener for the loader 1668 * 1669 * @param {common.ScrollLoader} _self contextual reference to the object that is calling the function 1670 */ 1671 var setListeners = function setListeners(_self) { 1672 _self.boundary.addEventListener('scroll', function () { 1673 if (!_self.loading && _self.element) { 1674 var top = _self.element.getBoundingClientRect().top; 1675 if (window.innerHeight - top >= _self.limit && _self.element.offsetHeight !== 0) { 1676 _self.loading = true;
1677 setLoadingText(_self); 1678 if (_self.delegate) { 1679 _self.delegate.didRequestLoad(); 1680 } 1681 if (_self.infiniteScroll) { 1682 //Resource has loaded so we can set loading to false so it can load in chunks 1683 _self.loading = false; 1684 } 1685 } 1686 } 1687 }); 1688 }; 1689 1690 /** 1691 * when a loader has been informed by a delegate that loading has finished 1692 * then add it back down to the bottom of the container 1693 * 1694 * @param {common.ScrollLoader} _self contextual reference to the object that is calling the function 1695 */ 1696 var reAppendLoader = function reAppendLoader(_self) { 1697 if (_self.element && _self.container) { 1698 _self.container.appendChild(_self.element); 1699 show(_self); 1700 } 1701 }; 1702 1703 /** 1704 * make the loader visible 1705 * 1706 * @param {common.ScrollLoader} _self contextual reference to the object that is calling the function 1707 */ 1708 var show = function show(_self) { 1709 _self.element.style.display = 'block'; 1710 setLoadingText(_self); 1711 }; 1712 1713 /** 1714 * apply the text content of the loader 1715 * 1716 * @param {common.ScrollLoader} _self contextual reference to the object that is calling the function 1717 */ 1718 var setLoadingText = function setLoadingText(_self) { 1719 //We don't want to show loading text, so force it to be a space character, otherwise infinite scroll doesn't work 1720 var load = ' '; 1721 if (_self.element) { 1722 common.Template.publish(_self.template, _self.element, { 1723 load: load 1724 }); 1725 } 1726 }; 1727 1728 /** 1729 * create a scroll loader, for requesting more content when the page scroll 1730 * reaches this point the scroller will append itself to the bottom of 1731 * the list of content provided as container 1732 * 1733 * @param {HTMLElement} container 1734 * @param {common.ScrollLoaderDelegate} delegate 1735 * @param {boolean} infiniteScroll whether to continue lazy loading after first load 1736 * @param {HTMLElement} [boundary] a custom scroll boundary, defaults to document 1737 * @param {String} [template] custom template to use for loader html 1738 */ 1739 common.ScrollLoader = function (container, delegate, infiniteScroll, boundary, template) { 1740 var _self = this; 1741 _self.delegate = delegate; 1742 _self.container = container; 1743 _self.infiniteScroll = infiniteScroll; 1744 _self.boundary = boundary || false; 1745 _self.loading = false; 1746 _self.element = false; 1747 _self.template = template || 'templates/content/loader.html'; 1748 // this property defines how far above the bottom of the screen the 1749 // loading element must rise in order to trigger a load request 1750 _self.limit = 0; 1751 drawLoader(_self); 1752 setListeners(_self); 1753 }; 1754 1755 /** 1756 * method that can be used when the scroll boundary changes. e.g. 1757 * if the screen resize changes the scroll container. 1758 * 1759 * Handles the reset of the loader listeners also 1760 * 1761 * @param {HTMLelement} newBoundary the new boundary element to use 1762 */ 1763 common.ScrollLoader.prototype.setBoundary = function (newBoundary) { 1764 var _self = this; 1765 _self.boundary = newBoundary; 1766 _self.removeLoader(); 1767 _self.reAppend(); 1768 _self.loading = false; 1769 }; 1770 1771 /** 1772 * public method that can be used to initialte a re-append 1773 */ 1774 common.ScrollLoader.prototype.reAppend = function () { 1775 var _self = this; 1776 drawLoader(_self); 1777 setListeners(_self); 1778 }; 1779 common.ScrollLoader.prototype.contentLoaded = function () { 1780 var _self = this; 1781 _self.loading = false; 1782 }; 1783 common.ScrollLoader.prototype.removeLoader = function () { 1784 var _self = this; 1785 if (_self.element && _self.element.parentNode) { 1786 _self.element.parentNode.removeChild(_self.element); 1787 } 1788 _self.element = false; 1789 }; 1790 common.ScrollLoader.prototype.newLoader = function () { 1791 var _self = this; 1792 if (!_self.element) { 1793 drawLoader(_self); 1794 } 1795 }; 1796 1797 /** 1798 * delegate will call this on the scroll loader when it has finished 1799 * loading data and adding this data to the view. 1800 */ 1801 common.ScrollLoader.prototype.completedDataLoad = function (dontAppend) { 1802 var _self = this; 1803 _self.loading = false; 1804 if (!dontAppend) { 1805 reAppendLoader(_self); 1806 } else { 1807 _self.removeLoader(); 1808 } 1809 }; 1810})(PULSE.CLIENT, PULSE.core); 1811var PULSE = PULSE || {}; 1812PULSE.CLIENT = PULSE.CLIENT || {}; 1813PULSE.CLIENT.UI = PULSE.CLIENT.UI || {}; 1814 1815/** 1816 * Pulse scroller 1817 * @author tmarson 1818 */ 1819PULSE.CLIENT.UI.Scroller = function ($) { 1820 /** 1821 * Generic scroll functionality 1822 * 1823 * @param {HTMLElement} container - element to be scrolled 1824 * @param {Object} [options] 1825 * @param {Number} [options.animSpeed = 250] - Time taken (ms) for scroller to change position 1826 * @param {Number} [options.scrollChange = 0.75] - Percentage change of scroller position 1827 * @param {string} [options.hideClass = 'hide'] - Class added to indicate control container superflousness 1828 * @param {string} [options.toggleClass = 'inactive'] - Class added to indicate controls threshold 1829 * @constructor 1830 */ 1831 var Scroller = function Scroller(container, options) { 1832 var _self = this; 1833 _self.config = $.extend({ 1834 animSpeed: 250, 1835 scrollChange: 0.75, 1836 hideClass: 'hide', 1837 toggleClass: 'inactive', 1838 hideControls: false 1839 }, options); 1840 _self.$container = $(container); 1841 _self.container = _self.$container[0];
1842 _self.$scrollControls = $('<div class="controls"></div>').appendTo(_self.$container.parent()); 1843 _self.$scrollRight = $('<span class="right"></span>').appendTo(_self.$scrollControls).data('posChange', _self.config.scrollChange); 1844 _self.$scrollLeft = $('<span class="left"></span>').addClass(_self.config.toggleClass).appendTo(_self.$scrollControls).data('posChange', -_self.config.scrollChange); 1845 var TIMEOUT_DELAY = 150; 1846 /** 1847 * add the scroll controls to the element or make sure 1848 * that it is scrollable in the view 1849 */ 1850 if (_self.config.hideControls) { 1851 _self.$scrollControls.css('display', 'none'); 1852 } 1853 _self.scrollPosition = 0; 1854 _self.checkThresholds(); 1855 if (parseInt(_self.$scrollControls.css('opacity')) === 0) { 1856 _self.$scrollControls.css({ 1857 opacity: 1 1858 }); 1859 } 1860 1861 // Scroll element 1862 _self.$scrollControls.on('click', 'span', function (evt) { 1863 evt.preventDefault(); 1864 _self.posChange = $(this).data('posChange') * _self.container.clientWidth; 1865 if (_self.posChange) { 1866 _self.scrollTo(_self.scrollPosition + _self.posChange); 1867 } 1868 }); 1869 1870 // Refresh scroll controls on resize (with grace period) 1871 $(window).on('resize', function () { 1872 clearTimeout(_self.windowTimeout); 1873 _self.windowTimeout = setTimeout(function () { 1874 _self.scrollPosition = _self.container.scrollLeft; 1875 _self.checkThresholds(); 1876 }, TIMEOUT_DELAY); 1877 }); 1878 1879 // Refresh scroll controls on scroll (with grace period) 1880 _self.$container.on('scroll', function () { 1881 if (!_self.scrollDisabled) { 1882 clearTimeout(_self.scrollTimeout); 1883 _self.scrollTimeout = setTimeout(function () { 1884 _self.scrollPosition = _self.container.scrollLeft; 1885 _self.checkThresholds(); 1886 }, TIMEOUT_DELAY); 1887 } 1888 }); 1889 if (_self.config.hideControls) { 1890 _self.$container.css('overflow-x', 'scroll'); 1891 _self.$container.css('overflow-y', 'hidden'); 1892 } 1893 }; 1894 Scroller.prototype = { 1895 /** 1896 * Checks the current scroll position - 1897 * if no scroll required, hide the controls, otherwise toggle state of left / right buttons. 1898 * 1899 * @returns {boolean} - Returns true if container can be scrolled 1900 */ 1901 checkThresholds: function checkThresholds() { 1902 var _self = this; 1903 1904 // If scroll container is not visible, don't check scroll position 1905 if (!_self.$container.is(':visible')) { 1906 _self.$scrollControls.addClass(_self.config.hideClass); 1907 return false; 1908 } 1909 1910 // Hide scroll if not needed 1911 if (_self.container.scrollWidth <= _self.container.clientWidth) { 1912 _self.$scrollControls.addClass(_self.config.hideClass); 1913 return false; 1914 } 1915 _self.$scrollControls.removeClass(_self.config.hideClass); 1916 _self.currentScroll = _self.container.scrollLeft; 1917 _self.maxScroll = _self.container.scrollWidth - _self.container.clientWidth; 1918 1919 // Check for scroll beyond left threshold 1920 if (_self.scrollPosition <= 0) { 1921 _self.scrollPosition = 0; 1922 _self.$scrollLeft.addClass(_self.config.toggleClass); 1923 } else { 1924 _self.$scrollLeft.removeClass(_self.config.toggleClass); 1925 } 1926 1927 // Check for scroll beyond right threshold 1928 if (_self.scrollPosition >= _self.maxScroll) { 1929 _self.scrollPosition = _self.maxScroll; 1930 _self.$scrollRight.addClass(_self.config.toggleClass); 1931 } else { 1932 _self.$scrollRight.removeClass(_self.config.toggleClass); 1933 } 1934 return true; 1935 }, 1936 /** 1937 * Scroll the container to a specific position and update controls 1938 * 1939 * @param {integer} position - the target position to scroll to 1940 * @param {boolean} [instantChange = false] - if true, do not animate 1941 */ 1942 scrollTo: function scrollTo(position, instantChange) { 1943 var _self = this; 1944 _self.scrollPosition = position; 1945 if (_self.scrollDisabled || !_self.checkThresholds()) { 1946 return; 1947 } 1948 1949 // Instant change 1950 if (instantChange) { 1951 _self.container.scrollLeft = _self.scrollPosition; 1952 return; 1953 } 1954 1955 // Animated change 1956 _self.scrollDisabled = true; 1957 _self.$container.animate({ 1958 scrollLeft: _self.scrollPosition + 'px' 1959 }, _self.config.animSpeed, function () { 1960 _self.scrollDisabled = false;
1961 _self.checkThresholds(); 1962 }); 1963 } 1964 }; 1965 $(function () { 1966 $.each($('[data-widget-type="scroller_widget"]'), function () { 1967 this.scroller = new Scroller(this); 1968 }); 1969 }); 1970 return Scroller; 1971}(jQuery); 1972var PULSE = PULSE || {}; 1973PULSE.CLIENT = PULSE.CLIENT || {}; 1974PULSE.CLIENT.UI = PULSE.CLIENT.UI || {}; 1975 1976/** 1977 * Pulse scroller - split - COMPLETE DUPLICATE OF SCROLLER - ALLOWS US TO CHANGE IT ON ONE SITE ONLY. 1978 * TODO: REPLACE ALL OTHERS WITH THIS ONE AND CLEAN UP!!! ð° 1979 */ 1980PULSE.CLIENT.UI.ScrollerSplit = function ($) { 1981 /** 1982 * Generic scroll functionality 1983 * 1984 * @param {HTMLElement} container - element to be scrolled 1985 * @param {Object} [options] 1986 * @param {Number} [options.animSpeed = 250] - Time taken (ms) for scroller to change position 1987 * @param {Number} [options.scrollChange = 0.75] - Percentage change of scroller position 1988 * @param {string} [options.hideClass = 'hide'] - Class added to indicate control container superflousness 1989 * @param {string} [options.toggleClass = 'inactive'] - Class added to indicate controls threshold 1990 * @constructor 1991 */ 1992 var Scroller = function Scroller(container, options) { 1993 var _self = this; 1994 _self.config = $.extend({ 1995 animSpeed: 250, 1996 scrollChange: 0.75, 1997 hideClass: 'hide', 1998 toggleClass: 'inactive', 1999 hideControls: false 2000 }, options); 2001 _self.$container = $(container); 2002 _self.container = _self.$container[0]; 2003 _self.$scrollControlsLeft = $('<div class="controls controls--left"></div>').appendTo(_self.$container.parent()); 2004 _self.$scrollControlsRight = $('<div class="controls controls--right"></div>').appendTo(_self.$container.parent()); 2005 _self.$scrollRight = $('<span class="right"></span>').appendTo(_self.$scrollControlsRight).data('posChange', _self.config.scrollChange); 2006 _self.$scrollLeft = $('<span class="left"></span>').addClass(_self.config.toggleClass).appendTo(_self.$scrollControlsLeft).data('posChange', -_self.config.scrollChange); 2007 var TIMEOUT_DELAY = 150; 2008 /** 2009 * add the scroll controls to the element or make sure 2010 * that it is scrollable in the view 2011 */ 2012 if (_self.config.hideControls) { 2013 _self.$scrollControlsLeft.css('display', 'none'); 2014 _self.$scrollControlsRight.css('display', 'none'); 2015 } 2016 _self.scrollPosition = 0; 2017 _self.checkThresholds(); 2018 if (parseInt(_self.$scrollControlsLeft.css('opacity')) === 0) { 2019 _self.$scrollControlsLeft.css({ 2020 opacity: 1 2021 }); 2022 _self.$scrollControlsRight.css({ 2023 opacity: 1 2024 }); 2025 } 2026 2027 // Scroll element 2028 _self.$scrollControlsLeft.on('click', 'span', function (evt) { 2029 evt.preventDefault(); 2030 _self.posChange = $(this).data('posChange') * _self.container.clientWidth; 2031 if (_self.posChange) { 2032 _self.scrollTo(_self.scrollPosition + _self.posChange); 2033 } 2034 }); 2035 _self.$scrollControlsRight.on('click', 'span', function (evt) { 2036 evt.preventDefault(); 2037 _self.posChange = $(this).data('posChange') * _self.container.clientWidth; 2038 if (_self.posChange) { 2039 _self.scrollTo(_self.scrollPosition + _self.posChange); 2040 } 2041 }); 2042 2043 // Refresh scroll controls on resize (with grace period) 2044 $(window).on('resize', function () { 2045 clearTimeout(_self.windowTimeout); 2046 _self.windowTimeout = setTimeout(function () { 2047 _self.scrollPosition = _self.container.scrollLeft; 2048 _self.checkThresholds(); 2049 }, TIMEOUT_DELAY); 2050 }); 2051 2052 // Refresh scroll controls on scroll (with grace period) 2053 _self.$container.on('scroll', function () { 2054 if (!_self.scrollDisabled) { 2055 clearTimeout(_self.scrollTimeout); 2056 _self.scrollTimeout = setTimeout(function () { 2057 _self.scrollPosition = _self.container.scrollLeft; 2058 _self.checkThresholds(); 2059 }, TIMEOUT_DELAY); 2060 } 2061 }); 2062 if (_self.config.hideControls) { 2063 _self.$container.css('overflow-x', 'scroll'); 2064 _self.$container.css('overflow-y', 'hidden'); 2065 } 2066 }; 2067 Scroller.prototype = { 2068 /** 2069 * Checks the current scroll position - 2070 * if no scroll required, hide the controls, otherwise toggle state of left / right buttons. 2071 * 2072 * @returns {boolean} - Returns true if container can be scrolled 2073 */ 2074 checkThresholds: function checkThresholds() { 2075 var _self = this; 2076 2077 // If scroll container is not visible, don't check scroll position 2078 if (!_self.$container.is(':visible')) {
2079 _self.$scrollControlsLeft.addClass(_self.config.hideClass); 2080 _self.$scrollControlsRight.addClass(_self.config.hideClass); 2081 return false; 2082 } 2083 2084 // Hide scroll if not needed 2085 if (_self.container.scrollWidth <= _self.container.clientWidth) { 2086 _self.$scrollControlsLeft.addClass(_self.config.hideClass); 2087 _self.$scrollControlsRight.addClass(_self.config.hideClass); 2088 return false; 2089 } 2090 _self.$scrollControlsLeft.removeClass(_self.config.hideClass); 2091 _self.$scrollControlsRight.removeClass(_self.config.hideClass); 2092 _self.currentScroll = _self.container.scrollLeft; 2093 _self.maxScroll = _self.container.scrollWidth - _self.container.clientWidth; 2094 2095 // Check for scroll beyond left threshold 2096 if (_self.scrollPosition <= 0) { 2097 _self.scrollPosition = 0; 2098 _self.$scrollLeft.addClass(_self.config.toggleClass); 2099 } else { 2100 _self.$scrollLeft.removeClass(_self.config.toggleClass); 2101 } 2102 2103 // Check for scroll beyond right threshold 2104 if (_self.scrollPosition >= _self.maxScroll) { 2105 _self.scrollPosition = _self.maxScroll; 2106 _self.$scrollRight.addClass(_self.config.toggleClass); 2107 } else { 2108 _self.$scrollRight.removeClass(_self.config.toggleClass); 2109 } 2110 return true; 2111 }, 2112 /** 2113 * Scroll the container to a specific position and update controls 2114 * 2115 * @param {integer} position - the target position to scroll to 2116 * @param {boolean} [instantChange = false] - if true, do not animate 2117 */ 2118 scrollTo: function scrollTo(position, instantChange) { 2119 var _self = this; 2120 _self.scrollPosition = position; 2121 if (_self.scrollDisabled || !_self.checkThresholds()) { 2122 return; 2123 } 2124 2125 // Instant change 2126 if (instantChange) { 2127 _self.container.scrollLeft = _self.scrollPosition; 2128 return; 2129 } 2130 2131 // Animated change 2132 _self.scrollDisabled = true; 2133 _self.$container.animate({ 2134 scrollLeft: _self.scrollPosition + 'px' 2135 }, _self.config.animSpeed, function () { 2136 _self.scrollDisabled = false; 2137 _self.checkThresholds(); 2138 }); 2139 } 2140 }; 2141 $(function () { 2142 $.each($('[data-widget-type="scroller_widget_split"]'), function () { 2143 this.scroller = new Scroller(this); 2144 }); 2145 }); 2146 return Scroller; 2147}(jQuery); 2148if (!PULSE) { 2149 var PULSE = {}; 2150} 2151if (!PULSE.CLIENT) { 2152 PULSE.CLIENT = {}; 2153} 2154if (!PULSE.CLIENT.UI) { 2155 PULSE.CLIENT.UI = {}; 2156} 2157 2158/** 2159 * Logic for the show more content buttons 2160 * Options: 2161 * container : The container for the widget 2162 * showMoreContainer : The container where the user clicks to show more content 2163 * template : The template for the retrived content 2164 * displayMoreContainer : The containtainer to display the new content 2165 */ 2166PULSE.CLIENT.UI.ShowMore = function (options) { 2167 if (!options) { 2168 return; 2169 } 2170 var MIN_PAGE_THRESHOLD = 2; 2171 this.$container = $(options.container); 2172 this.$showMoreContainer = this.$container.find(options.showMoreContainer); 2173 this.$displayMoreContainer = this.$container.find(options.displayMoreContainer); 2174 this.template = options.template; 2175 this.numEntries = parseInt(this.$container.data('num-entries')); 2176 this.pageSize = parseInt(this.$container.data('page-size')); 2177 this.maxEntries = parseInt(this.$container.data('max')); 2178 this.contentType = this.$container.data('content-type'); 2179 this.total = this.pageSize; 2180 if (this.numEntries > this.maxEntries) { 2181 this.numEntries = this.maxEntries; 2182 } 2183 this.maxPages = Math.ceil(this.numEntries / this.pageSize); 2184 this.contentTags = this.$container.data('tags'); 2185 this.references = this.$container.data('references'); 2186 if (this.contentType === 'news') { 2187 this.contentType = 'text'; 2188 if (!this.contentTags || this.contentTags.length < 1) { 2189 this.contentTags = 'News Article'; 2190 } else { 2191 this.contentTags = this.contentTags + ',News Article'; 2192 } 2193 } 2194 this.urlGen = PULSE.CLIENT.RUGBY.getUrlGenerator({ 2195 type: this.contentType 2196 }); 2197 this.dataManager = PULSE.CLIENT.getJqueryDataManager(); 2198 this.pageNum = 0; 2199 if (this.maxPages < MIN_PAGE_THRESHOLD) { 2200 this.$showMoreContainer.hide(); 2201 } 2202 this.setListeners(); 2203}; 2204PULSE.CLIENT.UI.ShowMore.prototype.setListeners = function () { 2205 var _self = this; 2206 this.$showMoreContainer.on('click', function (evt) { 2207 evt.preventDefault(); 2208 _self.pageNum++; 2209 2210 // Notify widget to get more content 2211 PULSE.CLIENT.notify('content/get', { 2212 id: _self.span, 2213 success: true 2214 }); 2215 if (_self.pageNum === _self.maxPages - 1) { 2216 _self.$showMoreContainer.hide(); 2217 } 2218 }); 2219}; 2220PULSE.CLIENT.UI.ShowMore.prototype.setData = function (data) { 2221 var _self = this; 2222 _self.numEntries = data.pageInfo.numEntries; 2223 if (_self.numEntries > _self.maxEntries) { 2224 _self.numEntries = _self.maxEntries; 2225 } 2226 _self.maxPages = Math.ceil(_self.numEntries / _self.pageSize); 2227 for (var i = 0; i < data.content.length; i++) { 2228 _self.total++; 2229 if (_self.total < _self.maxEntries + 1) {
2230 PULSE.CLIENT.Template.append(_self.template, _self.$displayMoreContainer, { 2231 content: data.content[i] 2232 }); 2233 } 2234 } 2235 PULSE.CLIENT.notify('content/update', { 2236 id: _self.span, 2237 success: true 2238 }); 2239}; 2240/*globals PULSE, PULSE.app*/ 2241 2242(function (common, core) { 2243 /* PRIVATE METHODS */ 2244 2245 /** 2246 * @namespace common.slider.private 2247 */ 2248 2249 var setDefaults = function setDefaults(config) { 2250 if (!config.wrap) { 2251 return console.warn('You must provide a wrap element in your slider config'); 2252 } 2253 if (!config.controls || !config.controls.next || !config.controls.previous) { 2254 return console.warn('You must provide a both next and previous control elements in your slider config'); 2255 } 2256 if (!config.slideList) { 2257 config.slideList = config.wrap.querySelector('.js-slide-list'); 2258 } 2259 if (!config.slides) { 2260 config.slides = Array.prototype.slice.call(config.wrap.querySelectorAll('.js-slide')); 2261 } 2262 if (!config.slideRate) { 2263 config.slideRate = 50; // pixels to move per FPS 2264 } 2265 if (!config.thumbSlideRate) { 2266 config.thumbSlideRate = 10; // pixels to move per FPS 2267 } 2268 if (!config.interval) { 2269 config.interval = 5000; 2270 } 2271 if (!config.auto) { 2272 config.auto = false; 2273 } 2274 if (!config.startIndex) { 2275 config.startIndex = 0; 2276 } 2277 if (!config.hideClass) { 2278 config.hideClass = 'is-hidden'; 2279 } 2280 if (config.thumbsWrap && !config.thumbsType) { 2281 config.thumbsType = 'image'; 2282 } 2283 if (!config.thumbClass) { 2284 config.thumbClass = 'thumbnail'; 2285 } 2286 if (!config.imageClass) { 2287 config.imageClass = 'photoGallery__image'; 2288 } 2289 return config; 2290 };
2291 var bindControls = function bindControls() { 2292 var _self = this; 2293 _self.config.controls.previous.addEventListener('click', function (mouseEvent) { 2294 mouseEvent.preventDefault(); 2295 _self.config.slidePrev = true; 2296 _self.slidePrev(); 2297 }); 2298 _self.config.controls.next.addEventListener('click', function (mouseEvent) { 2299 mouseEvent.preventDefault(); 2300 _self.config.slideNext = true; 2301 _self.slideNext(); 2302 }); 2303 if (_self.config.controls.expand) { 2304 // used to handle multiple expand elements that also set the current image on expand 2305 if (_self.config.controls.expand.length > 1) { 2306 for (var i = 0; i < _self.config.controls.expand.length; i++) { 2307 _self.config.controls.expand[i].addEventListener('click', function (mouseEvent) { 2308 mouseEvent.preventDefault(); 2309 _self.config.currentSlide = mouseEvent.target.parentNode.parentNode; 2310 }); 2311 } 2312 } else { 2313 _self.config.controls.expand.addEventListener('click', function (mouseEvent) { 2314 mouseEvent.preventDefault(); 2315 }); 2316 } 2317 } 2318 if (_self.config.controls.autoStart && _self.config.controls.autoStop) { 2319 _self.config.controls.autoStart.addEventListener('click', function (mouseEvent) { 2320 mouseEvent.preventDefault(); 2321 _self.startAuto(); 2322 }); 2323 _self.config.controls.autoStop.addEventListener('click', function (mouseEvent) { 2324 mouseEvent.preventDefault(); 2325 _self.stopAuto(); 2326 }); 2327 } 2328 }; 2329 var buildViewport = function buildViewport() { 2330 var _self = this; 2331 var newViewport = false; 2332 if (!_self.config.viewport) { 2333 newViewport = true; 2334 _self.config.viewport = document.createElement('div'); 2335 core.style.addClass(_self.config.viewport, 'slider-viewport'); 2336 } 2337 _self.config.viewport.innerHTML = ''; 2338 _self.config.viewportList = document.createElement('ul'); 2339 core.style.addClass(_self.config.viewportList, 'slider-viewport__list'); 2340 _self.config.viewport.appendChild(_self.config.viewportList); 2341 if (newViewport) { 2342 _self.config.wrap.appendChild(_self.config.viewport); 2343 } 2344 bindViewport.call(_self); 2345 }; 2346 var bindViewport = function bindViewport() { 2347 var _self = this; 2348 var timeout; 2349 var _checkScroll = function checkScroll() { 2350 var TIMEOUT_INTERVAL = 50; 2351 clearTimeout(timeout); 2352 timeout = setTimeout(function () {
2353 var SCROLL_DIST = 30; 2354 var scrollDiff = Math.abs(_self.config.viewport.scrollLeft - _self.config.viewport.offsetWidth); 2355 if (scrollDiff > SCROLL_DIST) { 2356 if (_self.config.viewport.scrollLeft > _self.config.viewport.offsetWidth) { 2357 _self.slideNext(); 2358 } else { 2359 _self.slidePrev(); 2360 } 2361 } else { 2362 _self.updateViewport(_self.config.currentSlide); 2363 } 2364 _self.config.viewport.removeEventListener('scroll', _checkScroll); 2365 }, TIMEOUT_INTERVAL); 2366 }; 2367 _self.config.viewportList.addEventListener('touchstart', function () { 2368 _self.config.viewport.addEventListener('scroll', _checkScroll); 2369 }); 2370 }; 2371 var bindResize = function bindResize() { 2372 var _self = this; 2373 var timeout; 2374 var TIMEOUT_INTERVAL = 200; 2375 var checkResize = function checkResize() { 2376 clearTimeout(timeout); 2377 timeout = setTimeout(function () { 2378 _self.updateViewport(_self.config.currentSlide); 2379 }, TIMEOUT_INTERVAL); 2380 }; 2381 window.addEventListener('resize', checkResize); 2382 }; 2383 var buildThumbnails = function buildThumbnails() { 2384 var _self = this; 2385 _self.config.thumbnails = _self.config.slides.map(mapThumbnails.bind(_self)); 2386 }; 2387 var mapThumbnails = function mapThumbnails(slide, index) { 2388 var _self = this; 2389 slide.indexVal = index; 2390 var thumb = document.createElement('li'); 2391 core.style.addClass(thumb, _self.config.thumbClass); 2392 if (_self.config.thumbsType === 'image') { 2393 thumb.appendChild(slide.querySelector('picture').cloneNode(true)); 2394 } else if (_self.config.thumbsType === 'index') { 2395 thumb.innerHTML = index + 1; 2396 } 2397 thumb.addEventListener('click', function () { 2398 _self.slideToIndex(index); 2399 }); 2400 _self.config.thumbsWrap.appendChild(thumb); 2401 return thumb; 2402 }; 2403 var bindThumbControls = function bindThumbControls() { 2404 var _self = this; 2405 _self.config.thumbControls.left.addEventListener('click', function (mouseEvent) { 2406 mouseEvent.preventDefault(); 2407 _self.slideThumbsLeft(); 2408 }); 2409 _self.config.thumbControls.right.addEventListener('click', function (mouseEvent) { 2410 mouseEvent.preventDefault(); 2411 _self.slideThumbsRight(); 2412 }); 2413 }; 2414 var setFullScreenPhoto = function setFullScreenPhoto(slide, data, className) { 2415 var picElem = slide.querySelector('.js-item-image'); 2416 var picContent = ''; 2417 picContent += '<img src=' + data + ' class="' + className + '"/>'; 2418 picElem.innerHTML = picContent; 2419 }; 2420 common.Slider = function (config) { 2421 'use strict'; 2422 2423 var _self = this; 2424 _self.config = setDefaults(config); 2425 _self.config.slides = Array.prototype.slice.call(_self.config.slides); 2426 _self.config.currentSlide = _self.config.slides[_self.config.startIndex]; 2427 _self.config.firstSlide = _self.config.slides[0]; 2428 _self.config.lastSlide = _self.config.slides[_self.config.slides.length - 1]; 2429 _self.isViewportFullsize = false; 2430 bindControls.call(_self); 2431 buildViewport.call(_self); 2432 bindResize.call(_self); 2433 if (_self.config.thumbsWrap) { 2434 buildThumbnails.call(_self); 2435 if (_self.config.thumbControls) { 2436 bindThumbControls.call(_self); 2437 } 2438 } 2439 _self.updateViewport(_self.config.currentSlide); 2440 if (_self.config.auto) { 2441 _self.startAuto(); 2442 } 2443 }; 2444 common.Slider.prototype.slidePrev = function () { 2445 var _self = this; 2446 var limit = 0; 2447 function moveSlide() { 2448 var newPos = _self.config.viewport.scrollLeft - _self.config.slideRate; 2449 if (newPos <= limit) { 2450 _self.config.viewport.scrollLeft = limit; 2451 cancelAnimationFrame(sliding); 2452 _self.updateViewport(_self.config.prevSlide); 2453 } else if (_self.config.slidePrev === true) { 2454 _self.config.viewport.scrollLeft = newPos; 2455 requestAnimationFrame(moveSlide); 2456 } 2457 } 2458 var sliding = requestAnimationFrame(moveSlide); 2459 }; 2460 common.Slider.prototype.slideNext = function () { 2461 var _self = this; 2462 var DOUBLE_VIEWPORT = 2; 2463 var VIEWPORT_BUFFER = 10; 2464 var limit = _self.config.viewport.offsetWidth * DOUBLE_VIEWPORT - VIEWPORT_BUFFER; 2465 function moveSlide() { 2466 var newPos = _self.config.viewport.scrollLeft + _self.config.slideRate; 2467 if (newPos >= limit) { 2468 _self.config.viewport.scrollLeft = limit; 2469 cancelAnimationFrame(sliding); 2470 _self.updateViewport(_self.config.nextSlide); 2471 } else if (_self.config.slideNext === true) { 2472 _self.config.viewport.scrollLeft = newPos; 2473 requestAnimationFrame(moveSlide); 2474 } 2475 } 2476 var sliding = requestAnimationFrame(moveSlide); 2477 }; 2478 common.Slider.prototype.slideToIndex = function (targetIndex) { 2479 var _self = this; 2480 if (_self.config.currentSlide.indexVal !== targetIndex) { 2481 var newTarget = _self.config.currentSlide; 2482 if (_self.config.currentSlide.indexVal < targetIndex) { 2483 newTarget.forceNext = _self.config.slides[targetIndex]; 2484 _self.updateViewport(newTarget, 'next'); 2485 } else { 2486 newTarget = _self.config.currentSlide; 2487 newTarget.forcePrev = _self.config.slides[targetIndex]; 2488 _self.updateViewport(newTarget, 'prev'); 2489 } 2490 } 2491 }; 2492 common.Slider.prototype.startAuto = function () { 2493 var _self = this; 2494 if (!_self.autoPlaying) { 2495 _self.autoPlaying = setInterval(function () { 2496 _self.slideNext(); 2497 }, _self.config.interval); 2498 core.style.toggleClass(_self.config.controls.autoStart, _self.config.hideClass); 2499 core.style.toggleClass(_self.config.controls.autoStop, _self.config.hideClass); 2500 } 2501 }; 2502 common.Slider.prototype.stopAuto = function () { 2503 var _self = this; 2504 if (_self.autoPlaying) { 2505 clearInterval(_self.autoPlaying); 2506 _self.autoPlaying = false;
2507 core.style.toggleClass(_self.config.controls.autoStart, _self.config.hideClass); 2508 core.style.toggleClass(_self.config.controls.autoStop, _self.config.hideClass); 2509 } 2510 }; 2511 common.Slider.prototype.arrowKeyNav = function (keyboardEvent) { 2512 var _self = this; 2513 _self.config.slidePrev = false; 2514 _self.config.slideNext = false; 2515 var RIGHT_KEY = 39, 2516 LEFT_KEY = 37, 2517 ESCAPE_KEY = 27; 2518 if (keyboardEvent.keyCode === RIGHT_KEY) { 2519 _self.config.slideNext = true; 2520 _self.slideNext(); 2521 _self.stopAuto(); 2522 } else if (keyboardEvent.keyCode === LEFT_KEY) { 2523 _self.config.slidePrev = true; 2524 _self.slidePrev(); 2525 _self.stopAuto(); 2526 } 2527 }; 2528 common.Slider.prototype.updateViewport = function (target, slide) { 2529 var _self = this; 2530 _self.config.currentSlide = target; 2531 if (target.forcePrev) { 2532 _self.config.prevSlide = target.forcePrev; 2533 target.forcePrev = false; 2534 _self.config.slidePrev = true; 2535 } else { 2536 _self.config.prevSlide = core.dom.getPreviousSiblings(_self.config.currentSlide)[0]; 2537 if (!_self.config.prevSlide) { 2538 _self.config.prevSlide = _self.config.lastSlide; 2539 } 2540 } 2541 if (target.forceNext) { 2542 _self.config.nextSlide = target.forceNext; 2543 target.forceNext = false; 2544 _self.config.slideNext = true; 2545 } else { 2546 _self.config.nextSlide = core.dom.getNextSiblings(_self.config.currentSlide)[0]; 2547 if (!_self.config.nextSlide) { 2548 _self.config.nextSlide = _self.config.firstSlide; 2549 } 2550 } 2551 _self.config.viewportList.innerHTML = ''; 2552 var prevCloned = _self.config.prevSlide.cloneNode(true); 2553 var currentCloned = _self.config.currentSlide.cloneNode(true); 2554 var nextCloned = _self.config.nextSlide.cloneNode(true); 2555 var clonedElements = [prevCloned, currentCloned, nextCloned]; 2556 for (var i = 0; i < clonedElements.length; i++) { 2557 PULSE.core.style.addClass(clonedElements[i], 'visible'); 2558 } 2559 _self.config.viewportList.appendChild(prevCloned); 2560 _self.config.viewportList.appendChild(currentCloned); 2561 _self.config.viewportList.appendChild(nextCloned); 2562 _self.config.viewport.scrollLeft = _self.config.viewport.offsetWidth; 2563 if (slide === 'next') { 2564 _self.slideNext(true); 2565 } else if (slide === 'prev') { 2566 _self.slidePrev(true); 2567 } 2568 if (_self.config.thumbnails) { 2569 _self.setActiveThumb(_self.config.currentSlide.indexVal); 2570 } 2571 _self.loadHighResImages(); 2572 }; 2573 common.Slider.prototype.loadHighResImages = function () { 2574 var _self = this; 2575 var array = [_self.config.prevSlide, _self.config.currentSlide, _self.config.nextSlide]; 2576 array.forEach(function (element, key) { 2577 var fullscreenData = element.firstElementChild.getAttribute('data-fullscreen'); 2578 if (fullscreenData && element.dataset.imageLoaded !== 'true') { 2579 element.dataset.imageLoaded = true; 2580 setFullScreenPhoto(_self.config.viewportList.querySelector('li:nth-child(' + Number(key + 1) + ')'), fullscreenData, _self.config.imageClass); 2581 setFullScreenPhoto(element, fullscreenData, _self.config.imageClass); 2582 } 2583 }); 2584 }; 2585 common.Slider.prototype.setActiveThumb = function (activeIndex) { 2586 var _self = this; 2587 Array.prototype.forEach.call(_self.config.thumbnails, function (thumb) { 2588 core.style.removeClass(thumb, 'is-active'); 2589 }); 2590 core.style.addClass(_self.config.thumbnails[activeIndex], 'is-active'); 2591 }; 2592 common.Slider.prototype.slideThumbsRight = function () { 2593 var _self = this; 2594 var getThumbWidth = function getThumbWidth() { 2595 var width = _self.config.thumbnails[0].offsetWidth; 2596 var marginLeft = parseInt(window.getComputedStyle(_self.config.thumbnails[0]).getPropertyValue('margin-left')); 2597 var marginRight = parseInt(window.getComputedStyle(_self.config.thumbnails[0]).getPropertyValue('margin-right')); 2598 return width + marginRight + marginLeft; 2599 }; 2600 var startPos = _self.config.thumbsWrap.scrollLeft; 2601 var distance = getThumbWidth(); 2602 var limit = _self.config.thumbnails.length * distance - _self.config.thumbsWrap.offsetWidth; 2603 function moveThumbs() { 2604 var newPos = _self.config.thumbsWrap.scrollLeft += _self.config.thumbSlideRate; 2605 if (newPos > limit) { 2606 _self.config.thumbsWrap.scrollLeft = limit; 2607 cancelAnimationFrame(slidingThumbs); 2608 } else if (newPos >= startPos + distance) { 2609 _self.config.thumbsWrap.scrollLeft = newPos; 2610 cancelAnimationFrame(slidingThumbs); 2611 } else { 2612 _self.config.thumbsWrap.scrollLeft = newPos;
2613 requestAnimationFrame(moveThumbs); 2614 } 2615 } 2616 if (_self.config.thumbsWrap.scrollLeft < limit) { 2617 var slidingThumbs = requestAnimationFrame(moveThumbs); 2618 } 2619 }; 2620 common.Slider.prototype.slideThumbsLeft = function () { 2621 var _self = this; 2622 var startPos = _self.config.thumbsWrap.scrollLeft; 2623 var distance = _self.config.thumbnails[0].offsetWidth; 2624 var limit = 0; 2625 function moveThumbs() { 2626 var newPos = _self.config.thumbsWrap.scrollLeft -= _self.config.thumbSlideRate; 2627 if (newPos < limit) { 2628 _self.config.thumbsWrap.scrollLeft = limit; 2629 cancelAnimationFrame(slidingThumbs); 2630 } else if (newPos <= startPos - distance) { 2631 _self.config.thumbsWrap.scrollLeft = newPos; 2632 cancelAnimationFrame(slidingThumbs); 2633 } else { 2634 _self.config.thumbsWrap.scrollLeft = newPos; 2635 requestAnimationFrame(moveThumbs); 2636 } 2637 } 2638 if (_self.config.thumbsWrap.scrollLeft > limit) { 2639 var slidingThumbs = requestAnimationFrame(moveThumbs); 2640 } 2641 }; 2642})(PULSE.CLIENT, PULSE.core); 2643$(document).ready(function () { 2644 var ELEMENTS = { 2645 toggle: '.js-share-toggle' 2646 }; 2647 var CLASS = { 2648 open: 'is-open' 2649 }; 2650 2651 /** 2652 * controller to handle sharing of page to social media 2653 */ 2654 PULSE.CLIENT.UI.Social = function (core) { 2655 var _self = this; 2656 2657 /** 2658 * classes assigned to social share icons in the page 2659 */ 2660 _self.socialServiceClasses = { 2661 TWITTER: '.tw_link', 2662 FACEBOOK: '.fb_link', 2663 GPLUS: '.gp_link', 2664 FBMESSENGER: '.fb-messenger_link', 2665 WHATSAPP: '.wa_link', 2666 CLIPBOARD: '.clipboard_link' 2667 }; 2668 _self.squareSocialServiceClasses = { 2669 CLIPBOARD: '.social-square--clipboard', 2670 CLIPBOARD_FEEDBACK: '.social-share__clipboard-feedback', 2671 FACEBOOK: '.social-square--facebook', 2672 FBMESSENGER: '.social-square--facebook-messenger', 2673 TWITTER: '.social-square--twitter', 2674 WHATSAPP: '.social-square--whatsapp' 2675 }; 2676 2677 /** 2678 * retrieve the page title to be inserted into the share text 2679 * based on the page's title meta tag' 2680 * 2681 * This can be overriden by applying a data-social-title alongside 2682 * data-social on the social icon element. 2683 * 2684 * @param {jQuery} $socialElement the element posessing data-social / data-social-title attributes 2685 * 2686 */ 2687 _self.getPageTitle = function ($socialElement) { 2688 if ($socialElement.length > 0 && $socialElement.attr('data-social-title')) { 2689 return $socialElement.attr('data-social-title'); 2690 } 2691 2692 /** 2693 * use plain ttile meta tag is available 2694 */ 2695 var $pageTitleMetaTag = $('meta[property="title"]'); 2696 if ($pageTitleMetaTag.length > 0) { 2697 return $pageTitleMetaTag.attr('content'); 2698 } 2699 2700 /** 2701 * otherwise try to use open graph title 2702 */ 2703 $pageTitleMetaTag = $('meta[property="og:title"]'); 2704 if ($pageTitleMetaTag.length > 0) { 2705 return $pageTitleMetaTag.attr('content'); 2706 } 2707 return ''; 2708 }; 2709 2710 /** 2711 * combine the url, title, hashtag into a single string, adding an 2712 * ellipsis to the title if needed. 2713 * 2714 * @param {Boolean} containsUrl wether there is a url within the tweet text 2715 * @param {Number} [limit=140] an integer > 0 to limit the whole social text to 2716 * 2717 * @returns {string} text with url, title, hashtag and via tag 2718 */ 2719 _self.getTwitterShareText = function (containsUrl, limit, $socialElement) { 2720 var viaText = ' via @'; 2721 var ellipsisString = '...'; 2722 var pageTitle = _self.getPageTitle($socialElement); 2723 var titleText = ''; 2724 // how many characters of the title would we like to be able to show 2725 // as a minimum 2726 var minTitleLength = 10; 2727 //e.g via @worldrugby 2728 var socialVia = PULSE.CLIENT.CURRENT_SITE.getSocialVia(); 2729 2730 /** 2731 * check the length of the tweet body without any title text, 2732 * determine how much of the title we are able to show. 2733 * 2734 * twitter will reduce any url in the tweet text to 23 characters using 2735 * a t.co redirect ( https://support.twitter.com/articles/78124 ) 2736 */ 2737 2738 // 23 url characters and 1 trailing space 2739 var urlLength = 24; 2740 // each hashtag will have a leading space and a '#' added 2741 var leadingCharacterLength = 2; 2742 var hashtagLength = PULSE.CLIENT.CURRENT_SITE.getHashTags().reduce(function (prev, curr) { 2743 return prev + curr.length + leadingCharacterLength; 2744 }, 0); 2745 // via handle is prepended with ' via @' 2746 var viaLength = socialVia.length > 0 ? socialVia.length + viaText.length : 0;
2747 // length without title 2748 var preTitleLength = urlLength + hashtagLength + viaLength; 2749 var titleAllowedLength = limit - preTitleLength; 2750 if (titleAllowedLength > minTitleLength && pageTitle.length > 0) { 2751 /** 2752 * insert as much of the title as we can and append an 2753 * ellipsis if needed 2754 */ 2755 if (titleAllowedLength < pageTitle.length) { 2756 titleText = pageTitle.slice(0, titleAllowedLength - ellipsisString.length) + ellipsisString; 2757 } else { 2758 titleText = pageTitle; 2759 } 2760 } 2761 return titleText; 2762 }; 2763 2764 /* 2765 * Generic fire .... share function 2766 * @param {object} controller 2767 */ 2768 var fireShare = function fireShare(evt, controller) { 2769 var url = _self.getUrl($(evt.currentTarget)); 2770 controller.publishEvent(url); 2771 evt.preventDefault(); 2772 evt.stopPropagation(); 2773 }; 2774 var fireFacebookShare = function fireFacebookShare(evt) { 2775 fireShare(evt, PULSE.CLIENT.FacebookController); 2776 }; 2777 var fireGooglePlusShare = function fireGooglePlusShare(evt) { 2778 fireShare(evt, PULSE.CLIENT.GooglePlusController); 2779 }; 2780 var fireWhatsAppShare = function fireWhatsAppShare(evt) { 2781 fireShare(evt, PULSE.CLIENT.WhatsAppController); 2782 }; 2783 var fireFacebookMessengerShare = function fireFacebookMessengerShare(evt) { 2784 fireShare(evt, PULSE.CLIENT.FacebookMessengerController); 2785 }; 2786 var fireTwitterShare = function fireTwitterShare(evt) { 2787 /** 2788 * we are able to pre populate the tweet box so we include the title 2789 */ 2790 var $socialElement = $(this); 2791 var socialVia = PULSE.CLIENT.CURRENT_SITE.getSocialVia(); 2792 var hashtags = PULSE.CLIENT.CURRENT_SITE.getHashTags(); 2793 var url = _self.getUrl($socialElement); 2794 var hasUrl = url.length > 0; 2795 var textLimit = 140; 2796 var text = _self.getTwitterShareText(hasUrl, textLimit, $socialElement); 2797 var tweetParams = { 2798 text: text, 2799 url: url 2800 }; 2801 if (socialVia.length > 0) { 2802 tweetParams.via = socialVia; 2803 } 2804 if (hashtags.length > 0) { 2805 tweetParams.hashtags = hashtags.join(','); 2806 } 2807 PULSE.CLIENT.TwitterController.tweetEvent('tweet', tweetParams); 2808 evt.preventDefault(); 2809 evt.stopPropagation(); 2810 }; 2811 var fireCopyToClipboard = function fireCopyToClipboard(evt) { 2812 _self.copyUrlToClipboard(this); 2813 evt.preventDefault(); 2814 evt.stopPropagation(); 2815 }; 2816 var fireHideShareButton = function fireHideShareButton() { 2817 var shareRevealElement = document.querySelector('.social-share-reveal '); 2818 if (shareRevealElement) { 2819 shareRevealElement.classList.add(CLASS.open); 2820 } 2821 }; 2822 2823 /** 2824 * get the share url based on the clicked element, the element may have a 2825 * data-social attribute that can ovverride the share url, otherwise the 2826 * page url is shared. 2827 * 2828 * @param {jQuery} $element the social icon element that was clicked 2829 */ 2830 _self.getUrl = function ($element) { 2831 var socialUrl = $element.attr('data-social') || document.URL; 2832 if (socialUrl.indexOf('http') !== 0 && socialUrl.indexOf('https') !== 0) { 2833 socialUrl = location.protocol + socialUrl; 2834 } 2835 var url = new URL(socialUrl); 2836 url.searchParams.set('lang', PULSE.CLIENT.LANGUAGE); 2837 return url.href; 2838 }; 2839 _self.copyUrlToClipboard = function (element) { 2840 var socialUrl = element.getAttribute('data-social') || document.URL; 2841 var url = new URL(socialUrl); 2842 url.searchParams.set('lang', PULSE.CLIENT.LANGUAGE); 2843 2844 // eslint-disable-next-line no-undef 2845 clipboard.copy(url.href).then(function () { 2846 _self.copyUrlMessage(element); 2847 }, function () { 2848 _self.copyUrlMessage(element, true); 2849 }); 2850 }; 2851 2852 /** 2853 * Display a message that tells the user if the url has been copied to the clipboard 2854 * or not 2855 * 2856 * @param {boolean} fail - true if copying to the clipboard has failed 2857 */ 2858 _self.copyUrlMessage = function (element, fail) { 2859 var delayTime = 2000; 2860 _self.copyButton = element; 2861 _self.copyMessage = element.querySelector('.js-copy-message'); 2862 if (fail) { 2863 core.style.addClass(_self.copyButton, 'has-error'); 2864 } else { 2865 core.style.addClass(_self.copyButton, 'is-active'); 2866 } 2867 setTimeout(function () { 2868 core.style.removeClass(_self.copyButton, 'is-active'); 2869 core.style.removeClass(_self.copyButton, 'has-error'); 2870 }, delayTime); 2871 }; 2872 _self.hideClipboard = function () { 2873 var ANIMATION_TIMEOUT = 3300; 2874 var clipboardIcon = $(_self.squareSocialServiceClasses.CLIPBOARD); 2875 if (clipboardIcon.length === 0 || typeof Clipboard === 'undefined') { 2876 return; 2877 } 2878 if (!Clipboard.isSupported()) { 2879 clipboardIcon[0].style.display = 'none'; 2880 } 2881 var clipboardFeedbackDiv = $(_self.squareSocialServiceClasses.CLIPBOARD_FEEDBACK); 2882 var clipboard = new Clipboard(_self.squareSocialServiceClasses.CLIPBOARD); 2883 if (clipboardFeedbackDiv.length === 0) { 2884 return; 2885 } 2886 var baseClass = ' social-share__clipboard-feedback'; 2887 clipboard.on('success', function () { 2888 var successFeedback = $(_self.squareSocialServiceClasses.CLIPBOARD_FEEDBACK + '--success'); 2889 successFeedback[0].className += baseClass + '--display'; 2890 setTimeout(function () { 2891 successFeedback[0].className = baseClass + baseClass + '--success'; 2892 }, ANIMATION_TIMEOUT); 2893 }); 2894 clipboard.on('error', function () { 2895 var errorFeedback = $(_self.squareSocialServiceClasses.CLIPBOARD_FEEDBACK + '--error'); 2896 errorFeedback[0].className += baseClass + '--display'; 2897 setTimeout(function () { 2898 errorFeedback[0].className = baseClass + baseClass + '--failure'; 2899 }, ANIMATION_TIMEOUT); 2900 }); 2901 }; 2902 2903 /**
2904 * Set jQuery live listeners on social icon classes ( we may render social share icons 2905 * through underscore templates ) 2906 */ 2907 _self.setListeners = function () { 2908 $('body').on('click', _self.socialServiceClasses.FACEBOOK, fireFacebookShare); 2909 $('body').on('click', _self.squareSocialServiceClasses.FACEBOOK, fireFacebookShare); 2910 $('body').on('click', _self.socialServiceClasses.TWITTER, fireTwitterShare); 2911 $('body').on('click', _self.squareSocialServiceClasses.TWITTER, fireTwitterShare); 2912 $('body').on('click', _self.socialServiceClasses.GPLUS, fireGooglePlusShare); 2913 $('body').on('click', _self.socialServiceClasses.FBMESSENGER, fireFacebookMessengerShare); 2914 $('body').on('click', _self.socialServiceClasses.WHATSAPP, fireWhatsAppShare); 2915 $('body').on('click', _self.socialServiceClasses.CLIPBOARD, fireCopyToClipboard); 2916 $('body').on('click', ELEMENTS.toggle, fireHideShareButton); 2917 //NB: Custom protocols (whatsapp, facebook messenger) are triggered via anchor tags in the freemarker 2918 }; 2919 _self.hideClipboard(); 2920 _self.setListeners(); 2921 }; 2922 2923 /** 2924 * create instance of social controller when page is ready 2925 */ 2926 PULSE.CLIENT.UI.Social(PULSE.core); // eslint-disable-line 2927}); 2928/** 2929 * Sticky menu jquery function 2930 * @param {Object} config - Configuration options to affect the sticky menu 2931 */ 2932$.fn.stickyHeader = function (config) { 2933 var INTERVAL_DELAY = 100; 2934 var options = { 2935 cssClass: 'stuckMenu', 2936 fixedClass: 'isStuck', 2937 container: false 2938 }, 2939 $this = $(this), 2940 vartop, 2941 varscroll; 2942 var scrollRes; 2943 var currentMenuState = false; 2944 $.extend(options, config); 2945 var $stickyEl = (options.container ? $(options.container, $this) : $this).addClass(options.cssClass); 2946 if (!$this.length) { 2947 return; 2948 } 2949 var updatescrollbar = function updatescrollbar(addRemove) { 2950 if (currentMenuState !== addRemove) { 2951 //only if the state has actually changed 2952 // Stick if required 2953 if (addRemove) { 2954 $stickyEl.addClass(options.fixedClass); 2955 } else { 2956 // Remove the sticky 2957 $stickyEl.removeClass(options.fixedClass); 2958 } 2959 currentMenuState = addRemove; 2960 } 2961 }; 2962 setInterval(function () { 2963 updatescrollbar(scrollRes); 2964 }, INTERVAL_DELAY); 2965 $(document).on('scroll', function () { 2966 // Calculate position 2967 varscroll = $(window).scrollTop(); 2968 vartop = $this.offset().top; 2969 // Stick if required 2970 2971 scrollRes = vartop <= varscroll; 2972 }); 2973}; 2974(function (common) { 2975 /** 2976 * Calculates swipe direction 2977 * @param {HTMLDOMELement} element DOM Element to calculate swipe direction within 2978 * @param {Object} config SwipeDetect config 2979 * config.threshold - required minimum distance traveled to be considered swipe, defaults to 150 2980 * config.restraint - maximum distance allowed at the same time in perpendicular direction, , defaults to 100 2981 * config.allowedTime - maximum time allowed to travel that distance, , defaults to 300 2982 * @param {Function} callback function to run after detection 2983 * @constructor 2984 * @example 2985 * new common.SwipeDetect( element, { threshold: 150, restraint: 100, allowedTime: 300 }, function( swipedir ) { 2986 * switch ( swipedir ) { 2987 * case 'left': 2988 * // your custom handler 2989 * break; 2990 * case 'right': 2991 * // your custom handler 2992 * break; 2993 * case 'up': 2994 * // your custom handler 2995 * break; 2996 * case 'down': 2997 * // your custom handler 2998 * break; 2999 * default: 3000 * // your custom handler 3001 * } 3002 * }; 3003 */ 3004 common.SwipeDetect = function SwipeDetect(element, config, callback) { 3005 var startX, startY, distX, distY, startTime, elapsedTime, swipedir; 3006 var touchSurface = element; 3007 var defaults = { 3008 allowedTime: 300, 3009 preventDefault: true, 3010 restraint: 100, 3011 threshold: 150 3012 }; 3013 var allowedTime = config.allowedTime || defaults.allowedTime; 3014 var preventDefault = config.hasOwnProperty('preventDefault') ? config.preventDefault : defaults.preventDefault; 3015 var restraint = config.restraint || defaults.restraint; 3016 var threshold = config.threshold || defaults.threshold; 3017 3018 // eslint-disable-next-line no-console 3019 var defaulSwipeHandler = function defaulSwipeHandler(swipeDirection) { 3020 return console.log("SwipeDetect: ".concat(swipeDirection)); 3021 }; 3022 var handleSwipe = typeof callback === 'function' ? callback : defaulSwipeHandler; 3023 touchSurface.addEventListener('touchstart', function (event) { 3024 if (preventDefault) { 3025 event.preventDefault(); 3026 } 3027 var touchObj = event.changedTouches[0]; 3028 swipedir = 'none'; 3029 startX = touchObj.pageX; 3030 startY = touchObj.pageY; 3031 3032 // record time when finger first makes contact with surface 3033 startTime = new Date().getTime(); 3034 }, false); 3035 if (preventDefault) { 3036 touchSurface.addEventListener('touchmove', function (event) { 3037 event.preventDefault(); 3038 }, false); 3039 } 3040 touchSurface.addEventListener('touchend', function (event) { 3041 if (preventDefault) { 3042 event.preventDefault(); 3043 } 3044 var touchObj = event.changedTouches[0]; 3045 3046 // get horizontal dist traveled by finger while in contact with surface 3047 distX = touchObj.pageX - startX; 3048 3049 // get vertical dist traveled by finger while in contact with surface 3050 distY = touchObj.pageY - startY; 3051 3052 // get time elapsed 3053 elapsedTime = new Date().getTime() - startTime; 3054 3055 // first condition for swipe met 3056 if (elapsedTime <= allowedTime) { 3057 // 2nd condition for horizontal swipe met 3058 if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint) { 3059 // if dist traveled is negative, it indicates left swipe 3060 swipedir = distX < 0 ? 'left' : 'right'; 3061 3062 // 2nd condition for vertical swipe met 3063 } else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint) { 3064 // if dist traveled is negative, it indicates up swipe 3065 swipedir = distY < 0 ? 'up' : 'down'; 3066 } 3067 } 3068 handleSwipe(swipedir); 3069 }, false); 3070 }; 3071})(PULSE.app.common); 3072if (!PULSE) { 3073 var PULSE = {}; 3074} 3075if (!PULSE.CLIENT) { 3076 PULSE.CLIENT = {}; 3077} 3078if (!PULSE.CLIENT.UI) { 3079 PULSE.CLIENT.UI = {}; 3080} 3081PULSE.CLIENT.UI.ToggleNav = function (options) { 3082 var _self = this; 3083 if (!options) { 3084 return; 3085 } 3086 _self.activeTab = typeof options.activeTab !== 'undefined' ? options.activeTab : 0; 3087 _self.navOnClass = options.navActiveClass || 'active'; 3088 _self.contentClass = options.contentClassPrefix || 'tab'; 3089 _self.$nav = $(options.navigationContainer); // mandatory 3090 _self.$content = $(options.contentContainer); // mandatory 3091 _self.$navLinks = options.navLinksSelector ? _self.$nav.find(options.navLinksSelector) : _self.$nav.children(); 3092 _self.$next = $(options.nextButton); 3093 _self.$prev = $(options.prevButton); 3094 _self.animate = options.animate; 3095 _self.callback = options.callback; 3096 _self.$navLinks.eq(_self.activeTab).addClass(_self.navOnClass); 3097 _self.$content.children().hide(); 3098 _self.$content.find('.' + _self.contentClass + _self.activeTab).show(); 3099 _self.$navLinks.each(function (index) { 3100 $(this).click(function (evt) { 3101 if (_self.activeTab !== index) { 3102 _self.activeTab = index; 3103 _self.switchTabs(); 3104 } 3105 evt.preventDefault(); 3106 }); 3107 }); 3108 if (options.nextButton) { 3109 this.$next.click(function (evt) { 3110 if (_self.$navLinks.length - 1 > _self.activeTab) { 3111 _self.activeTab++; 3112 _self.switchTabs(); 3113 } 3114 evt.preventDefault(); 3115 }); 3116 } 3117 if (options.prevButton) { 3118 this.$prev.click(function (evt) { 3119 if (0 < _self.activeTab) { 3120 _self.activeTab--; 3121 _self.switchTabs(); 3122 } 3123 evt.preventDefault(); 3124 }); 3125 } 3126}; 3127PULSE.CLIENT.UI.ToggleNav.prototype.switchTabs = function (dontTrack) { 3128 var _self = this; 3129 3130 // Return early if the clicked link is 'inactive' 3131 if (_self.$navLinks.eq(_self.activeTab).hasClass('inactive')) { 3132 return; 3133 } 3134 var hideContentChildren = function hideContentChildren(callback) { 3135 _self.$content.children(':visible').first().fadeOut('fast', function () { 3136 if (callback) { 3137 callback(_self.activeTab, dontTrack); 3138 } 3139 if (_self.callback) { 3140 _self.callback(_self.activeTab, dontTrack); 3141 } 3142 }); 3143 }; 3144 if (_self.animate) { 3145 hideContentChildren(function () { 3146 _self.$content.find('.' + _self.contentClass + _self.activeTab).fadeIn('slow'); 3147 }); 3148 } else { 3149 _self.$content.children().hide(); 3150 _self.$content.find('.' + _self.contentClass + _self.activeTab).show(); 3151 if (_self.callback) { 3152 _self.callback(_self.activeTab, dontTrack); 3153 } 3154 } 3155 _self.$navLinks.removeClass(_self.navOnClass); 3156 _self.$navLinks.eq(_self.activeTab).addClass(_self.navOnClass); 3157}; 3158PULSE.CLIENT.UI.ToggleNav.prototype.switchToTab = function (index, dontTrack) { 3159 this.activeTab = index; 3160 this.switchTabs(dontTrack); 3161}; 3162 3163// --------- end toggle nav 3164if (!PULSE) { 3165 var PULSE = {}; 3166} 3167if (!PULSE.CLIENT) { 3168 PULSE.CLIENT = {}; 3169} 3170if (!PULSE.CLIENT.UI) { 3171 PULSE.CLIENT.UI = {}; 3172} 3173 3174/** 3175 * John Resig recommended function 3176 * @param {Object} elm - DOM element or jQuery object for it 3177 * @return {Boolean} - whether it's within the user's viewport 3178 */ 3179PULSE.CLIENT.UI.isElementInViewport = function (elm) { 3180 //special bonus for those using jQuery 3181 3182 var element = elm; 3183 if (typeof jQuery === 'function' && elm instanceof jQuery) { 3184 element = elm[0]; 3185 } 3186 var rect = element.getBoundingClientRect(); 3187 return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ 3188 rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */; 3189}; 3190/* eslint-disable */ 3191(function ($) { 3192 $.extend({ 3193 tablesorter: new function () { 3194 var parsers = [], 3195 widgets = []; 3196 this.defaults = { 3197 cssHeader: 'header', 3198 cssAsc: 'headerSortUp', 3199 cssDesc: 'headerSortDown', 3200 cssChildRow: 'expand-child', 3201 sortInitialOrder: 'asc', 3202 sortMultiSortKey: 'shiftKey', 3203 sortForce: null, 3204 sortAppend: null, 3205 sortLocaleCompare: true, 3206 textExtraction: 'simple', 3207 parsers: {}, 3208 widgets: [], 3209 widgetZebra: { 3210 css: ['even', 'odd'] 3211 }, 3212 headers: {}, 3213 widthFixed: false, 3214 cancelSelection: true, 3215 sortList: [], 3216 headerList: [], 3217 dateFormat: 'us',
3218 decimal: '/\.|\,/g', 3219 onRenderHeader: null, 3220 selectorHeaders: 'thead th', 3221 debug: false 3222 }; 3223 function benchmark(s, d) { 3224 log(s + ',' + (new Date().getTime() - d.getTime()) + 'ms'); 3225 } 3226 this.benchmark = benchmark; 3227 function log(s) { 3228 if (typeof console !== 'undefined' && typeof console.debug !== 'undefined') { 3229 console.log(s); 3230 } else { 3231 alert(s); 3232 } 3233 } 3234 function buildParserCache(table, $headers) { 3235 if (table.config.debug) { 3236 var parsersDebug = ''; 3237 } 3238 if (table.tBodies.length == 0) { 3239 return; 3240 } 3241 var rows = table.tBodies[0].rows; 3242 if (rows[0]) { 3243 var list = [], 3244 cells = rows[0].cells, 3245 l = cells.length; 3246 for (var i = 0; i < l; i++) { 3247 var p = false; 3248 if ($.metadata && $($headers[i]).metadata() && $($headers[i]).metadata().sorter) { 3249 p = getParserById($($headers[i]).metadata().sorter); 3250 } else if (table.config.headers[i] && table.config.headers[i].sorter) { 3251 p = getParserById(table.config.headers[i].sorter); 3252 } 3253 if (!p) { 3254 p = detectParserForColumn(table, rows, -1, i); 3255 } 3256 if (table.config.debug) { 3257 parsersDebug += 'column:' + i + ' parser:' + p.id + '\n'; 3258 } 3259 list.push(p); 3260 } 3261 } 3262 if (table.config.debug) { 3263 log(parsersDebug); 3264 } 3265 return list; 3266 } 3267 function detectParserForColumn(table, rows, rowIndex, cellIndex) { 3268 var l = parsers.length, 3269 node = false, 3270 nodeValue = false, 3271 keepLooking = true; 3272 while (nodeValue == '' && keepLooking) { 3273 rowIndex++; 3274 if (rows[rowIndex]) { 3275 node = getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex); 3276 nodeValue = trimAndGetNodeText(table.config, node); 3277 if (table.config.debug) { 3278 log('Checking if value was empty on row:' + rowIndex); 3279 } 3280 } else { 3281 keepLooking = false; 3282 } 3283 } 3284 for (var i = 1; i < l; i++) { 3285 if (parsers[i].is(nodeValue, table, node)) { 3286 return parsers[i]; 3287 } 3288 } 3289 return parsers[0]; 3290 } 3291 function getNodeFromRowAndCellIndex(rows, rowIndex, cellIndex) { 3292 return rows[rowIndex].cells[cellIndex]; 3293 } 3294 function trimAndGetNodeText(config, node) { 3295 return $.trim(getElementText(config, node)); 3296 } 3297 function getParserById(name) { 3298 var l = parsers.length; 3299 for (var i = 0; i < l; i++) { 3300 if (parsers[i].id.toLowerCase() == name.toLowerCase()) { 3301 return parsers[i]; 3302 } 3303 } 3304 return false; 3305 } 3306 function buildCache(table) { 3307 if (table.config.debug) { 3308 var cacheTime = new Date(); 3309 } 3310 var totalRows = table.tBodies[0] && table.tBodies[0].rows.length || 0, 3311 totalCells = table.tBodies[0].rows[0] && table.tBodies[0].rows[0].cells.length || 0, 3312 parsers = table.config.parsers, 3313 cache = { 3314 row: [], 3315 normalized: [] 3316 }; 3317 for (var i = 0; i < totalRows; ++i) { 3318 var c = $(table.tBodies[0].rows[i]), 3319 cols = []; 3320 if (c.hasClass(table.config.cssChildRow)) { 3321 cache.row[cache.row.length - 1] = cache.row[cache.row.length - 1].add(c); 3322 continue; 3323 } 3324 cache.row.push(c); 3325 for (var j = 0; j < totalCells; ++j) { 3326 cols.push(parsers[j].format(getElementText(table.config, c[0].cells[j]), table, c[0].cells[j])); 3327 } 3328 cols.push(cache.normalized.length); 3329 cache.normalized.push(cols); 3330 cols = null; 3331 } 3332 if (table.config.debug) { 3333 benchmark('Building cache for ' + totalRows + ' rows:', cacheTime); 3334 } 3335 return cache; 3336 } 3337 function getElementText(config, node) { 3338 var text = ''; 3339 if (!node) { 3340 return ''; 3341 }
3342 if (!config.supportsTextContent) { 3343 config.supportsTextContent = node.textContent || false; 3344 } 3345 if (config.textExtraction == 'simple') { 3346 if (config.supportsTextContent) { 3347 text = node.textContent; 3348 } else if (node.childNodes[0] && node.childNodes[0].hasChildNodes()) { 3349 text = node.childNodes[0].innerHTML; 3350 } else { 3351 text = node.innerHTML; 3352 } 3353 } else if (typeof config.textExtraction === 'function') { 3354 text = config.textExtraction(node); 3355 } else { 3356 text = $(node).text(); 3357 } 3358 return text; 3359 } 3360 function appendToTable(table, cache) { 3361 if (table.config.debug) { 3362 var appendTime = new Date(); 3363 } 3364 var c = cache, 3365 r = c.row, 3366 n = c.normalized, 3367 totalRows = n.length, 3368 checkCell = n[0].length - 1, 3369 tableBody = $(table.tBodies[0]), 3370 rows = []; 3371 for (var i = 0; i < totalRows; i++) { 3372 var pos = n[i][checkCell]; 3373 rows.push(r[pos]); 3374 if (!table.config.appender) { 3375 var l = r[pos].length; 3376 for (var j = 0; j < l; j++) { 3377 tableBody[0].appendChild(r[pos][j]); 3378 } 3379 } 3380 } 3381 if (table.config.appender) { 3382 table.config.appender(table, rows); 3383 } 3384 rows = null; 3385 if (table.config.debug) { 3386 benchmark('Rebuilt table:', appendTime); 3387 } 3388 applyWidget(table); 3389 setTimeout(function () { 3390 $(table).trigger('sortEnd'); 3391 }, 0); 3392 } 3393 function buildHeaders(table) { 3394 if (table.config.debug) { 3395 var time = new Date(); 3396 } 3397 var meta = $.metadata ? true : false; 3398 var header_index = computeTableHeaderCellIndexes(table); 3399 $tableHeaders = $(table.config.selectorHeaders, table).each(function (index) { 3400 this.column = header_index[this.parentNode.rowIndex + '-' + this.cellIndex]; 3401 this.order = formatSortingOrder(table.config.sortInitialOrder); 3402 this.count = this.order; 3403 if (checkHeaderMetadata(this) || checkHeaderOptions(table, index)) { 3404 this.sortDisabled = true; 3405 } 3406 if (checkHeaderOptionsSortingLocked(table, index)) { 3407 this.order = this.lockedOrder = checkHeaderOptionsSortingLocked(table, index); 3408 } 3409 if (!this.sortDisabled) { 3410 var $th = $(this).addClass(table.config.cssHeader); 3411 if (table.config.onRenderHeader) { 3412 table.config.onRenderHeader.apply($th); 3413 } 3414 } 3415 table.config.headerList[index] = this; 3416 }); 3417 if (table.config.debug) { 3418 benchmark('Built headers:', time); 3419 log($tableHeaders); 3420 } 3421 return $tableHeaders; 3422 } 3423 function computeTableHeaderCellIndexes(t) { 3424 var matrix = []; 3425 var lookup = {}; 3426 var thead = t.getElementsByTagName('THEAD')[0]; 3427 var trs = thead.getElementsByTagName('TR'); 3428 for (var i = 0; i < trs.length; i++) { 3429 var cells = trs[i].cells; 3430 for (var j = 0; j < cells.length; j++) { 3431 var c = cells[j]; 3432 var rowIndex = c.parentNode.rowIndex; 3433 var cellId = rowIndex + '-' + c.cellIndex; 3434 var rowSpan = c.rowSpan || 1; 3435 var colSpan = c.colSpan || 1; 3436 var firstAvailCol; 3437 if (typeof matrix[rowIndex] === 'undefined') { 3438 matrix[rowIndex] = []; 3439 } 3440 for (var k = 0; k < matrix[rowIndex].length + 1; k++) { 3441 if (typeof matrix[rowIndex][k] === 'undefined') { 3442 firstAvailCol = k; 3443 break; 3444 } 3445 } 3446 lookup[cellId] = firstAvailCol; 3447 for (var k = rowIndex; k < rowIndex + rowSpan; k++) { 3448 if (typeof matrix[k] === 'undefined') { 3449 matrix[k] = []; 3450 } 3451 var matrixrow = matrix[k]; 3452 for (var l = firstAvailCol; l < firstAvailCol + colSpan; l++) { 3453 matrixrow[l] = 'x'; 3454 } 3455 } 3456 } 3457 } 3458 return lookup; 3459 } 3460 function checkCellColSpan(table, rows, row) { 3461 var arr = [], 3462 r = table.tHead.rows, 3463 c = r[row].cells; 3464 for (var i = 0; i < c.length; i++) { 3465 var cell = c[i]; 3466 if (cell.colSpan > 1) { 3467 arr = arr.concat(checkCellColSpan(table, headerArr, row++)); 3468 } else if (table.tHead.length == 1 || cell.rowSpan > 1 || !r[row + 1]) { 3469 arr.push(cell); 3470 } 3471 } 3472 return arr; 3473 } 3474 function checkHeaderMetadata(cell) { 3475 if ($.metadata && $(cell).metadata().sorter === false) { 3476 return true; 3477 } 3478 return false;
3479 } 3480 function checkHeaderOptions(table, i) { 3481 if (table.config.headers[i] && table.config.headers[i].sorter === false) { 3482 return true; 3483 } 3484 return false; 3485 } 3486 function checkHeaderOptionsSortingLocked(table, i) { 3487 if (table.config.headers[i] && table.config.headers[i].lockedOrder) { 3488 return table.config.headers[i].lockedOrder; 3489 } 3490 return false; 3491 } 3492 function applyWidget(table) { 3493 var c = table.config.widgets; 3494 var l = c.length; 3495 for (var i = 0; i < l; i++) { 3496 getWidgetById(c[i]).format(table); 3497 } 3498 } 3499 function getWidgetById(name) { 3500 var l = widgets.length; 3501 for (var i = 0; i < l; i++) { 3502 if (widgets[i].id.toLowerCase() == name.toLowerCase()) { 3503 return widgets[i]; 3504 } 3505 } 3506 } 3507 function formatSortingOrder(v) { 3508 if (typeof v !== 'Number') { 3509 return v.toLowerCase() == 'desc' ? 1 : 0; 3510 } else { 3511 return v == 1 ? 1 : 0; 3512 } 3513 } 3514 function isValueInArray(v, a) { 3515 var l = a.length; 3516 for (var i = 0; i < l; i++) { 3517 if (a[i][0] == v) { 3518 return true; 3519 } 3520 } 3521 return false; 3522 } 3523 function setHeadersCss(table, $headers, list, css) { 3524 $headers.removeClass(css[0]).removeClass(css[1]); 3525 var h = []; 3526 $headers.each(function (offset) { 3527 if (!this.sortDisabled) { 3528 h[this.column] = $(this); 3529 } 3530 }); 3531 var l = list.length; 3532 for (var i = 0; i < l; i++) { 3533 h[list[i][0]].addClass(css[list[i][1]]); 3534 } 3535 } 3536 function fixColumnWidth(table, $headers) { 3537 var c = table.config; 3538 if (c.widthFixed) { 3539 var colgroup = $('<colgroup>'); 3540 $('tr:first td', table.tBodies[0]).each(function () { 3541 colgroup.append($('<col>').css('width', $(this).width())); 3542 }); 3543 $(table).prepend(colgroup); 3544 } 3545 } 3546 function updateHeaderSortCount(table, sortList) { 3547 var c = table.config, 3548 l = sortList.length; 3549 for (var i = 0; i < l; i++) { 3550 var s = sortList[i], 3551 o = c.headerList[s[0]]; 3552 o.count = s[1]; 3553 o.count++; 3554 } 3555 } 3556 function multisort(table, sortList, cache) { 3557 if (table.config.debug) { 3558 var sortTime = new Date(); 3559 } 3560 var dynamicExp = 'var sortWrapper = function(a,b) {', 3561 l = sortList.length; 3562 for (var i = 0; i < l; i++) { 3563 var c = sortList[i][0]; 3564 var order = sortList[i][1]; 3565 var s = table.config.parsers[c].type == 'text' ? order == 0 ? makeSortFunction('text', 'asc', c) : makeSortFunction('text', 'desc', c) : order == 0 ? makeSortFunction('numeric', 'asc', c) : makeSortFunction('numeric', 'desc', c); 3566 var e = 'e' + i; 3567 dynamicExp += 'var ' + e + ' = ' + s; 3568 dynamicExp += 'if(' + e + ') { return ' + e + '; } '; 3569 dynamicExp += 'else { '; 3570 } 3571 var orgOrderCol = cache.normalized[0].length - 1; 3572 dynamicExp += 'return a[' + orgOrderCol + ']-b[' + orgOrderCol + '];'; 3573 for (var i = 0; i < l; i++) { 3574 dynamicExp += '}; '; 3575 } 3576 dynamicExp += 'return 0; '; 3577 dynamicExp += '}; '; 3578 if (table.config.debug) { 3579 benchmark('Evaling expression:' + dynamicExp, new Date()); 3580 } 3581 eval(dynamicExp); 3582 cache.normalized.sort(sortWrapper); 3583 if (table.config.debug) { 3584 benchmark('Sorting on ' + sortList.toString() + ' and dir ' + order + ' time:', sortTime); 3585 } 3586 return cache; 3587 } 3588 function makeSortFunction(type, direction, index) { 3589 var a = 'a[' + index + ']', 3590 b = 'b[' + index + ']'; 3591 if (type == 'text' && direction == 'asc') { 3592 return '(' + a + ' == ' + b + ' ? 0 : (' + a + ' === null ? Number.POSITIVE_INFINITY : (' + b + ' === null ? Number.NEGATIVE_INFINITY : (' + a + ' < ' + b + ') ? -1 : 1 )));'; 3593 } else if (type == 'text' && direction == 'desc') { 3594 return '(' + a + ' == ' + b + ' ? 0 : (' + a + ' === null ? Number.POSITIVE_INFINITY : (' + b + ' === null ? Number.NEGATIVE_INFINITY : (' + b + ' < ' + a + ') ? -1 : 1 )));'; 3595 } else if (type == 'numeric' && direction == 'asc') { 3596 return '(' + a + ' === null && ' + b + ' === null) ? 0 :(' + a + ' === null ? Number.POSITIVE_INFINITY : (' + b + ' === null ? Number.NEGATIVE_INFINITY : ' + a + ' - ' + b + '));'; 3597 } else if (type == 'numeric' && direction == 'desc') { 3598 return '(' + a + ' === null && ' + b + ' === null) ? 0 :(' + a + ' === null ? Number.POSITIVE_INFINITY : (' + b + ' === null ? Number.NEGATIVE_INFINITY : ' + b + ' - ' + a + '));'; 3599 } 3600 } 3601 function makeSortText(i) { 3602 return '((a[' + i + '] < b[' + i + ']) ? -1 : ((a[' + i + '] > b[' + i + ']) ? 1 : 0));'; 3603 } 3604 function makeSortTextDesc(i) { 3605 return '((b[' + i + '] < a[' + i + ']) ? -1 : ((b[' + i + '] > a[' + i + ']) ? 1 : 0));'; 3606 } 3607 function makeSortNumeric(i) { 3608 return 'a[' + i + ']-b[' + i + '];'; 3609 } 3610 function makeSortNumericDesc(i) { 3611 return 'b[' + i + ']-a[' + i + '];'; 3612 } 3613 function sortText(a, b) { 3614 if (table.config.sortLocaleCompare) { 3615 return a.localeCompare(b); 3616 } 3617 return a < b ? -1 : a > b ? 1 : 0; 3618 } 3619 function sortTextDesc(a, b) { 3620 if (table.config.sortLocaleCompare) { 3621 return b.localeCompare(a); 3622 } 3623 return b < a ? -1 : b > a ? 1 : 0; 3624 } 3625 function sortNumeric(a, b) { 3626 return a - b; 3627 } 3628 function sortNumericDesc(a, b) { 3629 return b - a; 3630 } 3631 function getCachedSortType(parsers, i) { 3632 return parsers[i].type; 3633 } 3634 this.construct = function (settings) { 3635 return this.each(function () { 3636 if (!this.tHead || !this.tBodies) { 3637 return; 3638 } 3639 var $this, 3640 $document, 3641 $headers, 3642 cache, 3643 config, 3644 shiftDown = 0, 3645 sortOrder; 3646 this.config = {}; 3647 config = $.extend(this.config, $.tablesorter.defaults, settings); 3648 $this = $(this); 3649 $.data(this, 'tablesorter', config); 3650 $headers = buildHeaders(this); 3651 this.config.parsers = buildParserCache(this, $headers); 3652 cache = buildCache(this); 3653 var sortCSS = [config.cssDesc, config.cssAsc]; 3654 fixColumnWidth(this); 3655 $headers.click(function (e) { 3656 var totalRows = $this[0].tBodies[0] && $this[0].tBodies[0].rows.length || 0; 3657 if (!this.sortDisabled && totalRows > 0) { 3658 $this.trigger('sortStart'); 3659 var $cell = $(this); 3660 var i = this.column; 3661 this.order = this.count++ % 2; 3662 if (this.lockedOrder) { 3663 this.order = this.lockedOrder; 3664 } 3665 if (!e[config.sortMultiSortKey]) { 3666 config.sortList = []; 3667 if (config.sortForce != null) { 3668 var a = config.sortForce; 3669 for (var j = 0; j < a.length; j++) { 3670 if (a[j][0] != i) { 3671 config.sortList.push(a[j]); 3672 } 3673 } 3674 } 3675 config.sortList.push([i, this.order]); 3676 } else if (isValueInArray(i, config.sortList)) { 3677 for (var j = 0; j < config.sortList.length; j++) { 3678 var s = config.sortList[j], 3679 o = config.headerList[s[0]]; 3680 if (s[0] == i) { 3681 o.count = s[1]; 3682 o.count++; 3683 s[1] = o.count % 2; 3684 } 3685 } 3686 } else { 3687 config.sortList.push([i, this.order]); 3688 } 3689 setTimeout(function () { 3690 setHeadersCss($this[0], $headers, config.sortList, sortCSS); 3691 appendToTable($this[0], multisort($this[0], config.sortList, cache)); 3692 }, 1); 3693 return false;
3694 } 3695 }).mousedown(function () { 3696 if (config.cancelSelection) { 3697 this.onselectstart = function () { 3698 return false; 3699 }; 3700 return false; 3701 } 3702 }); 3703 $this.bind('update', function () { 3704 var me = this; 3705 setTimeout(function () { 3706 me.config.parsers = buildParserCache(me, $headers); 3707 cache = buildCache(me); 3708 }, 1); 3709 }).bind('updateCell', function (e, cell) { 3710 var config = this.config; 3711 var pos = [cell.parentNode.rowIndex - 1, cell.cellIndex]; 3712 cache.normalized[pos[0]][pos[1]] = config.parsers[pos[1]].format(getElementText(config, cell), cell); 3713 }).bind('sorton', function (e, list) { 3714 $(this).trigger('sortStart'); 3715 config.sortList = list; 3716 var sortList = config.sortList; 3717 updateHeaderSortCount(this, sortList); 3718 setHeadersCss(this, $headers, sortList, sortCSS); 3719 appendToTable(this, multisort(this, sortList, cache)); 3720 }).bind('appendCache', function () { 3721 appendToTable(this, cache); 3722 }).bind('applyWidgetId', function (e, id) { 3723 getWidgetById(id).format(this); 3724 }).bind('applyWidgets', function () { 3725 applyWidget(this); 3726 }); 3727 if ($.metadata && $(this).metadata() && $(this).metadata().sortlist) { 3728 config.sortList = $(this).metadata().sortlist; 3729 } 3730 if (config.sortList.length > 0) { 3731 $this.trigger('sorton', [config.sortList]); 3732 } 3733 applyWidget(this); 3734 }); 3735 }; 3736 this.addParser = function (parser) { 3737 var l = parsers.length, 3738 a = true; 3739 for (var i = 0; i < l; i++) { 3740 if (parsers[i].id.toLowerCase() == parser.id.toLowerCase()) { 3741 a = false; 3742 } 3743 } 3744 if (a) { 3745 parsers.push(parser); 3746 } 3747 }; 3748 this.addWidget = function (widget) { 3749 widgets.push(widget); 3750 }; 3751 this.formatFloat = function (s) { 3752 var i = parseFloat(s); 3753 return isNaN(i) ? 0 : i; 3754 }; 3755 this.formatInt = function (s) { 3756 var i = parseInt(s); 3757 return isNaN(i) ? 0 : i; 3758 }; 3759 this.isDigit = function (s, config) { 3760 return /^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g, ''))); 3761 }; 3762 this.clearTableBody = function (table) { 3763 if ($.browser.msie) { 3764 var empty = function empty() { 3765 while (this.firstChild) { 3766 this.removeChild(this.firstChild); 3767 } 3768 }; 3769 empty.apply(table.tBodies[0]); 3770 } else { 3771 table.tBodies[0].innerHTML = ''; 3772 } 3773 }; 3774 }() 3775 }); 3776 $.fn.extend({ 3777 tablesorter: $.tablesorter.construct 3778 }); 3779 var ts = $.tablesorter; 3780 ts.addParser({ 3781 id: 'text', 3782 is: function is(s) { 3783 return true; 3784 }, 3785 format: function format(s) { 3786 return $.trim(s.toLocaleLowerCase()); 3787 }, 3788 type: 'text' 3789 }); 3790 ts.addParser({ 3791 id: 'digit', 3792 is: function is(s, table) { 3793 var c = table.config; 3794 return $.tablesorter.isDigit(s, c); 3795 }, 3796 format: function format(s) { 3797 return $.tablesorter.formatFloat(s); 3798 }, 3799 type: 'numeric' 3800 }); 3801 ts.addParser({ 3802 id: 'currency', 3803 is: function is(s) { 3804 return /^[£$â¬?.]/.test(s); 3805 }, 3806 format: function format(s) { 3807 return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$â¬]/g), '')); 3808 }, 3809 type: 'numeric' 3810 }); 3811 ts.addParser({ 3812 id: 'ipAddress', 3813 is: function is(s) { 3814 return /^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s); 3815 }, 3816 format: function format(s) { 3817 var a = s.split('.'), 3818 r = '', 3819 l = a.length; 3820 for (var i = 0; i < l; i++) { 3821 var item = a[i]; 3822 if (item.length == 2) { 3823 r += '0' + item; 3824 } else { 3825 r += item; 3826 } 3827 } 3828 return $.tablesorter.formatFloat(r); 3829 }, 3830 type: 'numeric' 3831 }); 3832 ts.addParser({ 3833 id: 'url', 3834 is: function is(s) { 3835 return /^(https?|ftp|file):\/\/$/.test(s); 3836 }, 3837 format: function format(s) { 3838 return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//), '')); 3839 }, 3840 type: 'text' 3841 }); 3842 ts.addParser({ 3843 id: 'isoDate', 3844 is: function is(s) { 3845 return /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s); 3846 }, 3847 format: function format(s) { 3848 return $.tablesorter.formatFloat(s != '' ? new Date(s.replace(new RegExp(/-/g), '/')).getTime() : '0'); 3849 }, 3850 type: 'numeric' 3851 }); 3852 ts.addParser({ 3853 id: 'percent', 3854 is: function is(s) { 3855 return /\%$/.test($.trim(s)); 3856 }, 3857 format: function format(s) { 3858 return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g), '')); 3859 }, 3860 type: 'numeric' 3861 }); 3862 ts.addParser({ 3863 id: 'usLongDate', 3864 is: function is(s) { 3865 return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/)); 3866 }, 3867 format: function format(s) { 3868 return $.tablesorter.formatFloat(new Date(s).getTime()); 3869 }, 3870 type: 'numeric' 3871 }); 3872 ts.addParser({ 3873 id: 'shortDate', 3874 is: function is(s) { 3875 return /\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s); 3876 }, 3877 format: function format(s, table) { 3878 var c = table.config; 3879 s = s.replace(/\-/g, '/'); 3880 if (c.dateFormat == 'us') { 3881 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, '$3/$1/$2'); 3882 } else if (c.dateFormat == 'uk') { 3883 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, '$3/$2/$1'); 3884 } else if (c.dateFormat == 'dd/mm/yy' || c.dateFormat == 'dd-mm-yy') { 3885 s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, '$1/$2/$3'); 3886 } 3887 return $.tablesorter.formatFloat(new Date(s).getTime()); 3888 }, 3889 type: 'numeric' 3890 }); 3891 ts.addParser({ 3892 id: 'time', 3893 is: function is(s) { 3894 return /^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s); 3895 }, 3896 format: function format(s) { 3897 return $.tablesorter.formatFloat(new Date('2000/01/01 ' + s
3897).getTime()); 3898 }, 3899 type: 'numeric' 3900 }); 3901 ts.addParser({ 3902 id: 'metadata', 3903 is: function is(s) { 3904 return false; 3905 }, 3906 format: function format(s, table, cell) { 3907 var c = table.config, 3908 p = !c.parserMetadataName ? 'sortValue' : c.parserMetadataName; 3909 return $(cell).metadata()[p]; 3910 }, 3911 type: 'numeric' 3912 }); 3913 ts.addWidget({ 3914 id: 'zebra', 3915 format: function format(table) { 3916 if (table.config.debug) { 3917 var time = new Date(); 3918 } 3919 var $tr, 3920 row = -1, 3921 odd; 3922 $('tr:visible', table.tBodies[0]).each(function (i) { 3923 $tr = $(this); 3924 if (!$tr.hasClass(table.config.cssChildRow)) { 3925 row++; 3926 } 3927 odd = row % 2 == 0; 3928 $tr.removeClass(table.config.widgetZebra.css[odd ? 0 : 1]).addClass(table.config.widgetZebra.css[odd ? 1 : 0]); 3929 }); 3930 if (table.config.debug) { 3931 $.tablesorter.benchmark('Applying Zebra widget', time); 3932 } 3933 } 3934 }); 3935})(jQuery); 3936(function (app, core) { 3937 'use strict'; 3938 3939 var OBSERVER_CONFIG = { 3940 root: null, 3941 // default to browser viewport 3942 rootMargin: '0px', 3943 threshold: 0.025 3944 }; 3945 var OBSERVABLE_SELECTOR = '.js-lazy-load'; 3946 3947 /** 3948 * Class that loads in images when they become active 3949 * upon entering the viewport 3950 * 3951 * @param {HTMLElement} container the container element of the items that should be observed 3952 * @constructor 3953 */ 3954 app.LazyLoad = function (container) { 3955 var _self = this; 3956 var observableContainers = [].slice.call(container.querySelectorAll(OBSERVABLE_SELECTOR)); 3957 3958 // get items unobserved and apply the listeners 3959 var uninitiatedObservables = _self.getUnobservredElements(observableContainers); 3960 _self.setObservableListener(uninitiatedObservables); 3961 }; 3962 3963 /** 3964 * Filters the observableContainer list so the items being observed have not been observed before 3965 * and are the correct markup elements for observing 3966 * 3967 * @param {HTMLElement[]} observableContainers - the matching elements that are meant to be observed 3968 * @return {Array} Filtered array of observable elements 3969 */ 3970 app.LazyLoad.prototype.getUnobservredElements = function getUnobservredElements(observableContainers) { 3971 return observableContainers.filter(function (observableEl) { 3972 return observableEl && observableEl.getAttribute('data-picture-in-view') !== 'true'; 3973 }); 3974 }; 3975 3976 /** 3977 * Listens for elements to become visible within the viewport 3978 * 3979 * @param {HTMLElement[]} observableContainers - the matching elements to apply observer listeners to 3980 */ 3981 app.LazyLoad.prototype.setObservableListener = function setOberservableListener(observableContainers) { 3982 var _self = this; 3983 _self.observer = new IntersectionObserver(onActivate, OBSERVER_CONFIG); 3984 observableContainers.forEach(function (element) { 3985 _self.observer.observe(element); 3986 }); 3987 }; 3988 3989 /** 3990 * Callback function from intersection observer 3991 * 3992 * `getAttribute` has been used deliberately as it tested faster than 3993 * `element.dataset.attrName`. 3994 * 3995 * @param {Object[]} entries An Array of IntersectionObserver elements 3996 */ 3997 var onActivate = function onActivate(entries) { 3998 entries.forEach(function (contentItem) { 3999 var imgContainer = contentItem.target; 4000 if (!imgContainer) { 4001 return; 4002 } 4003 4004 // check if the intersection observer's already run on this element 4005 var hasIntersectedBefore = imgContainer.getAttribute('data-picture-in-view'); 4006 if (contentItem.isIntersecting === false || hasIntersectedBefore === 'true') { 4007 return; 4008 } 4009 4010 // Picture is now in view so add this attr to stop it rendering again 4011 imgContainer.setAttribute('data-picture-in-view', 'true'); 4012 4013 // Behaviour for macros/render-picture.ftl 4014 if (imgContainer.hasAttribute('data-lazy-load-picture')) { 4015 var sources = imgContainer.getElementsByTagName('source'); 4016 if (sources.length > -1) { 4017 for (var i = 0; i < sources.length; i++) { 4018 var srcsetValue = sources[i].getAttribute('data-srcset'); 4019 if (srcsetValue) { 4020 sources[i].setAttribute('srcset', srcsetValue); 4021 sources[i].removeAttribute('data-srcset'); 4022 } 4023 } 4024 } 4025 var image = imgContainer.getElementsByTagName('img'); 4026 if (image.length === 1) { 4027 var srcValue = image[0].getAttribute('data-src'); 4028 image[0].setAttribute('src', srcValue); 4029 image[0].removeAttribute('data-src'); 4030 if (image[0].classList.contains('js-faded-image')) { 4031 image[0].addEventListener('load', function () { 4032 core.style.addClass(image[0], 'is-loaded'); 4033 }); 4034 } 4035 } 4036 } else { 4037 // Default behaviour 4038 4039 var model = { 4040 photoId: imgContainer.getAttribute('data-photo-id'), 4041 cssClass: imgContainer.getAttribute('data-picture-classes'), 4042 thumbnail: imgContainer.getAttribute('data-desktop-img'), 4043 thumbnailRetina: imgContainer.getAttribute('data-desktop-img-retina') || '', 4044 thumbnailMobile: imgContainer.getAttribute('data-mobile-img') || '', 4045 thumbnailMobileRetina: imgContainer.getAttribute('data-mobile-img-retina') || '', 4046 altAttribute: imgContainer.getAttribute('data-alt-attr'), 4047 fadeInOnLoad: true 4048 }; 4049 var imgSrc = imgContainer.getAttribute('data-image-src'); 4050 var imageEl; 4051 if (imgSrc) { 4052 model.imgSrc = imgSrc; 4053 imageEl = app.templating.render(model, 'templates/components/image-element.html', true); 4054 } else { 4055 imageEl = app.templating.render(model, 'templates/components/picture-element.html', true); 4056 } 4057 if (imageEl) { 4058 imgContainer.appendChild(imageEl); 4059 } 4060 var fadedImage = imgContainer.querySelector('.js-faded-image'); 4061 if (fadedImage) { 4062 fadedImage.addEventListener('load', function () { 4063 core.style.addClass(fadedImage, 'is-loaded'); 4064 }); 4065 } 4066 } 4067 }); 4068 }; 4069 var widgets = document.querySelectorAll('[data-widget="lazy-load-images"]'); 4070 Array.prototype.forEach.call(widgets, function (widget) { 4071 return new app.LazyLoad(widget); 4072 }); 4073})(PULSE.app, PULSE.core);
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.