1/** 2 * Intro.js v2.9.3 3 * https://github.com/usablica/intro.js 4 * 5 * Copyright (C) 2017 Afshin Mehrabani (@afshinmeh) 6 */ 7 8(function(f) { 9 if (typeof exports === "object" && typeof module !== "undefined") { 10 module.exports = f(); 11 // deprecated function 12 // @since 2.8.0 13 module.exports.introJs = function () { 14 console.warn('Deprecated: please use require("intro.js") directly, instead of the introJs method of the function'); 15 // introJs() 16 return f().apply(this, arguments); 17 }; 18 } else if (typeof define === "function" && define.amd) { 19 define([], f); 20 } else { 21 var g; 22 if (typeof window !== "undefined") { 23 g = window; 24 } else if (typeof global !== "undefined") { 25 g = global; 26 } else if (typeof self !== "undefined") { 27 g = self; 28 } else { 29 g = this; 30 } 31 g.introJs = f(); 32 } 33})(function () { 34 //Default config/variables 35 var VERSION = '2.9.3'; 36 37 /** 38 * IntroJs main class 39 * 40 * @class IntroJs 41 */ 42 function IntroJs(obj) { 43 this._targetElement = obj; 44 this._introItems = []; 45 46 this._options = { 47 /* Next button label in tooltip box */ 48 nextLabel: 'Next →', 49 /* Previous button label in tooltip box */ 50 prevLabel: '← Back', 51 /* Skip button label in tooltip box */ 52 skipLabel: 'Skip', 53 /* Done button label in tooltip box */ 54 doneLabel: 'Done', 55 /* Hide previous button in the first step? Otherwise, it will be disabled button. */ 56 hidePrev: false, 57 /* Hide next button in the last step? Otherwise, it will be disabled button. */ 58 hideNext: false, 59 /* Default tooltip box position */ 60 tooltipPosition: 'bottom', 61 /* Next CSS class for tooltip boxes */ 62 tooltipClass: '', 63 /* CSS class that is added to the helperLayer */ 64 highlightClass: '', 65 /* Close introduction when pressing Escape button? */ 66 exitOnEsc: true, 67 /* Close introduction when clicking on overlay layer? */ 68 exitOnOverlayClick: true, 69 /* Show step numbers in introduction? */ 70 showStepNumbers: true, 71 /* Let user use keyboard to navigate the tour? */ 72 keyboardNavigation: true, 73 /* Show tour control buttons? */ 74 showButtons: true, 75 /* Show tour bullets? */ 76 showBullets: true, 77 /* Show tour progress? */ 78 showProgress: false, 79 /* Scroll to highlighted element? */ 80 scrollToElement: true, 81 /* 82 * Should we scroll the tooltip or target element? 83 * 84 * Options are: 'element' or 'tooltip' 85 */ 86 scrollTo: 'element', 87 /* Padding to add after scrolling when element is not in the viewport (in pixels) */ 88 scrollPadding: 30, 89 /* Set the overlay opacity */ 90 overlayOpacity: 0.8, 91 /* Precedence of positions, when auto is enabled */ 92 positionPrecedence: ["bottom", "top", "right", "left"], 93 /* Disable an interaction with element? */ 94 disableInteraction: false, 95 /* Set how much padding to be used around helper element */ 96 helperElementPadding: 10, 97 /* Default hint position */ 98 hintPosition: 'top-middle', 99 /* Hint button label */ 100 hintButtonLabel: 'Got it', 101 /* Adding animation to hints? */ 102 hintAnimation: true, 103 /* additional classes to put on the buttons */ 104 buttonClass: "introjs-button" 105 }; 106 } 107 108 /** 109 * Initiate a new introduction/guide from an element in the page 110 * 111 * @api private 112 * @method _introForElement 113 * @param {Object} targetElm 114 * @param {String} group 115 * @returns {Boolean} Success or not? 116 */ 117 function _introForElement(targetElm, group) { 118 var allIntroSteps = targetElm.querySelectorAll("*[data-intro]"), 119 introItems = []; 120 121 if (this._options.steps) { 122 //use steps passed programmatically 123 _forEach(this._options.steps, function (step) { 124 var currentItem = _cloneObject(step); 125 126 //set the step
127 currentItem.step = introItems.length + 1; 128 129 //use querySelector function only when developer used CSS selector 130 if (typeof (currentItem.element) === 'string') { 131 //grab the element with given selector from the page 132 currentItem.element = document.querySelector(currentItem.element); 133 } 134 135 //intro without element 136 if (typeof (currentItem.element) === 'undefined' || currentItem.element === null) { 137 var floatingElementQuery = document.querySelector(".introjsFloatingElement"); 138 139 if (floatingElementQuery === null) { 140 floatingElementQuery = document.createElement('div'); 141 floatingElementQuery.className = 'introjsFloatingElement'; 142 143 document.body.appendChild(floatingElementQuery); 144 } 145 146 currentItem.element = floatingElementQuery; 147 currentItem.position = 'floating'; 148 } 149 150 currentItem.scrollTo = currentItem.scrollTo || this._options.scrollTo; 151 152 if (typeof (currentItem.disableInteraction) === 'undefined') { 153 currentItem.disableInteraction = this._options.disableInteraction; 154 } 155 156 if (currentItem.element !== null) { 157 introItems.push(currentItem); 158 } 159 }.bind(this)); 160 161 } else { 162 //use steps from data-* annotations 163 var elmsLength = allIntroSteps.length; 164 var disableInteraction; 165 166 //if there's no element to intro 167 if (elmsLength < 1) { 168 return false; 169 } 170 171 _forEach(allIntroSteps, function (currentElement) { 172 173 // PR #80 174 // start intro for groups of elements 175 if (group && (currentElement.getAttribute("data-intro-group") !== group)) { 176 return; 177 } 178 179 // skip hidden elements 180 if (currentElement.style.display === 'none') { 181 return; 182 } 183 184 var step = parseInt(currentElement.getAttribute('data-step'), 10); 185 186 if (typeof (currentElement.getAttribute('data-disable-interaction')) !== 'undefined') { 187 disableInteraction = !!currentElement.getAttribute('data-disable-interaction'); 188 } else { 189 disableInteraction = this._options.disableInteraction; 190 } 191 192 if (step > 0) { 193 introItems[step - 1] = { 194 element: currentElement, 195 intro: currentElement.getAttribute('data-intro'), 196 step: parseInt(currentElement.getAttribute('data-step'), 10), 197 tooltipClass: currentElement.getAttribute('data-tooltipclass'), 198 highlightClass: currentElement.getAttribute('data-highlightclass'), 199 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition, 200 scrollTo: currentElement.getAttribute('data-scrollto') || this._options.scrollTo, 201 disableInteraction: disableInteraction 202 }; 203 } 204 }.bind(this)); 205 206 //next add intro items without data-step 207 //todo: we need a cleanup here, two loops are redundant 208 var nextStep = 0; 209 210 _forEach(allIntroSteps, function (currentElement) { 211 212 // PR #80 213 // start intro for groups of elements 214 if (group && (currentElement.getAttribute("data-intro-group") !== group)) { 215 return; 216 } 217 218 if (currentElement.getAttribute('data-step') === null) { 219 220 while (true) { 221 if (typeof introItems[nextStep] === 'undefined') { 222 break; 223 } else { 224 nextStep++; 225 } 226 } 227 228 if (typeof (currentElement.getAttribute('data-disable-interaction')) !== 'undefined') { 229 disableInteraction = !!currentElement.getAttribute('data-disable-interaction'); 230 } else { 231 disableInteraction = this._options.disableInteraction; 232 } 233 234 introItems[nextStep] = { 235 element: currentElement, 236 intro: currentElement.getAttribute('data-intro'), 237 step: nextStep + 1, 238 tooltipClass: currentElement.getAttribute('data-tooltipclass'), 239 highlightClass: currentElement.getAttribute('data-highlightclass'), 240 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition, 241 scrollTo: currentElement.getAttribute('data-scrollto') || this._options.scrollTo, 242 disableInteraction: disableInteraction 243 }; 244 } 245 }.bind(this)); 246 } 247 248 //removing undefined/null elements 249 var tempIntroItems = []; 250 for (var z = 0; z < introItems.length; z++) { 251 if (introItems[z]) { 252 // copy non-falsy values to the end of the array 253 tempIntroItems.push(introItems[z]); 254 } 255 } 256 257 introItems = tempIntroItems; 258 259 //Ok, sort all items with given steps 260 introItems.sort(function (a, b) { 261 return a.step - b.step; 262 });
263 264 //set it to the introJs object 265 this._introItems = introItems; 266 267 //add overlay layer to the page 268 if(_addOverlayLayer.call(this, targetElm)) { 269 //then, start the show 270 _nextStep.call(this); 271 272 if (this._options.keyboardNavigation) { 273 DOMEvent.on(window, 'keydown', _onKeyDown, this, true); 274 } 275 //for window resize 276 DOMEvent.on(window, 'resize', _onResize, this, true); 277 } 278 return false; 279 } 280 281 function _onResize () { 282 this.refresh.call(this); 283 } 284 285 /** 286 * on keyCode: 287 * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode 288 * This feature has been removed from the Web standards. 289 * Though some browsers may still support it, it is in 290 * the process of being dropped. 291 * Instead, you should use KeyboardEvent.code, 292 * if it's implemented. 293 * 294 * jQuery's approach is to test for 295 * (1) e.which, then 296 * (2) e.charCode, then 297 * (3) e.keyCode 298 * https://github.com/jquery/jquery/blob/a6b0705294d336ae2f63f7276de0da1195495363/src/event.js#L638 299 * 300 * @param type var 301 * @return type 302 */ 303 function _onKeyDown (e) { 304 var code = (e.code === null) ? e.which : e.code; 305 306 // if code/e.which is null 307 if (code === null) { 308 code = (e.charCode === null) ? e.keyCode : e.charCode; 309 } 310 311 if ((code === 'Escape' || code === 27) && this._options.exitOnEsc === true) { 312 //escape key pressed, exit the intro 313 //check if exit callback is defined 314 _exitIntro.call(this, this._targetElement); 315 } else if (code === 'ArrowLeft' || code === 37) { 316 //left arrow 317 _previousStep.call(this); 318 } else if (code === 'ArrowRight' || code === 39) { 319 //right arrow 320 _nextStep.call(this); 321 } else if (code === 'Enter' || code === 13) { 322 //srcElement === ie 323 var target = e.target || e.srcElement; 324 if (target && target.className.match('introjs-prevbutton')) { 325 //user hit enter while focusing on previous button 326 _previousStep.call(this); 327 } else if (target && target.className.match('introjs-skipbutton')) { 328 //user hit enter while focusing on skip button 329 if (this._introItems.length - 1 === this._currentStep && typeof (this._introCompleteCallback) === 'function') { 330 this._introCompleteCallback.call(this); 331 } 332 333 _exitIntro.call(this, this._targetElement); 334 } else if (target && target.getAttribute('data-stepnumber')) { 335 // user hit enter while focusing on step bullet 336 target.click(); 337 } else { 338 //default behavior for responding to enter 339 _nextStep.call(this); 340 } 341 342 //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers 343 if(e.preventDefault) { 344 e.preventDefault(); 345 } else { 346 e.returnValue = false; 347 } 348 } 349 } 350 351 /* 352 * makes a copy of the object 353 * @api private 354 * @method _cloneObject 355 */ 356 function _cloneObject(object) { 357 if (object === null || typeof (object) !== 'object' || typeof (object.nodeType) !== 'undefined') { 358 return object; 359 } 360 var temp = {}; 361 for (var key in object) { 362 if (typeof(window.jQuery) !== 'undefined' && object[key] instanceof window.jQuery) { 363 temp[key] = object[key]; 364 } else { 365 temp[key] = _cloneObject(object[key]); 366 } 367 } 368 return temp; 369 } 370 /** 371 * Go to specific step of introduction 372 * 373 * @api private 374 * @method _goToStep 375 */ 376 function _goToStep(step) { 377 //because steps starts with zero 378 this._currentStep = step - 2; 379 if (typeof (this._introItems) !== 'undefined') { 380 _nextStep.call(this); 381 } 382 } 383 384 /** 385 * Go to the specific step of introduction with the explicit [data-step] number 386 * 387 * @api private 388 * @method _goToStepNumber 389 */ 390 function _goToStepNumber(step) { 391 this._currentStepNumber = step; 392 if (typeof (this._introItems) !== 'undefined') { 393 _nextStep.call(this); 394 } 395 } 396 397 /**
398 * Go to next step on intro 399 * 400 * @api private 401 * @method _nextStep 402 */ 403 function _nextStep() { 404 this._direction = 'forward'; 405 406 if (typeof (this._currentStepNumber) !== 'undefined') { 407 _forEach(this._introItems, function (item, i) { 408 if( item.step === this._currentStepNumber ) { 409 this._currentStep = i - 1; 410 this._currentStepNumber = undefined; 411 } 412 }.bind(this)); 413 } 414 415 if (typeof (this._currentStep) === 'undefined') { 416 this._currentStep = 0; 417 } else { 418 ++this._currentStep; 419 } 420 421 var nextStep = this._introItems[this._currentStep]; 422 var continueStep = true; 423 424 if (typeof (this._introBeforeChangeCallback) !== 'undefined') { 425 continueStep = this._introBeforeChangeCallback.call(this, nextStep.element); 426 } 427 428 // if `onbeforechange` returned `false`, stop displaying the element 429 if (continueStep === false) { 430 --this._currentStep; 431 return false; 432 } 433 434 if ((this._introItems.length) <= this._currentStep) { 435 //end of the intro 436 //check if any callback is defined 437 if (typeof (this._introCompleteCallback) === 'function') { 438 this._introCompleteCallback.call(this); 439 } 440 _exitIntro.call(this, this._targetElement); 441 return; 442 } 443 444 _showElement.call(this, nextStep); 445 } 446 447 /** 448 * Go to previous step on intro 449 * 450 * @api private 451 * @method _previousStep 452 */ 453 function _previousStep() { 454 this._direction = 'backward'; 455 456 if (this._currentStep === 0) { 457 return false; 458 } 459 460 --this._currentStep; 461 462 var nextStep = this._introItems[this._currentStep]; 463 var continueStep = true; 464 465 if (typeof (this._introBeforeChangeCallback) !== 'undefined') { 466 continueStep = this._introBeforeChangeCallback.call(this, nextStep.element); 467 } 468 469 // if `onbeforechange` returned `false`, stop displaying the element 470 if (continueStep === false) { 471 ++this._currentStep; 472 return false; 473 } 474 475 _showElement.call(this, nextStep); 476 } 477 478 /** 479 * Update placement of the intro objects on the screen 480 * @api private 481 */ 482 function _refresh() { 483 // re-align intros 484 _setHelperLayerPosition.call(this, document.querySelector('.introjs-helperLayer')); 485 _setHelperLayerPosition.call(this, document.querySelector('.introjs-tooltipReferenceLayer')); 486 _setHelperLayerPosition.call(this, document.querySelector('.introjs-disableInteraction')); 487 488 // re-align tooltip 489 if(this._currentStep !== undefined && this._currentStep !== null) { 490 var oldHelperNumberLayer = document.querySelector('.introjs-helperNumberLayer'), 491 oldArrowLayer = document.querySelector('.introjs-arrow'), 492 oldtooltipContainer = document.querySelector('.introjs-tooltip'); 493 _placeTooltip.call(this, this._introItems[this._currentStep].element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer); 494 } 495 496 //re-align hints 497 _reAlignHints.call(this); 498 return this; 499 } 500 501 /** 502 * Exit from intro 503 * 504 * @api private 505 * @method _exitIntro 506 * @param {Object} targetElement 507 * @param {Boolean} force - Setting to `true` will skip the result of beforeExit callback 508 */ 509 function _exitIntro(targetElement, force) { 510 var continueExit = true; 511 512 // calling onbeforeexit callback 513 // 514 // If this callback return `false`, it would halt the process 515 if (this._introBeforeExitCallback !== undefined) { 516 continueExit = this._introBeforeExitCallback.call(this); 517 } 518 519 // skip this check if `force` parameter is `true` 520 // otherwise, if `onbeforeexit` returned `false`, don't exit the intro 521 if (!force && continueExit === false) return; 522 523 //remove overlay layers from the page 524 var overlayLayers = targetElement.querySelectorAll('.introjs-overlay'); 525 526 if (overlayLayers && overlayLayers.length) { 527 _forEach(overlayLayers, function (overlayLayer) { 528 overlayLayer.style.opacity = 0; 529 window.setTimeout(function () { 530 if (this.parentNode) { 531 this.parentNode.removeChild(this); 532 } 533 }.bind(overlayLayer), 500); 534 }.bind(this)); 535 } 536 537 //remove all helper layers 538 var helperLayer = targetElement.querySelector('.introjs-helperLayer');
539 if (helperLayer) { 540 helperLayer.parentNode.removeChild(helperLayer); 541 } 542 543 var referenceLayer = targetElement.querySelector('.introjs-tooltipReferenceLayer'); 544 if (referenceLayer) { 545 referenceLayer.parentNode.removeChild(referenceLayer); 546 } 547 548 //remove disableInteractionLayer 549 var disableInteractionLayer = targetElement.querySelector('.introjs-disableInteraction'); 550 if (disableInteractionLayer) { 551 disableInteractionLayer.parentNode.removeChild(disableInteractionLayer); 552 } 553 554 //remove intro floating element 555 var floatingElement = document.querySelector('.introjsFloatingElement'); 556 if (floatingElement) { 557 floatingElement.parentNode.removeChild(floatingElement); 558 } 559 560 _removeShowElement(); 561 562 //remove `introjs-fixParent` class from the elements 563 var fixParents = document.querySelectorAll('.introjs-fixParent'); 564 _forEach(fixParents, function (parent) { 565 _removeClass(parent, /introjs-fixParent/g); 566 }); 567 568 //clean listeners 569 DOMEvent.off(window, 'keydown', _onKeyDown, this, true); 570 DOMEvent.off(window, 'resize', _onResize, this, true); 571 572 //check if any callback is defined 573 if (this._introExitCallback !== undefined) { 574 this._introExitCallback.call(this); 575 } 576 577 //set the step to zero 578 this._currentStep = undefined; 579 } 580 581 /** 582 * Render tooltip box in the page 583 * 584 * @api private 585 * @method _placeTooltip 586 * @param {HTMLElement} targetElement 587 * @param {HTMLElement} tooltipLayer 588 * @param {HTMLElement} arrowLayer 589 * @param {HTMLElement} helperNumberLayer 590 * @param {Boolean} hintMode 591 */ 592 function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer, hintMode) { 593 var tooltipCssClass = '', 594 currentStepObj, 595 tooltipOffset, 596 targetOffset, 597 windowSize, 598 currentTooltipPosition; 599 600 hintMode = hintMode || false; 601 602 //reset the old style 603 tooltipLayer.style.top = null; 604 tooltipLayer.style.right = null; 605 tooltipLayer.style.bottom = null; 606 tooltipLayer.style.left = null; 607 tooltipLayer.style.marginLeft = null; 608 tooltipLayer.style.marginTop = null; 609 610 arrowLayer.style.display = 'inherit'; 611 612 if (typeof(helperNumberLayer) !== 'undefined' && helperNumberLayer !== null) { 613 helperNumberLayer.style.top = null; 614 helperNumberLayer.style.left = null; 615 } 616 617 //prevent error when `this._currentStep` is undefined 618 if (!this._introItems[this._currentStep]) return; 619 620 //if we have a custom css class for each step 621 currentStepObj = this._introItems[this._currentStep]; 622 if (typeof (currentStepObj.tooltipClass) === 'string') { 623 tooltipCssClass = currentStepObj.tooltipClass; 624 } else { 625 tooltipCssClass = this._options.tooltipClass; 626 } 627 628 tooltipLayer.className = ('introjs-tooltip ' + tooltipCssClass).replace(/^\s+|\s+$/g, ''); 629 tooltipLayer.setAttribute('role', 'dialog'); 630 631 currentTooltipPosition = this._introItems[this._currentStep].position; 632 633 // Floating is always valid, no point in calculating 634 if (currentTooltipPosition !== "floating") { 635 currentTooltipPosition = _determineAutoPosition.call(this, targetElement, tooltipLayer, currentTooltipPosition); 636 } 637 638 var tooltipLayerStyleLeft; 639 targetOffset = _getOffset(targetElement); 640 tooltipOffset = _getOffset(tooltipLayer); 641 windowSize = _getWinSize(); 642 643 _addClass(tooltipLayer, 'introjs-' + currentTooltipPosition); 644 645 switch (currentTooltipPosition) { 646 case 'top-right-aligned': 647 arrowLayer.className = 'introjs-arrow bottom-right'; 648 649 var tooltipLayerStyleRight = 0; 650 _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer); 651 tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px'; 652 break; 653 654 case 'top-middle-aligned': 655 arrowLayer.className = 'introjs-arrow bottom-middle'; 656 657 var tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2; 658 659 // a fix for middle aligned hints 660 if (hintMode) { 661 tooltipLayerStyleLeftRight += 5; 662 } 663 664 if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) { 665 tooltipLayer.style.right = null; 666 _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer); 667 } 668 tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px'; 669 break; 670 671 case 'top-left-aligned': 672 // top-left-aligned is the same as the default top 673 case 'top': 674 arrowLayer.className = 'introjs-arrow bottom'; 675 676 tooltipLayerStyleLeft = (hintMode) ? 0 : 15; 677 678 _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer); 679 tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px'; 680 break; 681 case 'right': 682 tooltipLayer.style.left = (targetOffset.width + 20) + 'px'; 683 if (targetOffset.top + tooltipOffset.height > windowSize.height) { 684 // In this case, right would have fallen below the bottom of the screen. 685 // Modify so that the bottom of the tooltip connects with the target 686 arrowLayer.className = "introjs-arrow left-bottom"; 687 tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px"; 688 } else { 689 arrowLayer.className = 'introjs-arrow left'; 690 } 691 break; 692 case 'left': 693 if (!hintMode && this._options.showStepNumbers === true) { 694 tooltipLayer.style.top = '15px'; 695 } 696 697 if (targetOffset.top + tooltipOffset.height > windowSize.height) { 698 // In this case, left would have fallen below the bottom of the screen. 699 // Modify so that the bottom of the tooltip connects with the target 700 tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px"; 701 arrowLayer.className = 'introjs-arrow right-bottom'; 702 } else { 703 arrowLayer.className = 'introjs-arrow right'; 704 } 705 tooltipLayer.style.right = (targetOffset.width + 20) + 'px'; 706 707 break; 708 case 'floating': 709 arrowLayer.style.display = 'none'; 710 711 //we have to adjust the top and left of layer manually for intro items without element 712 tooltipLayer.style.left = '50%'; 713 tooltipLayer.style.top = '50%'; 714 tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2) + 'px'; 715 tooltipLayer.style.marginTop = '-' + (tooltipOffset.height / 2) + 'px'; 716 717 if (typeof(helperNumberLayer) !== 'undefined' && helperNumberLayer !== null) { 718 helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px'; 719 helperNumberLayer.style.top = '-' + ((tooltipOffset.height / 2) + 18) + 'px'; 720 } 721 722 break; 723 case 'bottom-right-aligned': 724 arrowLayer.className = 'introjs-arrow top-right'; 725 726 tooltipLayerStyleRight = 0; 727 _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer); 728 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 729 break; 730 731 case 'bottom-middle-aligned': 732 arrowLayer.className = 'introjs-arrow top-middle'; 733 734 tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2; 735 736 // a fix for middle aligned hints 737 if (hintMode) { 738 tooltipLayerStyleLeftRight += 5; 739 } 740 741 if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) { 742 tooltipLayer.style.right = null; 743 _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer); 744 } 745 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 746 break; 747 748 // case 'bottom-left-aligned': 749 // Bottom-left-aligned is the same as the default bottom 750 // case 'bottom': 751 // Bottom going to follow the default behavior 752 default: 753 arrowLayer.className = 'introjs-arrow top'; 754 755 tooltipLayerStyleLeft = 0; 756 _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer); 757 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 758 } 759 } 760 761 /** 762 * Set tooltip left so it doesn't go off the right side of the window 763 * 764 * @return boolean true, if tooltipLayerStyleLeft is ok. false, otherwise. 765 */ 766 function _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer) { 767 if (targetOffset.left + tooltipLayerStyleLeft + tooltipOffset.width > windowSize.width) { 768 // off the right side of the window 769 tooltipLayer.style.left = (windowSize.width - tooltipOffset.width - targetOffset.left) + 'px'; 770 return false; 771 } 772 tooltipLayer.style.left = tooltipLayerStyleLeft + 'px'; 773 return true; 774 } 775 776 /** 777 * Set tooltip right so it doesn't go off the left side of the window 778 * 779 * @return boolean true, if tooltipLayerStyleRight is ok. false, otherwise. 780 */ 781 function _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer) { 782 if (targetOffset.left + targetOffset.width - tooltipLayerStyleRight - tooltipOffset.width < 0) { 783 // off the left side of the window 784 tooltipLayer.style.left = (-targetOffset.left) + 'px'; 785 return false; 786 } 787 tooltipLayer.style.right = tooltipLayerStyleRight + 'px'; 788 return true; 789 } 790 791 /** 792 * Determines the position of the tooltip based on the position precedence and availability 793 * of screen space. 794 * 795 * @param {Object} targetElement 796 * @param {Object} tooltipLayer 797 * @param {String} desiredTooltipPosition 798 * @return {String} calculatedPosition 799 */ 800 function _determineAutoPosition(targetElement, tooltipLayer, desiredTooltipPosition) { 801
802 // Take a clone of position precedence. These will be the available 803 var possiblePositions = this._options.positionPrecedence.slice(); 804 805 var windowSize = _getWinSize(); 806 var tooltipHeight = _getOffset(tooltipLayer).height + 10; 807 var tooltipWidth = _getOffset(tooltipLayer).width + 20; 808 var targetElementRect = targetElement.getBoundingClientRect(); 809 810 // If we check all the possible areas, and there are no valid places for the tooltip, the element 811 // must take up most of the screen real estate. Show the tooltip floating in the middle of the screen. 812 var calculatedPosition = "floating"; 813 814 /* 815 * auto determine position 816 */ 817 818 // Check for space below 819 if (targetElementRect.bottom + tooltipHeight + tooltipHeight > windowSize.height) { 820 _removeEntry(possiblePositions, "bottom"); 821 } 822 823 // Check for space above 824 if (targetElementRect.top - tooltipHeight < 0) { 825 _removeEntry(possiblePositions, "top"); 826 } 827 828 // Check for space to the right 829 if (targetElementRect.right + tooltipWidth > windowSize.width) { 830 _removeEntry(possiblePositions, "right"); 831 } 832 833 // Check for space to the left 834 if (targetElementRect.left - tooltipWidth < 0) { 835 _removeEntry(possiblePositions, "left"); 836 } 837 838 // @var {String} ex: 'right-aligned' 839 var desiredAlignment = (function (pos) { 840 var hyphenIndex = pos.indexOf('-'); 841 if (hyphenIndex !== -1) { 842 // has alignment 843 return pos.substr(hyphenIndex); 844 } 845 return ''; 846 })(desiredTooltipPosition || ''); 847 848 // strip alignment from position 849 if (desiredTooltipPosition) { 850 // ex: "bottom-right-aligned" 851 // should return 'bottom' 852 desiredTooltipPosition = desiredTooltipPosition.split('-')[0]; 853 } 854 855 if (possiblePositions.length) { 856 if (desiredTooltipPosition !== "auto" && 857 possiblePositions.indexOf(desiredTooltipPosition) > -1) { 858 // If the requested position is in the list, choose that 859 calculatedPosition = desiredTooltipPosition; 860 } else { 861 // Pick the first valid position, in order 862 calculatedPosition = possiblePositions[0]; 863 } 864 } 865 866 // only top and bottom positions have optional alignments 867 if (['top', 'bottom'].indexOf(calculatedPosition) !== -1) { 868 calculatedPosition += _determineAutoAlignment(targetElementRect.left, tooltipWidth, windowSize, desiredAlignment); 869 } 870 871 return calculatedPosition; 872 } 873 874 /** 875 * auto-determine alignment 876 * @param {Integer} offsetLeft 877 * @param {Integer} tooltipWidth 878 * @param {Object} windowSize 879 * @param {String} desiredAlignment 880 * @return {String} calculatedAlignment 881 */ 882 function _determineAutoAlignment (offsetLeft, tooltipWidth, windowSize, desiredAlignment) { 883 var halfTooltipWidth = tooltipWidth / 2, 884 winWidth = Math.min(windowSize.width, window.screen.width), 885 possibleAlignments = ['-left-aligned', '-middle-aligned', '-right-aligned'], 886 calculatedAlignment = ''; 887 888 // valid left must be at least a tooltipWidth 889 // away from right side 890 if (winWidth - offsetLeft < tooltipWidth) { 891 _removeEntry(possibleAlignments, '-left-aligned'); 892 } 893 894 // valid middle must be at least half 895 // width away from both sides 896 if (offsetLeft < halfTooltipWidth || 897 winWidth - offsetLeft < halfTooltipWidth) { 898 _removeEntry(possibleAlignments, '-middle-aligned'); 899 } 900 901 // valid right must be at least a tooltipWidth 902 // width away from left side 903 if (offsetLeft < tooltipWidth) { 904 _removeEntry(possibleAlignments, '-right-aligned'); 905 } 906 907 if (possibleAlignments.length) { 908 if (possibleAlignments.indexOf(desiredAlignment) !== -1) { 909 // the desired alignment is valid 910 calculatedAlignment = desiredAlignment; 911 } else { 912 // pick the first valid position, in order 913 calculatedAlignment = possibleAlignments[0]; 914 } 915 } else { 916 // if screen width is too small 917 // for ANY alignment, middle is 918 // probably the best for visibility 919 calculatedAlignment = '-middle-aligned'; 920 } 921 922 return calculatedAlignment; 923 } 924 925 /** 926 * Remove an entry from a string array if it's there, does nothing if it isn't there. 927 * 928 * @param {Array} stringArray 929 * @param {String} stringToRemove 930 */ 931 function _removeEntry(stringArray, stringToRemove) { 932 if (stringArray.indexOf(stringToRemove) > -1) {
933 stringArray.splice(stringArray.indexOf(stringToRemove), 1); 934 } 935 } 936 937 /** 938 * Update the position of the helper layer on the screen 939 * 940 * @api private 941 * @method _setHelperLayerPosition 942 * @param {Object} helperLayer 943 */ 944 function _setHelperLayerPosition(helperLayer) { 945 if (helperLayer) { 946 //prevent error when `this._currentStep` in undefined 947 if (!this._introItems[this._currentStep]) return; 948 949 var currentElement = this._introItems[this._currentStep], 950 elementPosition = _getOffset(currentElement.element), 951 widthHeightPadding = this._options.helperElementPadding; 952 953 // If the target element is fixed, the tooltip should be fixed as well. 954 // Otherwise, remove a fixed class that may be left over from the previous 955 // step. 956 if (_isFixed(currentElement.element)) { 957 _addClass(helperLayer, 'introjs-fixedTooltip'); 958 } else { 959 _removeClass(helperLayer, 'introjs-fixedTooltip'); 960 } 961 962 if (currentElement.position === 'floating') { 963 widthHeightPadding = 0; 964 } 965 966 //set new position to helper layer 967 helperLayer.style.cssText = 'width: ' + (elementPosition.width + widthHeightPadding) + 'px; ' + 968 'height:' + (elementPosition.height + widthHeightPadding) + 'px; ' + 969 'top:' + (elementPosition.top - widthHeightPadding / 2) + 'px;' + 970 'left: ' + (elementPosition.left - widthHeightPadding / 2) + 'px;'; 971 972 } 973 } 974 975 /** 976 * Add disableinteraction layer and adjust the size and position of the layer 977 * 978 * @api private 979 * @method _disableInteraction 980 */ 981 function _disableInteraction() { 982 var disableInteractionLayer = document.querySelector('.introjs-disableInteraction'); 983 984 if (disableInteractionLayer === null) { 985 disableInteractionLayer = document.createElement('div'); 986 disableInteractionLayer.className = 'introjs-disableInteraction'; 987 this._targetElement.appendChild(disableInteractionLayer); 988 } 989 990 _setHelperLayerPosition.call(this, disableInteractionLayer); 991 } 992 993 /** 994 * Setting anchors to behave like buttons 995 * 996 * @api private 997 * @method _setAnchorAsButton 998 */ 999 function _setAnchorAsButton(anchor){ 1000 anchor.setAttribute('role', 'button'); 1001 anchor.tabIndex = 0; 1002 } 1003 1004 /** 1005 * Show an element on the page 1006 * 1007 * @api private 1008 * @method _showElement 1009 * @param {Object} targetElement 1010 */ 1011 function _showElement(targetElement) { 1012 if (typeof (this._introChangeCallback) !== 'undefined') { 1013 this._introChangeCallback.call(this, targetElement.element); 1014 } 1015 1016 var self = this, 1017 oldHelperLayer = document.querySelector('.introjs-helperLayer'), 1018 oldReferenceLayer = document.querySelector('.introjs-tooltipReferenceLayer'), 1019 highlightClass = 'introjs-helperLayer', 1020 nextTooltipButton, 1021 prevTooltipButton, 1022 skipTooltipButton, 1023 scrollParent; 1024 1025 //check for a current step highlight class 1026 if (typeof (targetElement.highlightClass) === 'string') { 1027 highlightClass += (' ' + targetElement.highlightClass); 1028 } 1029 //check for options highlight class 1030 if (typeof (this._options.highlightClass) === 'string') { 1031 highlightClass += (' ' + this._options.highlightClass); 1032 } 1033 1034 if (oldHelperLayer !== null) { 1035 var oldHelperNumberLayer = oldReferenceLayer.querySelector('.introjs-helperNumberLayer'), 1036 oldtooltipLayer = oldReferenceLayer.querySelector('.introjs-tooltiptext'), 1037 oldArrowLayer = oldReferenceLayer.querySelector('.introjs-arrow'), 1038 oldtooltipContainer = oldReferenceLayer.querySelector('.introjs-tooltip'); 1039 1040 skipTooltipButton = oldReferenceLayer.querySelector('.introjs-skipbutton'); 1041 prevTooltipButton = oldReferenceLayer.querySelector('.introjs-prevbutton'); 1042 nextTooltipButton = oldReferenceLayer.querySelector('.introjs-nextbutton'); 1043 1044 //update or reset the helper highlight class 1045 oldHelperLayer.className = highlightClass; 1046 //hide the tooltip 1047 oldtooltipContainer.style.opacity = 0; 1048 oldtooltipContainer.style.display = "none"; 1049
1050 if (oldHelperNumberLayer !== null) { 1051 var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)]; 1052 1053 if (lastIntroItem !== null && (this._direction === 'forward' && lastIntroItem.position === 'floating') || (this._direction === 'backward' && targetElement.position === 'floating')) { 1054 oldHelperNumberLayer.style.opacity = 0; 1055 } 1056 } 1057 1058 // scroll to element 1059 scrollParent = _getScrollParent( targetElement.element ); 1060 1061 if (scrollParent !== document.body) { 1062 // target is within a scrollable element 1063 _scrollParentToElement(scrollParent, targetElement.element); 1064 } 1065 1066 // set new position to helper layer 1067 _setHelperLayerPosition.call(self, oldHelperLayer); 1068 _setHelperLayerPosition.call(self, oldReferenceLayer); 1069 1070 //remove `introjs-fixParent` class from the elements 1071 var fixParents = document.querySelectorAll('.introjs-fixParent'); 1072 _forEach(fixParents, function (parent) { 1073 _removeClass(parent, /introjs-fixParent/g); 1074 }); 1075 1076 //remove old classes if the element still exist 1077 _removeShowElement(); 1078 1079 //we should wait until the CSS3 transition is competed (it's 0.3 sec) to prevent incorrect `height` and `width` calculation 1080 if (self._lastShowElementTimer) { 1081 window.clearTimeout(self._lastShowElementTimer); 1082 } 1083 1084 self._lastShowElementTimer = window.setTimeout(function() { 1085 //set current step to the label 1086 if (oldHelperNumberLayer !== null) { 1087 oldHelperNumberLayer.innerHTML = targetElement.step; 1088 } 1089 //set current tooltip text 1090 oldtooltipLayer.innerHTML = targetElement.intro; 1091 //set the tooltip position 1092 oldtooltipContainer.style.display = "block"; 1093 _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer); 1094 1095 //change active bullet 1096 if (self._options.showBullets) { 1097 oldReferenceLayer.querySelector('.introjs-bullets li > a.active').className = ''; 1098 oldReferenceLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active'; 1099 } 1100 oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').style.cssText = 'width:' + _getProgress.call(self) + '%;'; 1101 oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').setAttribute('aria-valuenow', _getProgress.call(self)); 1102 1103 //show the tooltip 1104 oldtooltipContainer.style.opacity = 1; 1105 if (oldHelperNumberLayer) oldHelperNumberLayer.style.opacity = 1; 1106 1107 //reset button focus 1108 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null && /introjs-donebutton/gi.test(skipTooltipButton.className)) { 1109 // skip button is now "done" button 1110 skipTooltipButton.focus(); 1111 } else if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1112 //still in the tour, focus on next 1113 nextTooltipButton.focus(); 1114 } 1115 1116 // change the scroll of the window, if needed 1117 _scrollTo.call(self, targetElement.scrollTo, targetElement, oldtooltipLayer); 1118 }, 350); 1119 1120 // end of old element if-else condition 1121 } else { 1122 var helperLayer = document.createElement('div'), 1123 referenceLayer = document.createElement('div'), 1124 arrowLayer = document.createElement('div'), 1125 tooltipLayer = document.createElement('div'), 1126 tooltipTextLayer = document.createElement('div'), 1127 bulletsLayer = document.createElement('div'), 1128 progressLayer = document.createElement('div'), 1129 buttonsLayer = document.createElement('div'); 1130 1131 helperLayer.className = highlightClass; 1132 referenceLayer.className = 'introjs-tooltipReferenceLayer'; 1133 1134 // scroll to element
1135 scrollParent = _getScrollParent( targetElement.element ); 1136 1137 if (scrollParent !== document.body) { 1138 // target is within a scrollable element 1139 _scrollParentToElement(scrollParent, targetElement.element); 1140 } 1141 1142 //set new position to helper layer 1143 _setHelperLayerPosition.call(self, helperLayer); 1144 _setHelperLayerPosition.call(self, referenceLayer); 1145 1146 //add helper layer to target element 1147 this._targetElement.appendChild(helperLayer); 1148 this._targetElement.appendChild(referenceLayer); 1149 1150 arrowLayer.className = 'introjs-arrow'; 1151 1152 tooltipTextLayer.className = 'introjs-tooltiptext'; 1153 tooltipTextLayer.innerHTML = targetElement.intro; 1154 1155 bulletsLayer.className = 'introjs-bullets'; 1156 1157 if (this._options.showBullets === false) { 1158 bulletsLayer.style.display = 'none'; 1159 } 1160 1161 var ulContainer = document.createElement('ul'); 1162 ulContainer.setAttribute('role', 'tablist'); 1163 1164 var anchorClick = function () { 1165 self.goToStep(this.getAttribute('data-stepnumber')); 1166 }; 1167 1168 _forEach(this._introItems, function (item, i) { 1169 var innerLi = document.createElement('li'); 1170 var anchorLink = document.createElement('a'); 1171 1172 innerLi.setAttribute('role', 'presentation'); 1173 anchorLink.setAttribute('role', 'tab'); 1174 1175 anchorLink.onclick = anchorClick; 1176 1177 if (i === (targetElement.step-1)) { 1178 anchorLink.className = 'active'; 1179 } 1180 1181 _setAnchorAsButton(anchorLink); 1182 anchorLink.innerHTML = " "; 1183 anchorLink.setAttribute('data-stepnumber', item.step); 1184 1185 innerLi.appendChild(anchorLink); 1186 ulContainer.appendChild(innerLi); 1187 }); 1188 1189 bulletsLayer.appendChild(ulContainer); 1190 1191 progressLayer.className = 'introjs-progress'; 1192 1193 if (this._options.showProgress === false) { 1194 progressLayer.style.display = 'none'; 1195 } 1196 var progressBar = document.createElement('div'); 1197 progressBar.className = 'introjs-progressbar'; 1198 progressBar.setAttribute('role', 'progress'); 1199 progressBar.setAttribute('aria-valuemin', 0); 1200 progressBar.setAttribute('aria-valuemax', 100); 1201 progressBar.setAttribute('aria-valuenow', _getProgress.call(this)); 1202 progressBar.style.cssText = 'width:' + _getProgress.call(this) + '%;'; 1203 1204 progressLayer.appendChild(progressBar); 1205 1206 buttonsLayer.className = 'introjs-tooltipbuttons'; 1207 if (this._options.showButtons === false) { 1208 buttonsLayer.style.display = 'none'; 1209 } 1210 1211 tooltipLayer.className = 'introjs-tooltip'; 1212 tooltipLayer.appendChild(tooltipTextLayer); 1213 tooltipLayer.appendChild(bulletsLayer); 1214 tooltipLayer.appendChild(progressLayer); 1215 1216 //add helper layer number 1217 var helperNumberLayer = document.createElement('span'); 1218 if (this._options.showStepNumbers === true) { 1219 helperNumberLayer.className = 'introjs-helperNumberLayer'; 1220 helperNumberLayer.innerHTML = targetElement.step; 1221 referenceLayer.appendChild(helperNumberLayer); 1222 } 1223 1224 tooltipLayer.appendChild(arrowLayer); 1225 referenceLayer.appendChild(tooltipLayer); 1226 1227 //next button 1228 nextTooltipButton = document.createElement('a'); 1229 1230 nextTooltipButton.onclick = function() { 1231 if (self._introItems.length - 1 !== self._currentStep) { 1232 _nextStep.call(self); 1233 } 1234 }; 1235 1236 _setAnchorAsButton(nextTooltipButton); 1237 nextTooltipButton.innerHTML = this._options.nextLabel; 1238 1239 //previous button 1240 prevTooltipButton = document.createElement('a'); 1241 1242 prevTooltipButton.onclick = function() { 1243 if (self._currentStep !== 0) { 1244 _previousStep.call(self); 1245 } 1246 }; 1247 1248 _setAnchorAsButton(prevTooltipButton); 1249 prevTooltipButton.innerHTML = this._options.prevLabel; 1250 1251 //skip button 1252 skipTooltipButton = document.createElement('a'); 1253 skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton '; 1254 _setAnchorAsButton(skipTooltipButton); 1255 skipTooltipButton.innerHTML = this._options.skipLabel; 1256 1257 skipTooltipButton.onclick = function() { 1258 if (self._introItems.length - 1 === self._currentStep && typeof (self._introCompleteCallback) === 'function') { 1259 self._introCompleteCallback.call(self); 1260 } 1261 1262 if (self._introItems.length - 1 !== self._currentStep && typeof (self._introExitCallback) === 'function') { 1263 self._introExitCallback.call(self); 1264 } 1265 1266 if (typeof(self._introSkipCallback) === 'function') { 1267 self._introSkipCallback.call(self); 1268 } 1269 1270 _exitIntro.call(self, self._targetElement); 1271 }; 1272 1273 buttonsLayer.appendChild(skipTooltipButton); 1274 1275 //in order to prevent displaying next/previous button always 1276 if (this._introItems.length > 1) { 1277 buttonsLayer.appendChild(prevTooltipButton); 1278 buttonsLayer.appendChild(nextTooltipButton); 1279 } 1280 1281 tooltipLayer.appendChild(buttonsLayer); 1282 1283 //set proper position 1284 _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer); 1285 1286 // change the scroll of the window, if needed 1287 _scrollTo.call(this, targetElement.scrollTo, targetElement, tooltipLayer); 1288 1289 //end of new element if-else condition 1290 } 1291 1292 // removing previous disable interaction layer 1293 var disableInteractionLayer = self._targetElement.querySelector('.introjs-disableInteraction'); 1294 if (disableInteractionLayer) { 1295 disableInteractionLayer.parentNode.removeChild(disableInteractionLayer); 1296 } 1297 1298 //disable interaction 1299 if (targetElement.disableInteraction) { 1300 _disableInteraction.call(self); 1301 } 1302 1303 // when it's the first step of tour 1304 if (this._currentStep === 0 && this._introItems.length > 1) { 1305 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) { 1306 skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton'; 1307 } 1308 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1309 nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton'; 1310 } 1311 1312 if (this._options.hidePrev === true) { 1313 if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) { 1314 prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton introjs-hidden'; 1315 } 1316 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1317 _addClass(nextTooltipButton, 'introjs-fullbutton'); 1318 } 1319 } else { 1320 if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) { 1321 prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton introjs-disabled'; 1322 } 1323 } 1324 1325 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) { 1326 skipTooltipButton.innerHTML = this._options.skipLabel; 1327 } 1328 } else if (this._introItems.length - 1 === this._currentStep || this._introItems.length === 1) { 1329 // last step of tour 1330 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) { 1331 skipTooltipButton.innerHTML = this._options.doneLabel; 1332 // adding donebutton class in addition to skipbutton 1333 _addClass(skipTooltipButton, 'introjs-donebutton'); 1334 } 1335 if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) { 1336 prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton'; 1337 } 1338 1339 if (this._options.hideNext === true) { 1340 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1341 nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton introjs-hidden'; 1342 } 1343 if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) { 1344 _addClass(prevTooltipButton, 'introjs-fullbutton'); 1345 } 1346 } else { 1347 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1348 nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton introjs-disabled'; 1349 } 1350 } 1351 } else { 1352 // steps between start and end 1353 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) { 1354 skipTooltipButton.className = this._options.buttonClass + ' introjs-skipbutton'; 1355 } 1356 if (typeof prevTooltipButton !== "undefined" && prevTooltipButton !== null) { 1357 prevTooltipButton.className = this._options.buttonClass + ' introjs-prevbutton'; 1358 } 1359 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1360 nextTooltipButton.className = this._options.buttonClass + ' introjs-nextbutton'; 1361 } 1362 if (typeof skipTooltipButton !== "undefined" && skipTooltipButton !== null) { 1363 skipTooltipButton.innerHTML = this._options.skipLabel; 1364 } 1365 } 1366 1367 prevTooltipButton.setAttribute('role', 'button'); 1368 nextTooltipButton.setAttribute('role', 'button'); 1369 skipTooltipButton.setAttribute('role', 'button'); 1370 1371 //Set focus on "next" button, so that hitting Enter always moves you onto the next step 1372 if (typeof nextTooltipButton !== "undefined" && nextTooltipButton !== null) { 1373 nextTooltipButton.focus(); 1374 } 1375 1376 _setShowElement(targetElement); 1377 1378 if (typeof (this._introAfterChangeCallback) !== 'undefined') { 1379 this._introAfterChangeCallback.call(this, targetElement.element); 1380 } 1381 } 1382 1383 /** 1384 * To change the scroll of `window` after highlighting an element 1385 * 1386 * @api private 1387 * @method _scrollTo 1388 * @param {String} scrollTo 1389 * @param {Object} targetElement 1390 * @param {Object} tooltipLayer 1391 */ 1392 function _scrollTo(scrollTo, targetElement, tooltipLayer) { 1393 if (scrollTo === 'off') return; 1394 var rect; 1395 1396 if (!this._options.scrollToElement) return; 1397 1398 if (scrollTo === 'tooltip') { 1399 rect = tooltipLayer.getBoundingClientRect(); 1400 } else { 1401 rect = targetElement.element.getBoundingClientRect(); 1402 } 1403 1404 if (!_elementInViewport(targetElement.element)) { 1405 var winHeight = _getWinSize().height; 1406 var top = rect.bottom - (rect.bottom - rect.top); 1407 1408 // TODO (afshinm): do we need scroll padding now? 1409 // I have changed the scroll option and now it scrolls the window to 1410 // the center of the target element or tooltip. 1411 1412 if (top < 0 || targetElement.element.clientHeight > winHeight) { 1413 window.scrollBy(0, rect.top - ((winHeight / 2) - (rect.height / 2)) - this._options.scrollPadding); // 30px padding from edge to look nice 1414 1415 //Scroll down 1416 } else { 1417 window.scrollBy(0, rect.top - ((winHeight / 2) - (rect.height / 2)) + this._options.scrollPadding); // 30px padding from edge to look nice 1418 } 1419 } 1420 } 1421 1422 /** 1423 * To remove all show element(s) 1424 * 1425 * @api private 1426 * @method _removeShowElement 1427 */ 1428 function _removeShowElement() { 1429 var elms = document.querySelectorAll('.introjs-showElement'); 1430
1431 _forEach(elms, function (elm) { 1432 _removeClass(elm, /introjs-[a-zA-Z]+/g); 1433 }); 1434 } 1435 1436 /** 1437 * To set the show element 1438 * This function set a relative (in most cases) position and changes the z-index 1439 * 1440 * @api private 1441 * @method _setShowElement 1442 * @param {Object} targetElement 1443 */ 1444 function _setShowElement(targetElement) { 1445 var parentElm; 1446 // we need to add this show element class to the parent of SVG elements 1447 // because the SVG elements can't have independent z-index 1448 if (targetElement.element instanceof SVGElement) { 1449 parentElm = targetElement.element.parentNode; 1450 1451 while (targetElement.element.parentNode !== null) { 1452 if (!parentElm.tagName || parentElm.tagName.toLowerCase() === 'body') break; 1453 1454 if (parentElm.tagName.toLowerCase() === 'svg') { 1455 _addClass(parentElm, 'introjs-showElement introjs-relativePosition'); 1456 } 1457 1458 parentElm = parentElm.parentNode; 1459 } 1460 } 1461 1462 _addClass(targetElement.element, 'introjs-showElement'); 1463 1464 var currentElementPosition = _getPropValue(targetElement.element, 'position'); 1465 if (currentElementPosition !== 'absolute' && 1466 currentElementPosition !== 'relative' && 1467 currentElementPosition !== 'fixed') { 1468 //change to new intro item 1469 _addClass(targetElement.element, 'introjs-relativePosition'); 1470 } 1471 1472 parentElm = targetElement.element.parentNode; 1473 while (parentElm !== null) { 1474 if (!parentElm.tagName || parentElm.tagName.toLowerCase() === 'body') break; 1475 1476 //fix The Stacking Context problem. 1477 //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context 1478 var zIndex = _getPropValue(parentElm, 'z-index'); 1479 var opacity = parseFloat(_getPropValue(parentElm, 'opacity')); 1480 var transform = _getPropValue(parentElm, 'transform') || _getPropValue(parentElm, '-webkit-transform') || _getPropValue(parentElm, '-moz-transform') || _getPropValue(parentElm, '-ms-transform') || _getPropValue(parentElm, '-o-transform'); 1481 if (/[0-9]+/.test(zIndex) || opacity < 1 || (transform !== 'none' && transform !== undefined)) { 1482 _addClass(parentElm, 'introjs-fixParent'); 1483 } 1484 1485 parentElm = parentElm.parentNode; 1486 } 1487 } 1488 1489 /** 1490 * Iterates arrays 1491 * 1492 * @param {Array} arr 1493 * @param {Function} forEachFnc 1494 * @param {Function} completeFnc 1495 * @return {Null} 1496 */ 1497 function _forEach(arr, forEachFnc, completeFnc) { 1498 // in case arr is an empty query selector node list 1499 if (arr) { 1500 for (var i = 0, len = arr.length; i < len; i++) { 1501 forEachFnc(arr[i], i); 1502 } 1503 } 1504 1505 if (typeof(completeFnc) === 'function') { 1506 completeFnc(); 1507 } 1508 } 1509 1510 /** 1511 * Mark any object with an incrementing number 1512 * used for keeping track of objects 1513 * 1514 * @param Object obj Any object or DOM Element 1515 * @param String key 1516 * @return Object 1517 */ 1518 var _stamp = (function () { 1519 var keys = {}; 1520 return function stamp (obj, key) { 1521 1522 // get group key 1523 key = key || 'introjs-stamp'; 1524 1525 // each group increments from 0 1526 keys[key] = keys[key] || 0; 1527 1528 // stamp only once per object 1529 if (obj[key] === undefined) { 1530 // increment key for each new object 1531 obj[key] = keys[key]++; 1532 } 1533 1534 return obj[key]; 1535 }; 1536 })(); 1537 1538 /** 1539 * DOMEvent Handles all DOM events 1540 * 1541 * methods: 1542 * 1543 * on - add event handler 1544 * off - remove event 1545 */ 1546 var DOMEvent = (function () { 1547 function DOMEvent () { 1548 var events_key = 'introjs_event'; 1549 1550 /** 1551 * Gets a unique ID for an event listener 1552 * 1553 * @param Object obj 1554 * @param String type event type 1555 * @param Function listener 1556 * @param Object context 1557 * @return String 1558 */ 1559 this._id = function (obj, type, listener, context) { 1560 return type + _stamp(listener) + (context ? '_' + _stamp(context) : ''); 1561 }; 1562 1563 /** 1564 * Adds event listener 1565 * 1566 * @param Object obj 1567 * @param String type event type 1568 * @param Function listener 1569 * @param Object context 1570 * @param Boolean useCapture 1571 * @return null 1572 */ 1573 this.on = function (obj, type, listener, context, useCapture) { 1574 var id = this._id.apply(this, arguments), 1575 handler = function (e) { 1576 return listener.call(context || obj, e || window.event); 1577 }; 1578 1579 if ('addEventListener' in obj) { 1580 obj.addEventListener(type, handler, useCapture); 1581 } else if ('attachEvent' in obj) { 1582 obj.attachEvent('on' + type, handler); 1583 } 1584 1585 obj[events_key] = obj[events_key] || {}; 1586 obj[events_key][id] = handler; 1587 }; 1588 1589 /** 1590 * Removes event listener 1591 * 1592 * @param Object obj 1593 * @param String type event type 1594 * @param Function listener 1595 * @param Object context 1596 * @param Boolean useCapture 1597 * @return null 1598 */ 1599 this.off = function (obj, type, listener, context, useCapture) { 1600 var id = this._id.apply(this, arguments), 1601 handler = obj[events_key] && obj[events_key][id]; 1602 1603 if (!handler) { 1604 return; 1605 } 1606 1607 if ('removeEventListener' in obj) { 1608 obj.removeEventListener(type, handler, useCapture); 1609 } else if ('detachEvent' in obj) { 1610 obj.detachEvent('on' + type, handler); 1611 } 1612 1613 obj[events_key][id] = null; 1614 }; 1615 } 1616 1617 return new DOMEvent(); 1618 })();
1619 1620 /** 1621 * Append a class to an element 1622 * 1623 * @api private 1624 * @method _addClass 1625 * @param {Object} element 1626 * @param {String} className 1627 * @returns null 1628 */ 1629 function _addClass(element, className) { 1630 if (element instanceof SVGElement) { 1631 // svg 1632 var pre = element.getAttribute('class') || ''; 1633 1634 element.setAttribute('class', pre + ' ' + className); 1635 } else { 1636 if (element.classList !== undefined) { 1637 // check for modern classList property 1638 var classes = className.split(' '); 1639 _forEach(classes, function (cls) { 1640 element.classList.add( cls ); 1641 }); 1642 } else if (!element.className.match( className )) { 1643 // check if element doesn't already have className 1644 element.className += ' ' + className; 1645 } 1646 } 1647 } 1648 1649 /** 1650 * Remove a class from an element 1651 * 1652 * @api private 1653 * @method _removeClass 1654 * @param {Object} element 1655 * @param {RegExp|String} classNameRegex can be regex or string 1656 * @returns null 1657 */ 1658 function _removeClass(element, classNameRegex) { 1659 if (element instanceof SVGElement) { 1660 var pre = element.getAttribute('class') || ''; 1661 1662 element.setAttribute('class', pre.replace(classNameRegex, '').replace(/^\s+|\s+$/g, '')); 1663 } else { 1664 element.className = element.className.replace(classNameRegex, '').replace(/^\s+|\s+$/g, ''); 1665 } 1666 } 1667 1668 /** 1669 * Get an element CSS property on the page 1670 * Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml 1671 * 1672 * @api private 1673 * @method _getPropValue 1674 * @param {Object} element 1675 * @param {String} propName 1676 * @returns Element's property value 1677 */ 1678 function _getPropValue (element, propName) { 1679 var propValue = ''; 1680 if (element.currentStyle) { //IE 1681 propValue = element.currentStyle[propName]; 1682 } else if (document.defaultView && document.defaultView.getComputedStyle) { //Others 1683 propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); 1684 } 1685 1686 //Prevent exception in IE 1687 if (propValue && propValue.toLowerCase) { 1688 return propValue.toLowerCase(); 1689 } else { 1690 return propValue; 1691 } 1692 } 1693 1694 /** 1695 * Checks to see if target element (or parents) position is fixed or not 1696 * 1697 * @api private 1698 * @method _isFixed 1699 * @param {Object} element 1700 * @returns Boolean 1701 */ 1702 function _isFixed (element) { 1703 var p = element.parentNode; 1704 1705 if (!p || p.nodeName === 'HTML') { 1706 return false; 1707 } 1708 1709 if (_getPropValue(element, 'position') === 'fixed') { 1710 return true; 1711 } 1712 1713 return _isFixed(p); 1714 } 1715 1716 /** 1717 * Provides a cross-browser way to get the screen dimensions 1718 * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight 1719 * 1720 * @api private 1721 * @method _getWinSize 1722 * @returns {Object} width and height attributes 1723 */ 1724 function _getWinSize() { 1725 if (window.innerWidth !== undefined) { 1726 return { width: window.innerWidth, height: window.innerHeight }; 1727 } else { 1728 var D = document.documentElement; 1729 return { width: D.clientWidth, height: D.clientHeight }; 1730 } 1731 } 1732 1733 /** 1734 * Check to see if the element is in the viewport or not 1735 * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport 1736 * 1737 * @api private 1738 * @method _elementInViewport 1739 * @param {Object} el 1740 */ 1741 function _elementInViewport(el) { 1742 var rect = el.getBoundingClientRect(); 1743 1744 return ( 1745 rect.top >= 0 && 1746 rect.left >= 0 && 1747 (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right 1748 rect.right <= window.innerWidth 1749 ); 1750 } 1751 1752 /** 1753 * Add overlay layer to the page 1754 * 1755 * @api private 1756 * @method _addOverlayLayer 1757 * @param {Object} targetElm 1758 */ 1759 function _addOverlayLayer(targetElm) { 1760 var overlayLayer = document.createElement('div'), 1761 styleText = '', 1762 self = this; 1763 1764 //set css class name 1765 overlayLayer.className = 'introjs-overlay'; 1766 1767 //check if the target element is body, we should calculate the size of overlay layer in a better way 1768 if (!targetElm.tagName || targetElm.tagName.toLowerCase() === 'body') { 1769 styleText += 'top: 0;bottom: 0; left: 0;right: 0;position: fixed;'; 1770 overlayLayer.style.cssText = styleText; 1771 } else { 1772 //set overlay layer position 1773 var elementPosition = _getOffset(targetElm); 1774 if (elementPosition) { 1775 styleText += 'width: ' + elementPosition.width + 'px; height:' + elementPosition.height + 'px;
1775 top:' + elementPosition.top + 'px;left: ' + elementPosition.left + 'px;'; 1776 overlayLayer.style.cssText = styleText; 1777 } 1778 } 1779 1780 targetElm.appendChild(overlayLayer); 1781 1782 overlayLayer.onclick = function() { 1783 if (self._options.exitOnOverlayClick === true) { 1784 _exitIntro.call(self, targetElm); 1785 } 1786 }; 1787 1788 window.setTimeout(function() { 1789 styleText += 'opacity: ' + self._options.overlayOpacity.toString() + ';'; 1790 overlayLayer.style.cssText = styleText; 1791 }, 10); 1792 1793 return true; 1794 } 1795 1796 /** 1797 * Removes open hint (tooltip hint) 1798 * 1799 * @api private 1800 * @method _removeHintTooltip 1801 */ 1802 function _removeHintTooltip() { 1803 var tooltip = document.querySelector('.introjs-hintReference'); 1804 1805 if (tooltip) { 1806 var step = tooltip.getAttribute('data-step'); 1807 tooltip.parentNode.removeChild(tooltip); 1808 return step; 1809 } 1810 } 1811 1812 /** 1813 * Start parsing hint items 1814 * 1815 * @api private 1816 * @param {Object} targetElm 1817 * @method _startHint 1818 */ 1819 function _populateHints(targetElm) { 1820 1821 this._introItems = []; 1822 1823 if (this._options.hints) { 1824 _forEach(this._options.hints, function (hint) { 1825 var currentItem = _cloneObject(hint); 1826 1827 if (typeof(currentItem.element) === 'string') { 1828 //grab the element with given selector from the page 1829 currentItem.element = document.querySelector(currentItem.element); 1830 } 1831 1832 currentItem.hintPosition = currentItem.hintPosition || this._options.hintPosition; 1833 currentItem.hintAnimation = currentItem.hintAnimation || this._options.hintAnimation; 1834 1835 if (currentItem.element !== null) { 1836 this._introItems.push(currentItem); 1837 } 1838 }.bind(this)); 1839 } else { 1840 var hints = targetElm.querySelectorAll('*[data-hint]'); 1841 1842 if (!hints || !hints.length) { 1843 return false; 1844 } 1845 1846 //first add intro items with data-step 1847 _forEach(hints, function (currentElement) { 1848 // hint animation 1849 var hintAnimation = currentElement.getAttribute('data-hintanimation'); 1850 1851 if (hintAnimation) { 1852 hintAnimation = (hintAnimation === 'true'); 1853 } else { 1854 hintAnimation = this._options.hintAnimation; 1855 } 1856 1857 this._introItems.push({ 1858 element: currentElement, 1859 hint: currentElement.getAttribute('data-hint'), 1860 hintPosition: currentElement.getAttribute('data-hintposition') || this._options.hintPosition, 1861 hintAnimation: hintAnimation, 1862 tooltipClass: currentElement.getAttribute('data-tooltipclass'), 1863 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition 1864 }); 1865 }.bind(this)); 1866 } 1867 1868 _addHints.call(this); 1869 1870 /* 1871 todo: 1872 these events should be removed at some point 1873 */ 1874 DOMEvent.on(document, 'click', _removeHintTooltip, this, false); 1875 DOMEvent.on(window, 'resize', _reAlignHints, this, true); 1876 } 1877 1878 /** 1879 * Re-aligns all hint elements 1880 * 1881 * @api private 1882 * @method _reAlignHints 1883 */ 1884 function _reAlignHints() { 1885 _forEach(this._introItems, function (item) { 1886 if (typeof(item.targetElement) === 'undefined') { 1887 return; 1888 } 1889 1890 _alignHintPosition.call(this, item.hintPosition, item.element, item.targetElement); 1891 }.bind(this)); 1892 } 1893 1894 /** 1895 * Get a queryselector within the hint wrapper 1896 * 1897 * @param {String} selector 1898 * @return {NodeList|Array} 1899 */ 1900 function _hintQuerySelectorAll(selector) { 1901 var hintsWrapper = document.querySelector('.introjs-hints'); 1902 return (hintsWrapper) ? hintsWrapper.querySelectorAll(selector) : []; 1903 } 1904 1905 /** 1906 * Hide a hint 1907 * 1908 * @api private 1909 * @method _hideHint 1910 */ 1911 function _hideHint(stepId) { 1912 var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0]; 1913 1914 _removeHintTooltip.call(this); 1915 1916 if (hint) { 1917 _addClass(hint, 'introjs-hidehint'); 1918 } 1919 1920 // call the callback function (if any) 1921 if (typeof (this._hintCloseCallback) !== 'undefined') { 1922 this._hintCloseCallback.call(this, stepId); 1923 } 1924 } 1925 1926 /** 1927 * Hide all hints 1928 * 1929 * @api private 1930 * @method _hideHints 1931 */ 1932 function _hideHints() { 1933 var hints = _hintQuerySelectorAll('.introjs-hint'); 1934
1935 _forEach(hints, function (hint) { 1936 _hideHint.call(this, hint.getAttribute('data-step')); 1937 }.bind(this)); 1938 } 1939 1940 /** 1941 * Show all hints 1942 * 1943 * @api private 1944 * @method _showHints 1945 */ 1946 function _showHints() { 1947 var hints = _hintQuerySelectorAll('.introjs-hint'); 1948 1949 if (hints && hints.length) { 1950 _forEach(hints, function (hint) { 1951 _showHint.call(this, hint.getAttribute('data-step')); 1952 }.bind(this)); 1953 } else { 1954 _populateHints.call(this, this._targetElement); 1955 } 1956 } 1957 1958 /** 1959 * Show a hint 1960 * 1961 * @api private 1962 * @method _showHint 1963 */ 1964 function _showHint(stepId) { 1965 var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0]; 1966 1967 if (hint) { 1968 _removeClass(hint, /introjs-hidehint/g); 1969 } 1970 } 1971 1972 /** 1973 * Removes all hint elements on the page 1974 * Useful when you want to destroy the elements and add them again (e.g. a modal or popup) 1975 * 1976 * @api private 1977 * @method _removeHints 1978 */ 1979 function _removeHints() { 1980 var hints = _hintQuerySelectorAll('.introjs-hint'); 1981 1982 _forEach(hints, function (hint) { 1983 _removeHint.call(this, hint.getAttribute('data-step')); 1984 }.bind(this)); 1985 } 1986 1987 /** 1988 * Remove one single hint element from the page 1989 * Useful when you want to destroy the element and add them again (e.g. a modal or popup) 1990 * Use removeHints if you want to remove all elements. 1991 * 1992 * @api private 1993 * @method _removeHint 1994 */ 1995 function _removeHint(stepId) { 1996 var hint = _hintQuerySelectorAll('.introjs-hint[data-step="' + stepId + '"]')[0]; 1997 1998 if (hint) { 1999 hint.parentNode.removeChild(hint); 2000 } 2001 } 2002 2003 /** 2004 * Add all available hints to the page 2005 * 2006 * @api private 2007 * @method _addHints 2008 */ 2009 function _addHints() { 2010 var self = this; 2011 2012 var hintsWrapper = document.querySelector('.introjs-hints'); 2013 2014 if (hintsWrapper === null) { 2015 hintsWrapper = document.createElement('div'); 2016 hintsWrapper.className = 'introjs-hints'; 2017 } 2018 2019 /** 2020 * Returns an event handler unique to the hint iteration 2021 * 2022 * @param {Integer} i 2023 * @return {Function} 2024 */ 2025 var getHintClick = function (i) { 2026 return function(e) { 2027 var evt = e ? e : window.event; 2028 2029 if (evt.stopPropagation) { 2030 evt.stopPropagation(); 2031 } 2032 2033 if (evt.cancelBubble !== null) { 2034 evt.cancelBubble = true; 2035 } 2036 2037 _showHintDialog.call(self, i); 2038 }; 2039 }; 2040 2041 _forEach(this._introItems, function(item, i) { 2042 // avoid append a hint twice 2043 if (document.querySelector('.introjs-hint[data-step="' + i + '"]')) { 2044 return; 2045 } 2046 2047 var hint = document.createElement('a'); 2048 _setAnchorAsButton(hint); 2049 2050 hint.onclick = getHintClick(i); 2051 2052 hint.className = 'introjs-hint'; 2053 2054 if (!item.hintAnimation) { 2055 _addClass(hint, 'introjs-hint-no-anim'); 2056 } 2057 2058 // hint's position should be fixed if the target element's position is fixed 2059 if (_isFixed(item.element)) { 2060 _addClass(hint, 'introjs-fixedhint'); 2061 } 2062 2063 var hintDot = document.createElement('div'); 2064 hintDot.className = 'introjs-hint-dot'; 2065 var hintPulse = document.createElement('div'); 2066 hintPulse.className = 'introjs-hint-pulse'; 2067 2068 hint.appendChild(hintDot); 2069 hint.appendChild(hintPulse); 2070 hint.setAttribute('data-step', i); 2071 2072 // we swap the hint element with target element 2073 // because _setHelperLayerPosition uses `element` property 2074 item.targetElement = item.element; 2075 item.element = hint; 2076 2077 // align the hint position 2078 _alignHintPosition.call(this, item.hintPosition, hint, item.targetElement); 2079 2080 hintsWrapper.appendChild(hint); 2081 }.bind(this)); 2082 2083 // adding the hints wrapper 2084 document.body.appendChild(hintsWrapper); 2085 2086 // call the callback function (if any) 2087 if (typeof (this._hintsAddedCallback) !== 'undefined') { 2088 this._hintsAddedCallback.call(this); 2089 } 2090 } 2091 2092 /** 2093 * Aligns hint position 2094 * 2095 * @api private 2096 * @method _alignHintPosition 2097 * @param {String} position 2098 * @param {Object} hint 2099 * @param {Object} element 2100 */ 2101 function _alignHintPosition(position, hint, element) { 2102 // get/calculate offset of target element 2103 var offset = _getOffset.call(this, element); 2104 var iconWidth = 20; 2105 var iconHeight = 20; 2106 2107 // align the hint element 2108 switch (position) { 2109 default: 2110 case 'top-left': 2111 hint.style.left = offset.left + 'px'; 2112 hint.style.top = offset.top + 'px'; 2113 break; 2114 case 'top-right': 2115 hint.style.left = (offset.left + offset.width - iconWidth) + 'px'; 2116 hint.style.top = offset.top + 'px'; 2117 break; 2118 case 'bottom-left': 2119 hint.style.left = offset.left + 'px'; 2120 hint.style.top = (offset.top + offset.height - iconHeight) + 'px'; 2121 break; 2122 case 'bottom-right': 2123 hint.style.left = (offset.left + offset.width - iconWidth) + 'px'; 2124 hint.style.top = (offset.top + offset.height - iconHeight) + 'px'; 2125 break; 2126 case 'middle-left': 2127 hint.style.left = offset.left + 'px'; 2128 hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px'; 2129 break; 2130 case 'middle-right': 2131 hint.style.left = (offset.left + offset.width - iconWidth) + 'px'; 2132 hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px'; 2133 break; 2134 case 'middle-middle': 2135 hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px'; 2136 hint.style.top = (offset.top + (offset.height - iconHeight) / 2) + 'px'; 2137 break; 2138 case 'bottom-middle': 2139 hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px'; 2140 hint.style.top = (offset.top + offset.height - iconHeight) + 'px'; 2141 break; 2142 case 'top-middle': 2143 hint.style.left = (offset.left + (offset.width - iconWidth) / 2) + 'px'; 2144 hint.style.top = offset.top + 'px'; 2145 break; 2146 } 2147 } 2148 2149 /** 2150 * Triggers when user clicks on the hint element 2151 * 2152 * @api private 2153 * @method _showHintDialog 2154 * @param {Number} stepId 2155 */ 2156 function _showHintDialog(stepId) { 2157 var hintElement = document.querySelector('.introjs-hint[data-step="' + stepId + '"]'); 2158 var item = this._introItems[stepId]; 2159 2160 // call the callback function (if any) 2161 if (typeof (this._hintClickCallback) !== 'undefined') { 2162 this._hintClickCallback.call(this, hintElement, item, stepId
2162); 2163 } 2164 2165 // remove all open tooltips 2166 var removedStep = _removeHintTooltip.call(this); 2167 2168 // to toggle the tooltip 2169 if (parseInt(removedStep, 10) === stepId) { 2170 return; 2171 } 2172 2173 var tooltipLayer = document.createElement('div'); 2174 var tooltipTextLayer = document.createElement('div'); 2175 var arrowLayer = document.createElement('div'); 2176 var referenceLayer = document.createElement('div'); 2177 2178 tooltipLayer.className = 'introjs-tooltip'; 2179 2180 tooltipLayer.onclick = function (e) { 2181 //IE9 & Other Browsers 2182 if (e.stopPropagation) { 2183 e.stopPropagation(); 2184 } 2185 //IE8 and Lower 2186 else { 2187 e.cancelBubble = true; 2188 } 2189 }; 2190 2191 tooltipTextLayer.className = 'introjs-tooltiptext'; 2192 2193 var tooltipWrapper = document.createElement('p'); 2194 tooltipWrapper.innerHTML = item.hint; 2195 2196 var closeButton = document.createElement('a'); 2197 closeButton.className = this._options.buttonClass; 2198 closeButton.setAttribute('role', 'button'); 2199 closeButton.innerHTML = this._options.hintButtonLabel; 2200 closeButton.onclick = _hideHint.bind(this, stepId); 2201 2202 tooltipTextLayer.appendChild(tooltipWrapper); 2203 tooltipTextLayer.appendChild(closeButton); 2204 2205 arrowLayer.className = 'introjs-arrow'; 2206 tooltipLayer.appendChild(arrowLayer); 2207 2208 tooltipLayer.appendChild(tooltipTextLayer); 2209 2210 // set current step for _placeTooltip function 2211 this._currentStep = hintElement.getAttribute('data-step'); 2212 2213 // align reference layer position 2214 referenceLayer.className = 'introjs-tooltipReferenceLayer introjs-hintReference'; 2215 referenceLayer.setAttribute('data-step', hintElement.getAttribute('data-step')); 2216 _setHelperLayerPosition.call(this, referenceLayer); 2217 2218 referenceLayer.appendChild(tooltipLayer); 2219 document.body.appendChild(referenceLayer); 2220 2221 //set proper position 2222 _placeTooltip.call(this, hintElement, tooltipLayer, arrowLayer, null, true); 2223 } 2224 2225 /** 2226 * Get an element position on the page 2227 * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966 2228 * 2229 * @api private 2230 * @method _getOffset 2231 * @param {Object} element 2232 * @returns Element's position info 2233 */ 2234 function _getOffset(element) { 2235 var body = document.body; 2236 var docEl = document.documentElement; 2237 var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop; 2238 var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft; 2239 var x = element.getBoundingClientRect(); 2240 return { 2241 top: x.top + scrollTop, 2242 width: x.width, 2243 height: x.height, 2244 left: x.left + scrollLeft 2245 }; 2246 } 2247 2248 /** 2249 * Find the nearest scrollable parent 2250 * copied from https://stackoverflow.com/questions/35939886/find-first-scrollable-parent 2251 * 2252 * @param Element element 2253 * @return Element 2254 */ 2255 function _getScrollParent(element) { 2256 var style = window.getComputedStyle(element); 2257 var excludeStaticParent = (style.position === "absolute"); 2258 var overflowRegex = /(auto|scroll)/; 2259 2260 if (style.position === "fixed") return document.body; 2261 2262 for (var parent = element; (parent = parent.parentElement);) { 2263 style = window.getComputedStyle(parent); 2264 if (excludeStaticParent && style.position === "static") { 2265 continue; 2266 } 2267 if (overflowRegex.test(style.overflow + style.overflowY + style.overflowX)) return parent; 2268 } 2269 2270 return document.body; 2271 } 2272 2273 /** 2274 * scroll a scrollable element to a child element 2275 * 2276 * @param Element parent 2277 * @param Element element 2278 * @return Null 2279 */ 2280 function _scrollParentToElement (parent, element) { 2281 parent.scrollTop = element.offsetTop - parent.offsetTop; 2282 } 2283 2284 /** 2285 * Gets the current progress percentage 2286 * 2287 * @api private 2288 * @method _getProgress 2289 * @returns current progress percentage 2290 */ 2291 function _getProgress() { 2292 // Steps are 0 indexed 2293 var currentStep = parseInt((this._currentStep + 1), 10); 2294 return ((currentStep / this._introItems.length) * 100); 2295 } 2296 2297 /** 2298 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 2299 * via: http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically 2300 * 2301 * @param obj1 2302 * @param obj2 2303 * @returns obj3 a new object based on obj1 and obj2 2304 */ 2305 function _mergeOptions(obj1,obj2) { 2306 var obj3 = {}, 2307 attrname; 2308 for (attrname in obj1) { obj3[attrname] = obj1[attrname]; } 2309 for (attrname in obj2) { obj3[attrname] = obj2[attrname]; } 2310 return obj3; 2311 } 2312 2313 var introJs = function (targetElm) { 2314 var instance; 2315 2316 if (typeof (targetElm) === 'object') { 2317 //Ok, create a new instance 2318 instance = new IntroJs(targetElm); 2319 2320 } else if (typeof (targetElm) === 'string') { 2321 //select the target element with query selector 2322 var targetElement = document.querySelector(targetElm); 2323 2324 if (targetElement) { 2325 instance = new IntroJs(targetElement); 2326 } else { 2327 throw new Error('There is no element with given selector.'); 2328 } 2329 } else { 2330 instance = new IntroJs(document.body); 2331 } 2332 // add instance to list of _instances 2333 // passing group to _stamp to increment 2334 // from 0 onward somewhat reliably 2335 introJs.instances[ _stamp(instance, 'introjs-instance') ] = instance; 2336 2337 return instance; 2338 }; 2339 2340 /** 2341 * Current IntroJs version 2342 * 2343 * @property version
2344 * @type String 2345 */ 2346 introJs.version = VERSION; 2347 2348 /** 2349 * key-val object helper for introJs instances 2350 * 2351 * @property instances 2352 * @type Object 2353 */ 2354 introJs.instances = {}; 2355 2356 //Prototype 2357 introJs.fn = IntroJs.prototype = { 2358 clone: function () { 2359 return new IntroJs(this); 2360 }, 2361 setOption: function(option, value) { 2362 this._options[option] = value; 2363 return this; 2364 }, 2365 setOptions: function(options) { 2366 this._options = _mergeOptions(this._options, options); 2367 return this; 2368 }, 2369 start: function (group) { 2370 _introForElement.call(this, this._targetElement, group); 2371 return this; 2372 }, 2373 goToStep: function(step) { 2374 _goToStep.call(this, step); 2375 return this; 2376 }, 2377 addStep: function(options) { 2378 if (!this._options.steps) { 2379 this._options.steps = []; 2380 } 2381 2382 this._options.steps.push(options); 2383 2384 return this; 2385 }, 2386 addSteps: function(steps) { 2387 if (!steps.length) return; 2388 2389 for(var index = 0; index < steps.length; index++) { 2390 this.addStep(steps[index]); 2391 } 2392 2393 return this; 2394 }, 2395 goToStepNumber: function(step) { 2396 _goToStepNumber.call(this, step); 2397 2398 return this; 2399 }, 2400 nextStep: function() { 2401 _nextStep.call(this); 2402 return this; 2403 }, 2404 previousStep: function() { 2405 _previousStep.call(this); 2406 return this; 2407 }, 2408 exit: function(force) { 2409 _exitIntro.call(this, this._targetElement, force); 2410 return this; 2411 }, 2412 refresh: function() { 2413 _refresh.call(this); 2414 return this; 2415 }, 2416 onbeforechange: function(providedCallback) { 2417 if (typeof (providedCallback) === 'function') { 2418 this._introBeforeChangeCallback = providedCallback; 2419 } else { 2420 throw new Error('Provided callback for onbeforechange was not a function'); 2421 } 2422 return this; 2423 }, 2424 onchange: function(providedCallback) { 2425 if (typeof (providedCallback) === 'function') { 2426 this._introChangeCallback = providedCallback; 2427 } else { 2428 throw new Error('Provided callback for onchange was not a function.'); 2429 } 2430 return this; 2431 }, 2432 onafterchange: function(providedCallback) { 2433 if (typeof (providedCallback) === 'function') { 2434 this._introAfterChangeCallback = providedCallback; 2435 } else { 2436 throw new Error('Provided callback for onafterchange was not a function'); 2437 } 2438 return this; 2439 }, 2440 oncomplete: function(providedCallback) { 2441 if (typeof (providedCallback) === 'function') { 2442 this._introCompleteCallback = providedCallback; 2443 } else { 2444 throw new Error('Provided callback for oncomplete was not a function.'); 2445 } 2446 return this; 2447 }, 2448 onhintsadded: function(providedCallback) { 2449 if (typeof (providedCallback) === 'function') { 2450 this._hintsAddedCallback = providedCallback; 2451 } else { 2452 throw new Error('Provided callback for onhintsadded was not a function.'); 2453 } 2454 return this; 2455 }, 2456 onhintclick: function(providedCallback) { 2457 if (typeof (providedCallback) === 'function') { 2458 this._hintClickCallback = providedCallback; 2459 } else { 2460 throw new Error('Provided callback for onhintclick was not a function.'); 2461 } 2462 return this; 2463 }, 2464 onhintclose: function(providedCallback) { 2465 if (typeof (providedCallback) === 'function') { 2466 this._hintCloseCallback = providedCallback; 2467 } else { 2468 throw new Error('Provided callback for onhintclose was not a function.'); 2469 } 2470 return this; 2471 }, 2472 onexit: function(providedCallback) { 2473 if (typeof (providedCallback) === 'function') { 2474 this._introExitCallback = providedCallback; 2475 } else { 2476 throw new Error('Provided callback for onexit was not a function.'); 2477 } 2478 return this; 2479 }, 2480 onskip: function(providedCallback) { 2481 if (typeof (providedCallback) === 'function') { 2482 this._introSkipCallback = providedCallback; 2483 } else { 2484 throw new Error('Provided callback for onskip was not a function.'); 2485 } 2486 return this; 2487 }, 2488 onbeforeexit: function(providedCallback) { 2489 if (typeof (providedCallback) === 'function') { 2490 this._introBeforeExitCallback = providedCallback; 2491 } else { 2492 throw new Error('Provided callback for onbeforeexit was not a function.'); 2493 } 2494 return this; 2495 }, 2496 addHints: function() { 2497 _populateHints.call(this, this._targetElement); 2498 return this; 2499 }, 2500 hideHint: function (stepId) { 2501 _hideHint.call(this, stepId); 2502 return this; 2503 }, 2504 hideHints: function () { 2505 _hideHints.call(this); 2506 return this; 2507 }, 2508 showHint: function (stepId) { 2509 _showHint.call(this, stepId); 2510 return this; 2511 }, 2512 showHints: function () { 2513 _showHints.call(this); 2514 return this; 2515 }, 2516 removeHints: function () { 2517 _removeHints.call(this); 2518 return this; 2519 }, 2520 removeHint: function (stepId) { 2521 _removeHint.call(this, stepId); 2522 return this; 2523 }, 2524 showHintDialog: function (stepId) { 2525 _showHintDialog.call(this, stepId); 2526 return this; 2527 } 2528 }; 2529 2530 return introJs; 2531});
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.