1'use strict'; 2 3function isOnMobile() { 4 return window.matchMedia("(max-width: 767px)").matches; // Mobile media-breakpoint-down(sm) [0, 767px] 5} 6 7function isOnDesktop() { 8 return window.matchMedia("(min-width: 768px)").matches; // Desktop @include media-breakpoint-up(md) [768px, â) 9} 10 11/** @return {Boolean} whether jQuery is available */ 12function isJQueryAvailable() { 13 return !(typeof jQuery === 'undefined'); 14} 15 16/** throws error if jQuery isn't available. */ 17function throwsErrorIfJQueryUnavailable() { 18 if(! isJQueryAvailable()) { 19 throw new Error("jQuery is required, but it isn't available."); 20 } 21} 22 23/** 24 * Calling to find country info, e.g, {country_code: 'SG', country_name: 'Singapore'} 25 * @param callbackFunc function with one param of type JSON object {country_code: 'SG', country_name: 'Singapore'} 26 * @return {*} null if needs to do AJAX or JSON object like {country_code: 'SG', country_name: 'Singapore'} if cached locally. 27 */ 28function getClientCountryInfo(callbackFunc) { 29 var countryCode = asprise_util.getFromCookieOrLocalStorage('country_code', null); 30 var countryName = asprise_util.getFromCookieOrLocalStorage('country_name', null); 31 var info = countryCode ? {country_code: countryCode, country_name: countryName} : null; 32 if(callbackFunc) { 33 if(info) { // from cookie 34 callbackFunc(info); 35 } else { // initiate POST 36 var jqxhr = $.post( "/util/geoloc", function(data) { 37 try { 38 info = asprise_util.isString(data) ? JSON.parse(data) : data; 39 } catch (parseErr) { 40 asprise_util.log("Not valid JSON: " + parseErr, true); 41 } 42 if(info && info['country_code']) { 43 asprise_util.setCookieAndLocalStorage('country_code', info['country_code']); 44 } 45 if(info && info['country_name']) { 46 asprise_util.setCookieAndLocalStorage('country_name', info['country_name']); 47 } 48 if(info && info['country_code']) { 49 callbackFunc(info); 50 } 51 }).fail(function() { 52 asprise_util.log(asprise_util.getJqXhrReadableErrorMesg.apply(null, arguments), true); 53 }); 54 } 55 } 56 return info; 57} 58 59/** Gets the scroll position in vertical coordinate; compatible with all browsers. */ 60function getScrollY() { 61 var scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop; 62 return scrollY; 63} 64 65function logToConsole(mesg, isError) { 66 if(window.console) { 67 var dateStr = new Date().toLocaleTimeString(); 68 if(isError) { 69 if(console.error) { 70 console.error(dateStr + " " + mesg); 71 } else { 72 console.log(dateStr + " " + "ERROR: " + mesg); 73 } 74 } else { 75 if(console.info) { 76 console.info(dateStr + " " + mesg); 77 } else { 78 console.log(dateStr + "" + " INFO: " + mesg); 79 } 80 } 81 } else { // no console 82 if(isError) { 83 if(window.alert) { 84 alert("ERROR: " + mesg); 85 } 86 } 87 } 88} 89 90 91/** 92 * StickyNav: sticks on top when its original position is scrolled invisible; compatible with all browsers. 93 * @param {string} stickyNavWrapperSelector selects sticky nav wrappers (i.e., in-place div to prevent reflow when sticky nav
93changes display) 94 * @constructor 95 */ 96var StickyNav = function(stickyNavWrapperSelector) { 97 // Ref: https://codepen.io/Guilh/pen/JLKbn 98 // Ref: https://github.com/acch/scrollpos-styler/blob/master/scrollPosStyler.js 99 this.navWrappers = $(stickyNavWrapperSelector); 100 101 window.addEventListener('resize', function (e) { 102 this.onScrollChange(e); 103 }.bind(this), false); 104 105 // https://developer.mozilla.org/en-US/docs/Web/Events/scroll 106 this.last_known_scroll_position = 0; 107 window.addEventListener('scroll', function(e) { 108 var scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset : (document.documentElement || document.body.parentNode || document.body).scrollTop; 109 var direction = scrollY == this.last_known_scroll_position ? undefined : 110 (scrollY > this.last_known_scroll_position ? 'down' : 'up'); 111 this.last_known_scroll_position = scrollY; 112 if (direction) { 113 this.onScrollChange(e, direction); 114 } 115 }.bind(this), false); 116 117 this.onScrollChange(); 118}; 119 120/** 121 * To be called if there is any scroll change - either user scrolls or window resized. 122 * @param {UIEvent} event either 'resize' or 'scroll' 123 * @param {string|undefined} scroll direction 'up', 'down' or undefined 124 */ 125StickyNav.prototype.onScrollChange = function (event, scrollDirection) { 126 var verticalScrollPos = $(window).scrollTop(); 127 for(var i = 0; i < this.navWrappers.length; i++) { 128 var navWrapperElement = this.navWrappers.get(i); 129 var top = $(navWrapperElement).offset().top; 130 var bottom = top + navWrapperElement.clientHeight; // Assumption: border-box, i.e., height includes padding and border, but not margin. 131 if(verticalScrollPos > top) { 132 navWrapperElement.classList.add('sticky-nav-above-screen'); 133 } else { 134 navWrapperElement.classList.remove('sticky-nav-above-screen'); 135 } 136 137 if(bottom > verticalScrollPos + window.innerHeight) { // Assumption: no horizontal scrolls 138 navWrapperElement.classList.add('sticky-nav-below-screen'); 139 } else { 140 navWrapperElement.classList.remove('sticky-nav-below-screen'); 141 } 142 143 if(verticalScrollPos <= top && bottom <= verticalScrollPos + window.innerHeight) { 144 navWrapperElement.classList.add('sticky-nav-on-screen'); 145 } else { 146 navWrapperElement.classList.remove('sticky-nav-on-screen'); 147 } 148 149 if(scrollDirection) { 150 navWrapperElement.setAttribute('scroll-direction', scrollDirection); 151 } else { 152 navWrapperElement.setAttribute('scroll-direction', ''); 153 } 154 } 155}; 156 157/** 158 * MegaMenu: displays drop down content items with morphing transition effect. 159 * @param {string} navbarSelector selector to select the top level navigation bar containing branding, triggers and other elements, e.g., <nav> 160 * @param {string} mmTriggerContainerSelector the immediate parent of mega menu triggers, e.g., <ul> 161 * @param {string} mmHolderSelector div.mm-holder, the top level container for mega menu items 162 * @param {Boolean} fullWidth applicable for desktop only: true (default) to have mega menu to occupy full screen width instead of individual sizes. 163 * @param {Boolean} clickToExpandItem applicable for desktop only: false (default) to use hover as triggering event on the triggers true to use clicks instead 164 * @constructor 165 */ 166var MegaMenu = function(navbarSelector, mmTriggerContainerSelector, mmHolderSelector, modeConfigured, fullWidth, clickToExpandItem) { 167 this.modeCurrent = null; 168 169 this.navbarSelector = navbarSelector; 170 this.triggerContainerSelector = mmTriggerContainerSelector; 171 this.holderSelector = mmHolderSelector; 172 this.fullWidth = fullWidth == null ? true : fullWidth; 173 this.clickToExpandItem = clickToExpandItem == null ? false : clickToExpandItem; 174 175 this.setMode(modeConfigured); 176}; 177 178/** Mega items are displayed separated in their original container. */ 179MegaMenu.ModeDesktop = 'desktop'; 180/** Mega items are displayed inline directly under the trigger. */ 181MegaMenu.ModeMobile = 'mobile'; 182/** Automatically switches between desktop and mobile */ 183MegaMenu.ModeAuto = 'auto'; 184 185/** 186 * Remove event listeners set up in previous mode (if available) and add event listeners to triggers and items for the given mode. 187 * Does nothing if the the mode is unchanged. 188 * @param {string} modeConfigured any of MegaMenu.ModeDesktop or MegaMenu.ModeMobile 189 */ 190MegaMenu.prototype.setMode = function (modeConfigured) { 191 if(modeConfigured != MegaMenu.ModeMobile && modeConfigured != MegaMenu.ModeDesktop && modeConfigured != MegaMenu.ModeAuto) { 192 throw new Error("Unsupported mode: " + modeConfigured); 193 } 194 195 this.modeConfigured = modeConfigured; 196 var modeToBe = modeConfigured == MegaMenu.ModeAuto ? (isOnDesktop() ? MegaMenu.ModeDesktop : MegaMenu.ModeMobile) : modeConfigured; 197 198 if(modeToBe == this.modeCurrent) { // same mode, nothing to do. 199 return; 200 } 201 if(this.modeCurrent) { 202 logToConsole('Switching mode to: ' + modeToBe + " from " + this.modeCurrent); 203 } 204 if(isJQueryAvailable()) { 205 jQuery(this.navbarSelector).removeClass(MegaMenu.ModeMobile).removeClass(MegaMenu.ModeDesktop); 206 jQuery(this.navbarSelector).addClass(modeToBe); 207 } 208 209 // 1. Clean up 210 if(this.modeCurrent != null) { 211 this.collapse();
212 this.triggers.forEach(function (el) { 213 var menuItemSelector = el.getAttribute('data-menu-selector'); 214 var menuItemToSelect = menuItemSelector ? document.querySelector(menuItemSelector) : null; 215 if(menuItemToSelect) { 216 if(this.modeCurrent == MegaMenu.ModeDesktop) { 217 this.collapseDesktop(); 218 219 if (this.clickToExpandItem) { 220 el.classList.remove('clickToTrigger'); // el.classList.add('clickToTrigger'); 221 el.removeEventListener('click', this, false); // el.addEventListener('click', this, false); 222 } else { 223 el.removeEventListener('mouseenter', this, false); 224 el.removeEventListener('mouseleave', this, false); 225 } 226 } else if(this.modeCurrent == MegaMenu.ModeMobile) { 227 $(el).find('.content.collapseDesktop.show').collapse('hide'); 228 el.classList.remove('clickToTrigger'); // el.classList.add('clickToTrigger'); 229 for(var i = 0; i < el.children.length; i++) { 230 if(el.children[i].tagName.toUpperCase() == 'A') { 231 el.children[i].removeEventListener('click', this, false); 232 } 233 } 234 } 235 } 236 }.bind(this)); 237 238 if(this.modeCurrent == MegaMenu.ModeDesktop && !this.clickToExpandItem) { 239 this.holder.removeEventListener('mouseenter', this, false); 240 this.holder.removeEventListener('mouseleave', this, false); 241 } 242 this.items.forEach(function (el) { 243 el.classList.remove('active'); 244 el.style.opacity = 0; 245 }.bind(this)); 246 } 247 248 // 2. Switch mode 249 this.modeCurrent = modeToBe; // switch mode now 250 251 // 3. Set and add 252 this.navbar = document.querySelector(this.navbarSelector); 253 this.triggerContainer = document.querySelector(this.triggerContainerSelector); 254 /** @var {Element[]} */ 255 this.triggers = [].slice.call(this.triggerContainer.querySelectorAll('.mmi-trigger')); 256 this.selectedTrigger = undefined; 257 258 this.holder = document.querySelector(this.holderSelector); 259 this.arrow = this.holder.querySelector('.mm-arrow'); 260 this.bg = this.holder.querySelector('.mm-bg'); 261 this.bgBottom = this.holder.querySelector('.mm-bg-bottom'); 262 /** @var {Element} Element div.mm-items, direct parent of div.mmi's */ 263 this.container = this.holder.querySelector('.mm-items'); 264 /** @var {Element[]} Array of div.mmi */ 265 this.items = [].slice.call(this.container.querySelectorAll('.mmi')); 266 267 this.triggers.forEach(function (el) { 268 var menuItemSelector = el.getAttribute('data-menu-selector'); 269 var menuItemToSelect = menuItemSelector ? document.querySelector(menuItemSelector) : null; 270 if(menuItemToSelect) { 271 el.classList.add('hasMegaMenu'); 272 if(this.modeCurrent == MegaMenu.ModeDesktop) { 273 if (this.clickToExpandItem) { 274 el.classList.add('clickToTrigger'); 275 el.addEventListener('click', this, false); 276 } else { 277 el.addEventListener('mouseenter', this, false); 278 el.addEventListener('mouseleave', this, false); 279 } 280 } else if(this.modeCurrent == MegaMenu.ModeMobile) { 281 el.classList.add('clickToTrigger'); 282 // we can't listen to click on the whole <li> as content will be a descendant of <li> 283 for(var i = 0; i < el.children.length; i++) { 284 if(el.children[i].tagName.toUpperCase() == 'A') { 285 el.children[i].addEventListener('click', this, false); 286 } 287 } 288 var contentContainer = el.querySelector('.content'); 289 if(contentContainer && contentContainer.childElementCount == 0) { // we need to copy over 290 throwsErrorIfJQueryUnavailable(); 291 contentContainer.classList.remove('show'); // depends on Bootstrap v4 292 contentContainer.classList.add('collapse'); 293 294 var cloned = $(menuItemToSelect).clone().removeAttr('id'); 295 if(cloned.css('opacity') == 0) { // somehow, css style is set. 296 cloned.css('opacity', ''); 297 } 298 cloned.appendTo(contentContainer); 299 } 300 } 301 } else { 302 el.classList.remove('hasMegaMenu'); 303 } 304 }.bind(this)); 305 306 if(this.modeCurrent == MegaMenu.ModeDesktop && !this.clickToExpandItem) { 307 this.holder.addEventListener('mouseenter', this, false); 308 this.holder.addEventListener('mouseleave', this, false); 309 }
310 this.items.forEach(function (el) { 311 el.classList.remove('active'); 312 el.style.opacity = 0; 313 }.bind(this)); 314 315 if(this.modeConfigured == MegaMenu.ModeAuto) { 316 throwsErrorIfJQueryUnavailable(); 317 window.addEventListener('resize', function () { 318 this.setMode(isOnDesktop() ? MegaMenu.ModeDesktop : MegaMenu.ModeMobile); 319 }.bind(this), false); 320 } 321 322 return this; 323}; 324 325/** 326 * 327 * @param {MouseEvent} event 328 */ 329MegaMenu.prototype.handleEvent = function(event) { 330 var elementCurrent = event.currentTarget; // the element to which the event handler has been attached 331 var handled = false; 332 if(this.modeCurrent == MegaMenu.ModeDesktop) { // Desktop 333 if (this.triggers.indexOf(elementCurrent) >= 0) { // is a trigger 334 var action = null; 335 if (this.clickToExpandItem) { 336 if (event.type == 'click') { 337 action = elementCurrent.classList.contains('active') ? 'collapse' : 'expand'; 338 } 339 } else { // hover 340 if (event.type == 'mouseenter') { 341 action = 'expand'; 342 } else if (event.type == 'mouseleave') { 343 action = 'collapse'; 344 } 345 } 346 347 if (action == 'expand') { 348 elementCurrent.classList.add('active'); 349 // get mega menu id 350 var menuItemSelector = elementCurrent.getAttribute('data-menu-selector'); 351 var menuItemToSelect = menuItemSelector ? document.querySelector(menuItemSelector) : null; 352 if (menuItemToSelect) { 353 this.stopCollapseDesktop(); 354 } 355 this.expandDesktop(elementCurrent, menuItemToSelect); 356 handled = true; 357 } else if (action == 'collapse') { 358 // elementCurrent.classList.remove('active'); 359 this.startCollapseDesktopDesktop(); 360 handled = true; 361 } 362 } else if (this.holder == elementCurrent || this.items.indexOf(elementCurrent) >= 0) { // is a mega menu item 363 if (!this.clickToExpandItem) { 364 if (event.type == 'mouseenter') { 365 this.stopCollapseDesktop(); 366 handled = true; 367 } else if (event.type == 'mouseleave') { 368 this.startCollapseDesktopDesktop(); 369 handled = true; 370 } 371 } 372 } 373 } else if(this.modeCurrent == MegaMenu.ModeMobile) { // Mobile 374 // Clicked on a trigger's first child 375 if(event.type == 'click' && this.triggers.indexOf(elementCurrent.parentNode) >= 0) { // e.g., event type: click; current target: A.nav-link 376 var triggeringElement = elementCurrent.parentNode; // LI 377 this.expandMobile(triggeringElement); 378 handled = true; 379 } 380 } 381 382 if(! handled) { 383 console.warn("Not handled: " + event.type + ': current target: ' + elementCurrent.tagName + '.' + elementCurrent.classList); 384 } 385}; 386 387/** 388 * Applicable to Mobile only; Sets the given trigger to active and expand the content; closes the other active trigger if any. 389 * @param {Element} triggeringElement 390 */ 391MegaMenu.prototype.expandMobile = function(triggeringElement) { 392 this.selectedTrigger = triggeringElement; 393 if(this.selectedTrigger == null) { 394 this.collapseMobile(); 395 return; 396 } 397 398 var contentToToggle = triggeringElement.querySelector('.content'); 399 if (contentToToggle == null) { 400 logToConsole('.content is not set', true); 401 } else { 402 var active = ! triggeringElement.classList.contains('active'); 403 if(active) { // toggle active 404 triggeringElement.classList.add('active'); 405 } else { 406 triggeringElement.classList.remove('active'); 407 } 408 409 $(contentToToggle).collapse(active ? 'show' : 'hide'); 410 411 if(active) { // collapse others 412 $(triggeringElement.parentNode).find('.content').each(function() { // collapse other content 413 if(this != contentToToggle) { 414 $(this).collapse('hide'); 415 } 416 }); 417 418 $(triggeringElement.parentNode).children().each(function() { // de-active other triggers 419 if(this != triggeringElement) { 420 this.classList.remove('active'); 421 } 422 }); 423 } 424 } 425}; 426 427/** Applicable to Mobile only; removes 'active' class from all triggers and items */ 428MegaMenu.prototype.collapseMobile = function() { 429 this.selectedTrigger = undefined;
430 this.triggers.forEach(function (el) { 431 el.classList.remove('active'); 432 $(el).find('.content').each(function() { // collapse all content 433 $(this).collapse('hide'); 434 }); 435 }); 436 if(this.triggerContainer.parentNode.classList.contains('show')) { 437 $(this.triggerContainer.parentNode).collapse('hide'); // $('.navbar-collapse').collapse('hide'); 438 } 439}; 440 441/** Collpase Desktop/Mobile depending on the current mode. */ 442MegaMenu.prototype.collapse = function() { 443 if(this.modeCurrent == MegaMenu.ModeDesktop) { 444 this.collapseDesktop(); 445 } else if(this.modeCurrent == MegaMenu.ModeMobile) { 446 this.collapseMobile(); 447 } 448}; 449 450/** 451 * Applicable to Desktop only; expands the given item. 452 * @param {Element} triggerToSelect the trigger 453 * @param {Element} itemToSelect the div.mmi to be expanded 454 */ 455MegaMenu.prototype.expandDesktop = function (triggerToSelect, itemToSelect) { 456 this.selectedTrigger = triggerToSelect; 457 this.selectedItem = itemToSelect; 458 if(itemToSelect == null) { 459 this.startCollapseDesktopDesktop(); 460 return; 461 } 462 463 if(! triggerToSelect) { 464 throw 'mm item to select is set, but trigger is not set.'; 465 } 466 467 var itemContent = itemToSelect.querySelector('.mmi-content'); 468 var itemTop = itemContent.querySelector('.mmi-top'); 469 var itemBottom = itemContent.querySelector('.mmi-bottom'); 470 471 var rectTrigger = triggerToSelect.getBoundingClientRect(); 472 var rectTriggerContainer = document.querySelector(this.triggerContainerSelector).getBoundingClientRect(); 473 var rectItemContent = itemContent.getBoundingClientRect(); 474 475 this.triggers.forEach(function (el) { 476 return el.classList.remove('active'); 477 }); 478 triggerToSelect.classList.add('active'); 479 480 this.items.forEach(function (el) { 481 el.classList.remove('active'); 482 el.style.opacity = 0; 483 }); 484 itemToSelect.classList.add('active'); 485 486 this.bg.style.opacity = 1; 487 if(! this.fullWidth) { 488 this.bg.style.left = rectTrigger.left - (rectItemContent.width / 2 - rectTrigger.width / 2) + 'px'; 489 this.bg.style.width = rectItemContent.width + 'px'; 490 } 491 this.bg.style.height = rectItemContent.height + 'px'; 492 this.bgBottom.style.top = itemTop.offsetHeight + 'px'; 493 494 this.arrow.style.opacity = 1; 495 this.arrow.style.left = rectTrigger.left + rectTrigger.width / 2 - 10 + 'px'; // CP 496 497 this.container.style.opacity = 1; 498 //this.container.style.top = 0; 499 if(! this.fullWidth) { 500 this.container.style.left = this.bg.style.left; 501 this.container.style.width = this.bg.style.width; 502 } 503 this.container.style.height = this.bg.style.height; 504 505 itemToSelect.style.opacity = 1; 506 this.holder.classList.add('active'); 507 508 document.querySelector('body').classList.add('mega-menu-expanded'); 509 document.querySelector(this.navbarSelector).classList.add('mega-menu-expanded'); 510 // console.log("+ mega-menu-expanded"); 511}; 512 513/** Applicable to Desktop only; removes 'active' class from all triggers and items; remove 'expanded' from holder. */ 514MegaMenu.prototype.collapseDesktop = function() { 515 this.selectedTrigger = undefined; 516 this.triggers.forEach(function (el) { 517 return el.classList.remove('active'); 518 }); 519 520 this.items.forEach(function (el) { 521 el.classList.remove('active'); 522 el.style.opacity = 0; 523 }); 524 525 this.bg.style.opacity = 0; 526 this.arrow.style.opacity = 0; 527 this.container.style.opacity = 0; 528 this.holder.classList.remove('active'); 529 530 document.querySelector('body').classList.remove('mega-menu-expanded'); 531 document.querySelector(this.navbarSelector).classList.remove('mega-menu-expanded'); 532 // console.log("- mega-menu-expanded"); 533}; 534 535/** Applicable to Desktop only; triggers this.collapseDesktop after 50m */ 536MegaMenu.prototype.startCollapseDesktopDesktop = function () { 537 if(asprise_util.getUrlGetRequestParam('megamenu_debug')) { 538 console.log("MegaMenu collapse disabled."); 539 return; 540 } 541 this.collapseDesktopTimeoutId = setTimeout(function () { 542 var result = this.collapseDesktop(); 543 this.collapseDesktopTimeoutId = undefined; 544 return result; 545 }.bind(this), // bind 'this' to this 546 50 // in milliseconds 547 ); 548}; 549 550/** Applicable to Desktop only; cancels collapse timeout if it exists */ 551MegaMenu.prototype.stopCollapseDesktop = function () { 552 if(this.collapseDesktopTimeoutId == null) { 553 return; 554 } 555 clearTimeout(this.collapseDesktopTimeoutId); 556 this.collapseDesktopTimeoutId = undefined; 557};
558 559/** 560 * Creates and enables the mega menu; listens to collapse event of the navbar to collapse the mega menu. 561 * @param {string} navbarSelector default to '#navbar-site' 562 * @param {string} mmTriggerContainerSelector default to 'ul.navbar-nav' 563 * @param {string} mmHolderSelector default to 'div.mm-holder' 564 * @param {string} modeConfigured any of MegaMenu.ModeDesktop or MegaMenu.ModeMobile 565 * @return {MegaMenu} the mega menu created. 566 */ 567function enableMegaMenu(navbarSelector, mmTriggerContainerSelector, mmHolderSelector, modeConfigured) { 568 navbarSelector = navbarSelector || '#navbar-site'; 569 var mega = new MegaMenu(navbarSelector, mmTriggerContainerSelector || 'ul.navbar-nav', mmHolderSelector || 'div.mm-holder', modeConfigured, true, false); 570 $(navbarSelector + ' .navbar-collapse').on('hidden.bs.collapse', function (event) { 571 if (event.target.classList.contains('navbar-collapse')) { // skip event from nested collapse 572 if (event.type.indexOf('hid') >= 0) { 573 mega.collapse(); // Collapse mega menu if navbar-site is collapsed 574 } 575 } 576 }); 577 return mega; 578} 579 580/** 581 * Creates a headroom and init it; does nothing and returns null if the selector results no element. 582 * @param {string} docQuerySelectorToElement used to query the element to be headroomed, default to ".headroom" 583 * @param {int} offset don't unpin for the number of pixels scrolled 584 * @return {Headroom} 585 */ 586function enableHeadroom(docQuerySelectorToElement, offset) { 587 docQuerySelectorToElement = docQuerySelectorToElement || ".headroom"; 588 if(! document.querySelector(docQuerySelectorToElement)) { 589 return null; 590 } 591 var hr = new Headroom(document.querySelector(docQuerySelectorToElement), { 592 offset : offset || 10, // don't unpin for the top 160 pixel scrolled 593 onUnpin: function () { 594 // mega.collapse(); 595 // $('.navbar-collapse').collapse('hide'); 596 } 597 }); 598 hr.init(); // display nav when scrolling up 599 return hr; 600} 601 602/** 603 * Enables typing using typed.js; does nothing if the selector fails to select an element. 604 * @param {string} typedQuerySelector to be used in document.querySelector, defaults to '#typed' 605 * @param {int} waitTimeBeforeStartInMs, defaults to 3000 606 * @param {string[]} strings defaults to ['Scan Directly in Web Apps', ...] 607 */ 608function enableTyping(typedQuerySelector, waitTimeBeforeStartInMs, strings) { 609 typedQuerySelector = typedQuerySelector || '#typed'; 610 if(! document.querySelector(typedQuerySelector)) { 611 return null; 612 } 613 // typing effect 614 setTimeout(function() { 615 var typed = new Typed(typedQuerySelector, strings || { 616 strings: ["Scan Directly in Web Apps", 617 "Scan Directly in Dropbox", 618 "Scan Directly in Gmail", 619 "Scan Directly in Office 365", 620 "Scan Directly in QBO", 621 "Scan Directly in Salesforce", 622 "Scan Directly in SAP", 623 "Scan Directly in Web Apps" 624 ], 625 typeSpeed: 160, 626 backSpeed: 0, 627 backDelay: 500, 628 startDelay: 0, // use the outside setTimeout to avoid cursor appears at the end while not typing. 629 loop: false, 630 onComplete: function(self) { 631 self.cursor.classList.add('typed-complete'); 632 } 633 }); 634 }, (typeof waitTimeBeforeStartInMs === 'undefined') ? 3000 : waitTimeBeforeStartInMs); // wait for 3s then start 635} 636 637/** 638 * Enables sticky nav for the given nav wrapper selector; does nothing and returns null if the selector fails to select any element. 639 * @param {string} stickyNavWrapperSelector default to '.sticky-nav-wrapper' 640 */ 641function enableStickyNav(stickyNavWrapperSelector) { 642 stickyNavWrapperSelector = stickyNavWrapperSelector || '.sticky-nav-wrapper'; 643 if($(stickyNavWrapperSelector).length == 0) { 644 return null; 645 } 646 return new StickyNav(stickyNavWrapperSelector); 647}
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.