PageSourceSearch

https://www.world.rugby/resources/prod/v9.19.3/js/pulse-ui.js

js world.rugby collected 2026-09-24 08:46:20 UTC 143,760 bytes, 4,073 lines download raw bytes

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 = '&nbsp;';
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.