1window.codexData = window.codexData || {}; 2let headerNavigation = false; 3let headerNavigationFragment = ''; 4const oldViewName = window.codexData.view.name; 5 6function satelliteTrack(scrollViewTrack = false) { 7 // eslint-disable-next-line no-underscore-dangle 8 if (typeof window._satellite !== 'undefined') { 9 // eslint-disable-next-line no-underscore-dangle 10 let copyCodexData = Object.assign({}, codexData); 11 if( scrollViewTrack ) { 12 copyCodexData.content = {}; 13 } 14 const data = window._satellite.track('track', copyCodexData); 15 } 16} 17 18/** 19 * Does the element `el` have at least one of the CSS classnames in `classNames`? 20 * @param {Element} el Element to test for the presence of at least one of the CSS classnames in `classList`. 21 * @param {Array<string>} [classNames=[]] CSS classnames to test against. 22 * @returns {boolean} `false` if `classNames` is empty; otherwise whether or not `el` contains one of `classNames` in its `classList`. 23 */ 24function elementHasSomeClassName(el, classNames = []) { 25 return classNames.some(className => el.classList.contains(className)); 26} 27 28/** 29 * Handle carousel button click event. 30 * @param {HTMLElement} [slickElement=null] 31 */ 32function carouselClickEvent(slickElement = null) { 33 if (window.codexData) { 34 let slickIdentify = ''; 35 let slickArrowParentId = ''; 36 let slickArrowParentIdName = ''; 37 let pageName = ''; 38 39 if (window.location.pathname === '/') { 40 pageName = 'home'; 41 } 42 43 if (slickElement) { 44 delete codexData.flow; 45 46 const { flowComplete, flowStep, login, ...restEvents } = codexData.events; 47 48 codexData.events = restEvents; 49 50 const slickArrowElementClass = slickElement.className.split(' '); 51 52 slickArrowParentId = slickElement.closest('section'); 53 54 if (slickArrowParentId) { 55 const { name = '' } = slickArrowParentId.dataset.analytics ? JSON.parse(slickArrowParentId.dataset.analytics) : {}; 56 slickArrowParentIdName = name != '' ? ':' + name : ''; 57 } 58 59 if ( 60 slickElement.dataset?.carouselArrow?.toLowerCase() === 'next' 61 || elementHasSomeClassName(slickElement, ['slick-next', 'rotator-next-btn']) 62 ) { 63 slickIdentify = 'next'; 64 } else if ( 65 slickElement.dataset?.carouselArrow?.toLowerCase() === 'prev' 66 || elementHasSomeClassName(slickElement, ['slick-prev', 'rotator-prev-btn']) 67 ) { 68 slickIdentify = 'previous'; 69 } 70 } 71 72 window.codexData.content = {}; 73 window.codexData.events.interaction = true; 74 window.codexData.events.view = false; 75 76 pageOrViewName = pageName ? ':' + pageName : ':' + oldViewName; 77 78 window.codexData.interaction = { 79 name: (`${window.codexData.app.name}${pageOrViewName}${slickArrowParentIdName}:carousel-${slickIdentify}`), 80 }; 81 82 delete window.codexData.view; 83 84 satelliteTrack(); 85 } 86} 87 88function getRandomAlphanumericAsciiCharacter() { 89 const CHARACTER_RANGE = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 90 91 const randomIndex = Math.floor(Math.random() * CHARACTER_RANGE.length); 92 93 return CHARACTER_RANGE[randomIndex]; 94} 95 96function getRandomAlphanumericAsciiId(length) { 97 if (!(length > 0)) { 98 throw new Error('getRandomAlphanumericAsciiId must be called with a non-zero positive length'); 99 } 100 101 const id = Array(length) 102 .fill('') 103 .map(() => getRandomAlphanumericAsciiCharacter()); 104 105 return id.join(''); 106} 107 108function createSerializationId(idPrefix, id, timeMs) { 109 return `${idPrefix}${id}${timeMs}`; 110} 111 112/** 113 * @summary Use this function as a hook for successful newsletter signups. 114 * Call this function where you know the signup is successful, and it will take care of the analytics fields. 115 * @param {string} [flowName] Optionally override `flow.name`; otherwise will use default of `${app.name}-newsletter-signup` 116 * @returns {void} 117 */ 118function handleSuccessMessageAnalytics(flowName = null) { 119 if (window.codexData) { 120 // Get the app name from the codexData object, otherwise use `'corus'` as a default. 121 const namePrefix = window?.codexData?.app?.name ?? 'corus'; 122 123 // Get the 2-letter app ID from the codexData object, otherwise use `'CE'` as a default. 124 const idPrefix = window?.codexData?.misc?.serializationIdPrefix ?? 'CE'; 125 126 const oldCodexData = window.codexData; 127 128 const flowNameText = flowName ? flowName : `${namePrefix}-newsletter-signup`; 129 130 const newCodexData = { 131 ...oldCodexData, 132 events: { 133 flowComplete: true, 134 flowStep: true, 135 newsletterSignup: true,
136 view: false, // newsletter signup is not a page view 137 }, 138 flow: { 139 name: flowNameText, 140 serializationid: createSerializationId( 141 idPrefix, 142 getRandomAlphanumericAsciiId(4), 143 Date.now(), 144 ), 145 stepName: 'complete', 146 }, 147 }; 148 149 window.codexData = newCodexData; 150 151 satelliteTrack(); 152 } 153} 154 155if (window.codexData) { 156 // Trigger analytics for anchor links, buttons, or input or option elements when they have a 'data-analytics' attribute 157 const navOrBtnItems = document.querySelectorAll('a[data-analytics], button[data-analytics], input[data-analytics], option[data-analytics]'); 158 let navOrBtnItemName = ''; 159 160 navOrBtnItems.forEach((navOrBtnItem) => { 161 const { position = '', name = '', module = '', destination = '' } = JSON.parse(navOrBtnItem.dataset.analytics); 162 163 // By default first click should expand accordion 164 let expand = true; 165 166 navOrBtnItem.addEventListener('click', () => { 167 const { href: url = '' } = navOrBtnItem; 168 const [, fragmentUrl] = url.split('#'); 169 if (fragmentUrl) { 170 headerNavigation = true; 171 headerNavigationFragment = fragmentUrl; 172 } 173 window.codexData.events = window.codexData.events || {}; 174 window.codexData.content = {}; 175 window.codexData.events.interaction = true; 176 window.codexData.events.view = false; 177 let pageName = ''; 178 if (window.location.pathname === '/') { 179 pageName = 'home'; 180 } 181 182 sectionNavOrBtnItem = navOrBtnItem.closest('section'); 183 if (sectionNavOrBtnItem) { 184 const { name = '' } = sectionNavOrBtnItem.dataset.analytics ? JSON.parse(sectionNavOrBtnItem.dataset.analytics) : {}; 185 navOrBtnItemName = name != '' ? ':' + name : ''; 186 } 187 188 pageOrViewName = pageName ? ':' + pageName : ':' + oldViewName; 189 190 if (module) { 191 if (module == 'share') { 192 window.codexData.content = {}; 193 window.codexData.events.share = true; 194 window.codexData.share = {type : name}; 195 window.codexData.interaction = { 196 name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}`).replace(/(\W)\W*/g, '$1'), 197 }; 198 delete window.codexData.view; 199 } 200 201 if (module == 'faq') { 202 window.codexData.content = {}; 203 let expandOrCollapse = expand ? 'expand' : 'collapse'; 204 if(expandOrCollapse == 'expand') { 205 window.codexData.interaction = { 206 name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}:${name}:${expandOrCollapse}`).replace(/(\W)\W*/g, '$1'), 207 }; 208 209 // after expand, next on click event should collapse the accordion 210 expand = false; 211 } else { 212 delete window.codexData.events.interaction; 213 delete window.codexData.interaction; 214 215 expand = true; 216 return; 217 } 218 219 delete window.codexData.view; 220 } 221 222 /** 223 * Set data layer fields for items that direct users to external sites and applications. 224 * Use the `destination` property to indicate where the user is going using a slug. 225 * For example, `apple-podcasts`, `spotify`, etc. 226 */ 227 if (module === 'exit-link' && destination) { 228 window.codexData.content = {}; 229 230 const interactionName = ( 231 `${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}:${module}:${destination}` 232 ).replace(/(\W)\W*/g, '$1'); 233 234 window.codexData.interaction = { 235 name: interactionName, 236 }; 237 238 delete window.codexData.view; 239 } 240 } else { 241 const positionName = position ? ':' + position : ':'; 242 window.codexData.content = {}; 243 window.codexData.interaction = { 244 name: (`${window.codexData.app.name}:${pageOrViewName}${navOrBtnItemName}${positionName}:${name}`).replace(/(\W)\W*/g, '$1'), 245 }; 246 delete window.codexData.view; 247 } 248 249 satelliteTrack(); 250 }); 251 }); 252 253 //==================================================================================================================// 254 // NEWSLETTER PAGE FORM INITIAL FLOW SERIALIZATION ID // 255 //==================================================================================================================// 256 257 // If this is the Newsletter Page 258 if (window.codexData.content.type === 'page' && window.codexData.content.title.toLowerCase().includes('newsletter')) { 259 /** @type {NodeListOf<HTMLFormElement>} */ 260 const newsletterForms = document.querySelectorAll('form.newsletter-form'); 261 262 /** 263 * Create the initial newsletter flow serialization ID. 264 */ 265 if (newsletterForms.length > 0) { 266 // Get the 2-letter app ID from the codexData object, otherwise use `'CE'` as a default. 267 const idPrefix = window?.codexData?.misc?.serializationIdPrefix ?? 'CE'; 268 269 // Ensure codexData.flow is defined. 270 window.codexData.flow = window.codexData.flow || {}; 271 272 // Set the flow serialization ID, using the user's current client-side time in ms.
273 window.codexData.flow.serializationid = createSerializationId( 274 idPrefix, 275 getRandomAlphanumericAsciiId(4), 276 Date.now() 277 ); 278 } 279 } 280 281 //==================================================================================================================// 282 // END NEWSLETTER PAGE FORM INITIAL FLOW SERIALIZATION ID // 283 //==================================================================================================================// 284 285 // Trigger page views on scroll when section has 286 // 'data-analytics' and 'data-analytics-view' attributes 287 (() => { 288 let ticking = false; 289 function triggerAnalytics() { 290 ticking = false; 291 const elements = document.querySelectorAll('section[data-analytics][data-analytics-view=false]'); 292 293 elements.forEach((element) => { 294 const elementTop = element.getBoundingClientRect().top; 295 const elementBottom = element.getBoundingClientRect().bottom; 296 const windowHeight = window.innerHeight; 297 298 if (elementTop < windowHeight && windowHeight < elementBottom) { 299 const sectionID = element.getAttribute('id'); 300 301 if (sectionID && headerNavigation) { 302 if (sectionID === headerNavigationFragment) { 303 headerNavigation = false; 304 } else { 305 return; 306 } 307 } 308 309 window.codexData = window.codexData || {}; 310 window.codexData.view.type = 'section'; 311 312 const dataAnalytics = JSON.parse(element.dataset.analytics); 313 314 window.codexData.view.name = dataAnalytics.name; 315 316 window.codexData.events.view = true; 317 window.codexData.events.interaction = false; 318 319 if( typeof(window.overrideCodexDataConfig) === 'function' ) { 320 window.codexData = window.overrideCodexDataConfig(window.codexData); 321 } 322 323 satelliteTrack(true); 324 element.setAttribute('data-analytics-view', true); 325 } 326 }); 327 } 328 329 /** 330 * For Slick Slider ( Carousel ) arrow button click ( Next / Previous ) 331 * 332 * The arrow buttons should have one of the following to indicate it is a next / previous button: 333 * - a `data-carousel-arrow` attribute. Direction then depends on attribute value being `next` or `prev`. 334 * - a classlist including `slick-arrow`. Direction then depends on including a `slick-(next/prev)` class. 335 * - a classlist including `rotator-arrow`. Direction then depends on including a `rotator-(next/prev)-btn` class. 336 * 337 * Additionally, the buttons should NOT have a classlist including `fullSchedule-arrow` or `gallery-next`. 338 * 339 * @param {MouseEvent} event 340 */ 341 function handleCarouselClick(event) { 342 const slickArrowElement = event.target; 343 344 const hasCarouselArrowDatasetOrClassNames = ( 345 slickArrowElement.dataset?.carouselArrow /* data-carousel-arrrow */ 346 || elementHasSomeClassName(slickArrowElement, ['slick-arrow', 'rotator-arrow']) 347 ); 348 349 if ( 350 hasCarouselArrowDatasetOrClassNames 351 && !slickArrowElement.classList.contains('fullSchedule-arrow') 352 ) { 353 if (!slickArrowElement.classList.contains('gallery-next')) { 354 carouselClickEvent(slickArrowElement, true); 355 } 356 } 357 } 358 359 document.addEventListener('click', handleCarouselClick); 360 361 function loadRequest() { 362 // eslint-disable-next-line no-underscore-dangle 363 if (window._satellite !== 'undefined') { 364 ticking = ticking || requestAnimationFrame(triggerAnalytics); 365 } 366 } 367 368 window.addEventListener('scroll', loadRequest, false); 369 })(); 370 371 //==================================================================================================================// 372 // LIGHT / DARK MODE THEME TOGGLE // 373 //==================================================================================================================// 374 // Set data layer field for the state of the dark / light mode theme toggle, 375 // otherwise the browser / device theme preference. 376 // Field is of the form `{dark|light}:{system|user}` 377 378 /** 379 * Get the value of the `user_chosen_theme` cookie and verify it is either `'dark'` or `'light'`. 380 * Returns `undefined` if cookie does not exist or does not contain `'dark'` or `'light'`. 381 * @returns {'dark' | 'light' | undefined} 382 */ 383 const getUserChosenThemeCookieValue = () => { 384 // Adapated from MDN example. 385 const cookieValue = document.cookie 386 .split('; ') 387 .find((row) => row.startsWith('user_chosen_theme=')) 388 ?.split('=')[1]; 389 390 // Guard against unknown values 391 if (['dark', 'light'].includes(cookieValue)) { 392 return cookieValue; 393 } 394 395 return undefined; 396 }; 397 398 // Just in case 399 window.codexData.view = window.codexData.view || {}; 400 401 const userChosenThemeCookieValue = getUserChosenThemeCookieValue(); 402 403 if (userChosenThemeCookieValue) { 404 window.codexData.view.appearance = `${userChosenThemeCookieValue}:user`; 405 } else { 406 // Note that `light` could also indicate no preference / use browser or device settings 407 const isBrowserOrSystemDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; 408 409 window.codexData.view.appearance = `${isBrowserOrSystemDarkMode ? 'dark' : 'light'}:system`; 410 } 411 412 //==================================================================================================================// 413 // END LIGHT / DARK MODE THEME TOGGLE // 414 //==================================================================================================================// 415}
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.