1/** 2 * Intro.js v2.0 3 * https://github.com/usablica/intro.js 4 * MIT licensed 5 * 6 * Copyright (C) 2013 usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh) 7 */ 8 9(function (root, factory) { 10 if (typeof exports === 'object') { 11 // CommonJS 12 factory(exports); 13 } else if (typeof define === 'function' && define.amd) { 14 // AMD. Register as an anonymous module. 15 define(['exports'], factory); 16 } else { 17 // Browser globals 18 factory(root); 19 } 20} (this, function (exports) { 21 //Default config/variables 22 var VERSION = '2.0'; 23 24 /** 25 * IntroJs main class 26 * 27 * @class IntroJs 28 */ 29 function IntroJs(obj) { 30 this._targetElement = obj; 31 this._introItems = []; 32 33 this._options = { 34 /* Next button label in tooltip box */ 35 nextLabel: 'Next →', 36 /* Previous button label in tooltip box */ 37 prevLabel: '← Back', 38 /* Skip button label in tooltip box */ 39 skipLabel: 'Skip', 40 /* Done button label in tooltip box */ 41 doneLabel: 'Done', 42 /* Default tooltip box position */ 43 tooltipPosition: 'bottom', 44 /* Next CSS class for tooltip boxes */ 45 tooltipClass: '', 46 /* CSS class that is added to the helperLayer */ 47 highlightClass: '', 48 /* Close introduction when pressing Escape button? */ 49 exitOnEsc: true, 50 /* Close introduction when clicking on overlay layer? */ 51 exitOnOverlayClick: true, 52 /* Show step numbers in introduction? */ 53 showStepNumbers: true, 54 /* Let user use keyboard to navigate the tour? */ 55 keyboardNavigation: true, 56 /* Show tour control buttons? */ 57 showButtons: true, 58 /* Show tour bullets? */ 59 showBullets: true, 60 /* Show tour progress? */ 61 showProgress: false, 62 /* Scroll to highlighted element? */ 63 scrollToElement: true, 64 /* Set the overlay opacity */ 65 overlayOpacity: 0.8, 66 /* Precedence of positions, when auto is enabled */ 67 positionPrecedence: ["bottom", "top", "right", "left"], 68 /* Disable an interaction with element? */ 69 disableInteraction: false, 70 /* Default hint position */ 71 hintPosition: 'top-middle', 72 /* Hint button label */ 73 hintButtonLabel: 'Got it' 74 }; 75 } 76 77 /** 78 * Initiate a new introduction/guide from an element in the page 79 * 80 * @api private 81 * @method _introForElement 82 * @param {Object} targetElm 83 * @returns {Boolean} Success or not? 84 */ 85 function _introForElement(targetElm) { 86 var introItems = [], 87 self = this; 88 89 if (this._options.steps) { 90 //use steps passed programmatically 91 for (var i = 0, stepsLength = this._options.steps.length; i < stepsLength; i++) { 92 var currentItem = _cloneObject(this._options.steps[i]); 93 //set the step 94 currentItem.step = introItems.length + 1; 95 //use querySelector function only when developer used CSS selector 96 if (typeof(currentItem.element) === 'string') { 97 //grab the element with given selector from the page 98 currentItem.element = document.querySelector(currentItem.element); 99 } 100 101 //intro without element 102 if (typeof(currentItem.element) === 'undefined' || currentItem.element == null) { 103 var floatingElementQuery = document.querySelector(".introjsFloatingElement"); 104 105 if (floatingElementQuery == null) { 106 floatingElementQuery = document.createElement('div'); 107 floatingElementQuery.className = 'introjsFloatingElement'; 108 109 document.body.appendChild(floatingElementQuery); 110 } 111 112 currentItem.element = floatingElementQuery; 113 currentItem.position = 'floating'; 114 } 115 116 if (currentItem.element != null) { 117 introItems.push(currentItem); 118 } 119 } 120 121 } else { 122 //use steps from data-* annotations 123 var allIntroSteps = targetElm.querySelectorAll('*[data-intro]'); 124 //if there's no element to intro 125 if (allIntroSteps.length < 1) { 126 return false; 127 } 128 129 //first add intro items with data-step 130 for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) { 131 var currentElement = allIntroSteps[i]; 132 var step = parseInt(currentElement.getAttribute('data-step'), 10); 133 134 if (step > 0) { 135 introItems[step - 1] = { 136 element: currentElement, 137 intro: currentElement.getAttribute('data-intro'), 138 step: parseInt(currentElement.getAttribute('data-step'), 10), 139 tooltipClass: currentElement.getAttribute('data-tooltipClass'),
140 highlightClass: currentElement.getAttribute('data-highlightClass'), 141 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition 142 }; 143 } 144 } 145 146 //next add intro items without data-step 147 //todo: we need a cleanup here, two loops are redundant 148 var nextStep = 0; 149 for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) { 150 var currentElement = allIntroSteps[i]; 151 152 if (currentElement.getAttribute('data-step') == null) { 153 154 while (true) { 155 if (typeof introItems[nextStep] == 'undefined') { 156 break; 157 } else { 158 nextStep++; 159 } 160 } 161 162 introItems[nextStep] = { 163 element: currentElement, 164 intro: currentElement.getAttribute('data-intro'), 165 step: nextStep + 1, 166 tooltipClass: currentElement.getAttribute('data-tooltipClass'), 167 highlightClass: currentElement.getAttribute('data-highlightClass'), 168 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition 169 }; 170 } 171 } 172 } 173 174 //removing undefined/null elements 175 var tempIntroItems = []; 176 for (var z = 0; z < introItems.length; z++) { 177 introItems[z] && tempIntroItems.push(introItems[z]); // copy non-empty values to the end of the array 178 } 179 180 introItems = tempIntroItems; 181 182 //Ok, sort all items with given steps 183 introItems.sort(function (a, b) { 184 return a.step - b.step; 185 }); 186 187 //set it to the introJs object 188 self._introItems = introItems; 189 190 //add overlay layer to the page 191 if(_addOverlayLayer.call(self, targetElm)) { 192 //then, start the show 193 _nextStep.call(self); 194 195 var skipButton = targetElm.querySelector('.introjs-skipbutton'), 196 nextStepButton = targetElm.querySelector('.introjs-nextbutton'); 197 198 self._onKeyDown = function(e) { 199 if (e.keyCode === 27 && self._options.exitOnEsc == true) { 200 //escape key pressed, exit the intro 201 //check if exit callback is defined 202 if (self._introExitCallback != undefined) { 203 self._introExitCallback.call(self); 204 } 205 _exitIntro.call(self, targetElm); 206 } else if(e.keyCode === 37) { 207 //left arrow 208 _previousStep.call(self); 209 } else if (e.keyCode === 39) { 210 //right arrow 211 _nextStep.call(self); 212 } else if (e.keyCode === 13) { 213 //srcElement === ie 214 var target = e.target || e.srcElement; 215 if (target && target.className.indexOf('introjs-prevbutton') > 0) { 216 //user hit enter while focusing on previous button 217 _previousStep.call(self); 218 } else if (target && target.className.indexOf('introjs-skipbutton') > 0) { 219 //user hit enter while focusing on skip button 220 if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') { 221 self._introCompleteCallback.call(self); 222 } 223 //check if any callback is defined 224 if (self._introExitCallback != undefined) { 225 self._introExitCallback.call(self); 226 } 227 _exitIntro.call(self, targetElm); 228 } else { 229 //default behavior for responding to enter 230 _nextStep.call(self); 231 } 232 233 //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers 234 if(e.preventDefault) { 235 e.preventDefault(); 236 } else { 237 e.returnValue = false; 238 } 239 } 240 }; 241 242 self._onResize = function(e) { 243 _setHelperLayerPosition.call(self, document.querySelector('.introjs-helperLayer')); 244 _setHelperLayerPosition.call(self, document.querySelector('.introjs-tooltipReferenceLayer')); 245 }; 246 247 if (window.addEventListener) { 248 if (this._options.keyboardNavigation) { 249 window.addEventListener('keydown', self._onKeyDown, true); 250 } 251 //for window resize 252 window.addEventListener('resize', self._onResize, true); 253 } else if (document.attachEvent) { //IE 254 if (this._options.keyboardNavigation) { 255 document.attachEvent('onkeydown', self._onKeyDown); 256 } 257 //for window resize 258 document.attachEvent('onresize', self._onResize); 259 } 260 } 261 return false;
262 } 263 264 /* 265 * makes a copy of the object 266 * @api private 267 * @method _cloneObject 268 */ 269 function _cloneObject(object) { 270 if (object == null || typeof (object) != 'object' || typeof (object.nodeType) != 'undefined') { 271 return object; 272 } 273 var temp = {}; 274 for (var key in object) { 275 if (typeof (jQuery) != 'undefined' && object[key] instanceof jQuery) { 276 temp[key] = object[key]; 277 } else { 278 temp[key] = _cloneObject(object[key]); 279 } 280 } 281 return temp; 282 } 283 /** 284 * Go to specific step of introduction 285 * 286 * @api private 287 * @method _goToStep 288 */ 289 function _goToStep(step) { 290 //because steps starts with zero 291 this._currentStep = step - 2; 292 if (typeof (this._introItems) !== 'undefined') { 293 _nextStep.call(this); 294 } 295 } 296 297 /** 298 * Go to next step on intro 299 * 300 * @api private 301 * @method _nextStep 302 */ 303 function _nextStep() { 304 this._direction = 'forward'; 305 306 if (typeof (this._currentStep) === 'undefined') { 307 this._currentStep = 0; 308 } else { 309 ++this._currentStep; 310 } 311 312 if ((this._introItems.length) <= this._currentStep) { 313 //end of the intro 314 //check if any callback is defined 315 if (typeof (this._introCompleteCallback) === 'function') { 316 this._introCompleteCallback.call(this); 317 } 318 _exitIntro.call(this, this._targetElement); 319 return; 320 } 321 322 var nextStep = this._introItems[this._currentStep]; 323 if (typeof (this._introBeforeChangeCallback) !== 'undefined') { 324 this._introBeforeChangeCallback.call(this, nextStep.element); 325 } 326 327 _showElement.call(this, nextStep); 328 } 329 330 /** 331 * Go to previous step on intro 332 * 333 * @api private 334 * @method _nextStep 335 */ 336 function _previousStep() { 337 this._direction = 'backward'; 338 339 if (this._currentStep === 0) { 340 return false; 341 } 342 343 var nextStep = this._introItems[--this._currentStep]; 344 if (typeof (this._introBeforeChangeCallback) !== 'undefined') { 345 this._introBeforeChangeCallback.call(this, nextStep.element); 346 } 347 348 _showElement.call(this, nextStep); 349 } 350 351 /** 352 * Exit from intro 353 * 354 * @api private 355 * @method _exitIntro 356 * @param {Object} targetElement 357 */ 358 function _exitIntro(targetElement) { 359 //remove overlay layer from the page 360 var overlayLayer = targetElement.querySelector('.introjs-overlay'); 361 362 //return if intro already completed or skipped 363 if (overlayLayer == null) { 364 return; 365 } 366 367 //for fade-out animation 368 overlayLayer.style.opacity = 0; 369 setTimeout(function () { 370 if (overlayLayer.parentNode) { 371 overlayLayer.parentNode.removeChild(overlayLayer); 372 } 373 }, 500); 374 375 //remove all helper layers 376 var helperLayer = targetElement.querySelector('.introjs-helperLayer'); 377 if (helperLayer) { 378 helperLayer.parentNode.removeChild(helperLayer); 379 } 380 381 var referenceLayer = targetElement.querySelector('.introjs-tooltipReferenceLayer'); 382 if (referenceLayer) { 383 referenceLayer.parentNode.removeChild(referenceLayer); 384 } 385 //remove disableInteractionLayer 386 var disableInteractionLayer = targetElement.querySelector('.introjs-disableInteraction'); 387 if (disableInteractionLayer) { 388 disableInteractionLayer.parentNode.removeChild(disableInteractionLayer); 389 } 390 391 //remove intro floating element 392 var floatingElement = document.querySelector('.introjsFloatingElement'); 393 if (floatingElement) { 394 floatingElement.parentNode.removeChild(floatingElement); 395 } 396 397 //remove `introjs-showElement` class from the element 398 var showElement = document.querySelector('.introjs-showElement'); 399 if (showElement) { 400 showElement.className = showElement.className.replace(/introjs-[a-zA-Z]+/g, '').replace(/^\s+|\s+$/g, ''); // This is a manual trim. 401 } 402 403 //remove `introjs-fixParent` class from the elements 404 var fixParents = document.querySelectorAll('.introjs-fixParent'); 405 if (fixParents && fixParents.length > 0) { 406 for (var i = fixParents.length - 1; i >= 0; i--) { 407 fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, '').replace(/^\s+|\s+$/g, ''); 408 } 409 } 410 411 //clean listeners 412 if (window.removeEventListener) { 413 window.removeEventListener('keydown', this._onKeyDown, true); 414 } else if (document.detachEvent) { //IE 415 document.detachEvent('onkeydown', this._onKeyDown); 416 } 417 418 //set the step to zero 419 this._currentStep = undefined; 420 } 421 422 /** 423 * Render tooltip box in the page 424 * 425 * @api private 426 * @method _placeTooltip 427 * @param {HTMLElement} targetElement 428 * @param {HTMLElement} tooltipLayer 429 * @param {HTMLElement} arrowLayer 430 * @param {HTMLElement} helperNumberLayer 431 * @param {Boolean} hintMode 432 */ 433 function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer, hintMode) { 434 var tooltipCssClass = '', 435 currentStepObj, 436 tooltipOffset, 437 targetOffset, 438 windowSize, 439 currentTooltipPosition; 440 441 hintMode = hintMode || false; 442 443 //reset the old style 444 tooltipLayer.style.top = null; 445 tooltipLayer.style.right = null; 446 tooltipLayer.style.bottom = null; 447 tooltipLayer.style.left = null; 448 tooltipLayer.style.marginLeft = null; 449 tooltipLayer.style.marginTop = null; 450 451 arrowLayer.style.display = 'inherit'; 452 453 if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) { 454 helperNumberLayer.style.top = null; 455 helperNumberLayer.style.left = null; 456 } 457 458 //prevent error when `this._currentStep` is undefined 459 if (!this._introItems[this._currentStep]) return; 460 461 //if we have a custom css class for each step 462 currentStepObj = this._introItems[this._currentStep]; 463 if (typeof (currentStepObj.tooltipClass) === 'string') { 464 tooltipCssClass = currentStepObj.tooltipClass; 465 } else { 466 tooltipCssClass = this._options.tooltipClass; 467 } 468 469 tooltipLayer.className = ('introjs-tooltip ' + tooltipCssClass).replace(/^\s+|\s+$/g, ''); 470 471 currentTooltipPosition = this._introItems[this._currentStep].position; 472 if ((currentTooltipPosition == "auto" || this._options.tooltipPosition == "auto")) { 473 if (currentTooltipPosition != "floating") { // Floating is always valid, no point in calculating 474 currentTooltipPosition = _determineAutoPosition.call(this, targetElement, tooltipLayer, currentTooltipPosition); 475 } 476 } 477 targetOffset = _getOffset(targetElement); 478 tooltipOffset = _getOffset(tooltipLayer); 479 windowSize = _getWinSize(); 480 481 switch (currentTooltipPosition) { 482 case 'top': 483 arrowLayer.className = 'introjs-arrow bottom'; 484 485 if (hintMode) { 486 var tooltipLayerStyleLeft = 0; 487 } else { 488 var tooltipLayerStyleLeft = 15; 489 } 490 491 _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer); 492 tooltipLayer.style.bottom = (targetOffset.height + 20) + 'px'; 493 break; 494 case 'right': 495 tooltipLayer.style.left = (targetOffset.width + 20) + 'px'; 496 if (targetOffset.top + tooltipOffset.height > windowSize.height) { 497 // In this case, right would have fallen below the bottom of the screen. 498 // Modify so that the bottom of the tooltip connects with the target 499 arrowLayer.className = "introjs-arrow left-bottom"; 500 tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px"; 501 } else { 502 arrowLayer.className = 'introjs-arrow left'; 503 } 504 break; 505 case 'left': 506 if (!hintMode && this._options.showStepNumbers == true) { 507 tooltipLayer.style.top = '15px'; 508 } 509 510 if (targetOffset.top + tooltipOffset.height > windowSize.height) { 511 // In this case, left would have fallen below the bottom of the screen. 512 // Modify so that the bottom of the tooltip connects with the target 513 tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px"; 514 arrowLayer.className = 'introjs-arrow right-bottom'; 515 } else { 516 arrowLayer.className = 'introjs-arrow right'; 517 } 518 tooltipLayer.style.right = (targetOffset.width + 20) + 'px'; 519 520 break; 521 case 'floating': 522 arrowLayer.style.display = 'none'; 523 524 //we have to adjust the top and left of layer manually for intro items without element 525 tooltipLayer.style.left = '50%'; 526 tooltipLayer.style.top = '50%'; 527 tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2) + 'px'; 528 tooltipLayer.style.marginTop = '-' + (tooltipOffset.height / 2) + 'px'; 529 530 if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) { 531 helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px'; 532 helperNumberLayer.style.top = '-' + ((tooltipOffset.height / 2) + 18) + 'px'; 533 } 534 535 break; 536 case 'bottom-right-aligned': 537 arrowLayer.className = 'introjs-arrow top-right'; 538 539 var tooltipLayerStyleRight = 0; 540 _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer); 541 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 542 break; 543 544 case 'bottom-middle-aligned': 545 arrowLayer.className = 'introjs-arrow top-middle'; 546 547 var tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2; 548 549 // a fix for middle aligned hints 550 if (hintMode) { 551 tooltipLayerStyleLeftRight += 5; 552 } 553 554 if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) { 555 tooltipLayer.style.right = null; 556 _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer); 557 } 558 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 559 break; 560 561 case 'bottom-left-aligned': 562 // Bottom-left-aligned is the same as the default bottom 563 case 'bottom': 564 // Bottom going to follow the default behavior 565 default: 566 arrowLayer.className = 'introjs-arrow top'; 567 568 var tooltipLayerStyleLeft = 0; 569 _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer); 570 tooltipLayer.style.top = (targetOffset.height + 20) + 'px'; 571 break; 572 } 573 } 574 575 /** 576 * Set tooltip left so it doesn't go off the right side of the window 577 * 578 * @return boolean true, if tooltipLayerStyleLeft is ok. false, otherwise. 579 */ 580 function _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer) { 581 if (targetOffset.left + tooltipLayerStyleLeft + tooltipOffset.width > windowSize.width) { 582 // off the right side of the window 583 tooltipLayer.style.left = (windowSize.width - tooltipOffset.width - targetOffset.left) + 'px'; 584 return false;
585 } 586 tooltipLayer.style.left = tooltipLayerStyleLeft + 'px'; 587 return true; 588 } 589 590 /** 591 * Set tooltip right so it doesn't go off the left side of the window 592 * 593 * @return boolean true, if tooltipLayerStyleRight is ok. false, otherwise. 594 */ 595 function _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer) { 596 if (targetOffset.left + targetOffset.width - tooltipLayerStyleRight - tooltipOffset.width < 0) { 597 // off the left side of the window 598 tooltipLayer.style.left = (-targetOffset.left) + 'px'; 599 return false; 600 } 601 tooltipLayer.style.right = tooltipLayerStyleRight + 'px'; 602 return true; 603 } 604 605 /** 606 * Determines the position of the tooltip based on the position precedence and availability 607 * of screen space. 608 * 609 * @param {Object} targetElement 610 * @param {Object} tooltipLayer 611 * @param {Object} desiredTooltipPosition 612 * 613 */ 614 function _determineAutoPosition(targetElement, tooltipLayer, desiredTooltipPosition) { 615 616 // Take a clone of position precedence. These will be the available 617 var possiblePositions = this._options.positionPrecedence.slice(); 618 619 var windowSize = _getWinSize(); 620 var tooltipHeight = _getOffset(tooltipLayer).height + 10; 621 var tooltipWidth = _getOffset(tooltipLayer).width + 20; 622 var targetOffset = _getOffset(targetElement); 623 624 // If we check all the possible areas, and there are no valid places for the tooltip, the element 625 // must take up most of the screen real estate. Show the tooltip floating in the middle of the screen. 626 var calculatedPosition = "floating"; 627 628 // Check if the width of the tooltip + the starting point would spill off the right side of the screen 629 // If no, neither bottom or top are valid 630 if (targetOffset.left + tooltipWidth > windowSize.width || ((targetOffset.left + (targetOffset.width / 2)) - tooltipWidth) < 0) { 631 _removeEntry(possiblePositions, "bottom"); 632 _removeEntry(possiblePositions, "top"); 633 } else { 634 // Check for space below 635 if ((targetOffset.height + targetOffset.top + tooltipHeight) > windowSize.height) { 636 _removeEntry(possiblePositions, "bottom"); 637 } 638 639 // Check for space above 640 if (targetOffset.top - tooltipHeight < 0) { 641 _removeEntry(possiblePositions, "top"); 642 } 643 } 644 645 // Check for space to the right 646 if (targetOffset.width + targetOffset.left + tooltipWidth > windowSize.width) { 647 _removeEntry(possiblePositions, "right"); 648 } 649 650 // Check for space to the left 651 if (targetOffset.left - tooltipWidth < 0) { 652 _removeEntry(possiblePositions, "left"); 653 } 654 655 // At this point, our array only has positions that are valid. Pick the first one, as it remains in order 656 if (possiblePositions.length > 0) { 657 calculatedPosition = possiblePositions[0]; 658 } 659 660 // If the requested position is in the list, replace our calculated choice with that 661 if (desiredTooltipPosition && desiredTooltipPosition != "auto") { 662 if (possiblePositions.indexOf(desiredTooltipPosition) > -1) { 663 calculatedPosition = desiredTooltipPosition; 664 } 665 } 666 667 return calculatedPosition; 668 } 669 670 /** 671 * Remove an entry from a string array if it's there, does nothing if it isn't there. 672 * 673 * @param {Array} stringArray 674 * @param {String} stringToRemove 675 */ 676 function _removeEntry(stringArray, stringToRemove) { 677 if (stringArray.indexOf(stringToRemove) > -1) { 678 stringArray.splice(stringArray.indexOf(stringToRemove), 1); 679 } 680 } 681 682 /** 683 * Update the position of the helper layer on the screen 684 * 685 * @api private 686 * @method _setHelperLayerPosition 687 * @param {Object} helperLayer 688 */ 689 function _setHelperLayerPosition(helperLayer) { 690 if (helperLayer) { 691 //prevent error when `this._currentStep` in undefined 692 if (!this._introItems[this._currentStep]) return; 693 694 var currentElement = this._introItems[this._currentStep], 695 elementPosition = _getOffset(currentElement.element), 696 widthHeightPadding = 10; 697 698 // if the target element is fixed, the tooltip should be fixed as well. 699 if (_isFixed(currentElement.element)) { 700 helperLayer.className += ' introjs-fixedTooltip'; 701 } 702 703 if (currentElement.position == 'floating') { 704 widthHeightPadding = 0; 705 } 706 707 //set new position to helper layer 708 helperLayer.setAttribute('style', 'width: ' + (elementPosition.width + widthHeightPadding) + 'px; ' + 709 'height:' + (elementPosition.height + widthHeightPadding) + 'px; ' + 710 'top:' + (elementPosition.top - 5) + 'px;' + 711 'left: ' + (elementPosition.left - 5) + 'px;'); 712 713 } 714 } 715 716 /** 717 * Add disableinteraction layer and adjust the size and position of the layer 718 * 719 * @api private 720 * @method _disableInteraction 721 */ 722 function _disableInteraction() { 723 var disableInteractionLayer = document.querySelector('.introjs-disableInteraction'); 724 if (disableInteractionLayer === null) { 725 disableInteractionLayer = document.createElement('div'); 726 disableInteractionLayer.className = 'introjs-disableInteraction'; 727 this._targetElement.appendChild(disableInteractionLayer); 728 } 729 730 _setHelperLayerPosition.call(this, disableInteractionLayer); 731 } 732 733 /** 734 * Show an element on the page 735 * 736 * @api private 737 * @method _showElement 738 * @param {Object} targetElement 739 */ 740 function _showElement(targetElement) { 741 742 if (typeof (this._introChangeCallback) !== 'undefined') { 743 this._introChangeCallback.call(this, targetElement.element); 744 } 745 746 var self = this, 747 oldHelperLayer = document.querySelector('.introjs-helperLayer'), 748 oldReferenceLayer = document.querySelector('.introjs-tooltipReferenceLayer'), 749 highlightClass = 'introjs-helperLayer', 750 elementPosition = _getOffset(targetElement.element); 751 752 //check for a current step highlight class 753 if (typeof (targetElement.highlightClass) === 'string') { 754 highlightClass += (' ' + targetElement.highlightClass); 755 } 756 //check for options highlight class 757 if (typeof (this._options.highlightClass) === 'string') { 758 highlightClass += (' ' + this._options.highlightClass); 759 } 760 761 if (oldHelperLayer != null) {
762 var oldHelperNumberLayer = oldReferenceLayer.querySelector('.introjs-helperNumberLayer'), 763 oldtooltipLayer = oldReferenceLayer.querySelector('.introjs-tooltiptext'), 764 oldArrowLayer = oldReferenceLayer.querySelector('.introjs-arrow'), 765 oldtooltipContainer = oldReferenceLayer.querySelector('.introjs-tooltip'), 766 skipTooltipButton = oldReferenceLayer.querySelector('.introjs-skipbutton'), 767 prevTooltipButton = oldReferenceLayer.querySelector('.introjs-prevbutton'), 768 nextTooltipButton = oldReferenceLayer.querySelector('.introjs-nextbutton'); 769 770 //update or reset the helper highlight class 771 oldHelperLayer.className = highlightClass; 772 //hide the tooltip 773 oldtooltipContainer.style.opacity = 0; 774 oldtooltipContainer.style.display = "none"; 775 776 if (oldHelperNumberLayer != null) { 777 var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)]; 778 779 if (lastIntroItem != null && (this._direction == 'forward' && lastIntroItem.position == 'floating') || (this._direction == 'backward' && targetElement.position == 'floating')) { 780 oldHelperNumberLayer.style.opacity = 0; 781 } 782 } 783 784 //set new position to helper layer 785 _setHelperLayerPosition.call(self, oldHelperLayer); 786 _setHelperLayerPosition.call(self, oldReferenceLayer); 787 788 //remove `introjs-fixParent` class from the elements 789 var fixParents = document.querySelectorAll('.introjs-fixParent'); 790 if (fixParents && fixParents.length > 0) { 791 for (var i = fixParents.length - 1; i >= 0; i--) { 792 fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, '').replace(/^\s+|\s+$/g, ''); 793 }; 794 } 795 796 //remove old classes 797 var oldShowElement = document.querySelector('.introjs-showElement'); 798 oldShowElement.className = oldShowElement.className.replace(/introjs-[a-zA-Z]+/g, '').replace(/^\s+|\s+$/g, ''); 799 800 //we should wait until the CSS3 transition is competed (it's 0.3 sec) to prevent incorrect `height` and `width` calculation 801 if (self._lastShowElementTimer) { 802 clearTimeout(self._lastShowElementTimer); 803 } 804 self._lastShowElementTimer = setTimeout(function() { 805 //set current step to the label 806 if (oldHelperNumberLayer != null) { 807 oldHelperNumberLayer.innerHTML = targetElement.step; 808 } 809 //set current tooltip text 810 oldtooltipLayer.innerHTML = targetElement.intro; 811 //set the tooltip position 812 oldtooltipContainer.style.display = "block"; 813 _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer); 814 815 //change active bullet 816 oldReferenceLayer.querySelector('.introjs-bullets li > a.active').className = ''; 817 oldReferenceLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active'; 818 819 oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').setAttribute('style', 'width:' + _getProgress.call(self) + '%;'); 820 821 //show the tooltip 822 oldtooltipContainer.style.opacity = 1; 823 if (oldHelperNumberLayer) oldHelperNumberLayer.style.opacity = 1; 824 825 //reset button focus 826 if (nextTooltipButton.tabIndex === -1) { 827 //tabindex of -1 means we are at the end of the tour - focus on skip / done 828 skipTooltipButton.focus(); 829 } else { 830 //still in the tour, focus on next 831 nextTooltipButton.focus(); 832 } 833 }, 350); 834 835 } else { 836 var helperLayer = document.createElement('div'), 837 referenceLayer = document.createElement('div'), 838 arrowLayer = document.createElement('div'), 839 tooltipLayer = document.createElement('div'), 840 tooltipTextLayer = document.createElement('div'), 841 bulletsLayer = document.createElement('div'), 842 progressLayer = document.createElement('div'), 843 buttonsLayer = document.createElement('div'); 844 845 helperLayer.className = highlightClass; 846 referenceLayer.className = 'introjs-tooltipReferenceLayer'; 847 848 //set new position to helper layer 849 _setHelperLayerPosition.call(self, helperLayer); 850 _setHelperLayerPosition.call(self, referenceLayer); 851 852 //add helper layer to target element 853 this._targetElement.appendChild(helperLayer); 854 this._targetElement.appendChild(referenceLayer); 855 856 arrowLayer.className = 'introjs-arrow'; 857 858 tooltipTextLayer.className = 'introjs-tooltiptext'; 859 tooltipTextLayer.innerHTML = targetElement.intro; 860 861 bulletsLayer.className = 'introjs-bullets'; 862 863 if (this._options.showBullets === false) { 864 bulletsLayer.style.display = 'none'; 865 } 866 867 var ulContainer = document.createElement('ul'); 868 869 for (var i = 0, stepsLength = this._introItems.length; i < stepsLength; i++) { 870 var innerLi = document.createElement('li'); 871 var anchorLink = document.createElement('a'); 872 873 anchorLink.onclick = function() { 874 self.goToStep(this.getAttribute('data-stepnumber')); 875 }; 876 877 if (i === (targetElement.step-1)) anchorLink.className = 'active'; 878 879 anchorLink.href = 'javascript:void(0);'; 880 anchorLink.innerHTML = " "; 881 anchorLink.setAttribute('data-stepnumber', this._introItems[i].step); 882 883 innerLi.appendChild(anchorLink); 884 ulContainer.appendChild(innerLi); 885 } 886 887 bulletsLayer.appendChild(ulContainer); 888 889 progressLayer.className = 'introjs-progress'; 890 891 if (this._options.showProgress === false) { 892 progressLayer.style.display = 'none'; 893 } 894 var progressBar = document.createElement('div'); 895 progressBar.className = 'introjs-progressbar'; 896 progressBar.setAttribute('style', 'width:' + _getProgress.call(this) + '%;'); 897 898 progressLayer.appendChild(progressBar); 899 900 buttonsLayer.className = 'introjs-tooltipbuttons'; 901 if (this._options.showButtons === false) { 902 buttonsLayer.style.display = 'none'; 903 } 904 905 tooltipLayer.className = 'introjs-tooltip'; 906 tooltipLayer.appendChild(tooltipTextLayer); 907 tooltipLayer.appendChild(bulletsLayer); 908 tooltipLayer.appendChild(progressLayer); 909 910 //add helper layer number 911 if (this._options.showStepNumbers == true) { 912 var helperNumberLayer = document.createElement('span');
913 helperNumberLayer.className = 'introjs-helperNumberLayer'; 914 helperNumberLayer.innerHTML = targetElement.step; 915 referenceLayer.appendChild(helperNumberLayer); 916 } 917 918 tooltipLayer.appendChild(arrowLayer); 919 referenceLayer.appendChild(tooltipLayer); 920 921 //next button 922 var nextTooltipButton = document.createElement('a'); 923 924 nextTooltipButton.onclick = function() { 925 if (self._introItems.length - 1 != self._currentStep) { 926 _nextStep.call(self); 927 } 928 }; 929 930 nextTooltipButton.href = 'javascript:void(0);'; 931 nextTooltipButton.innerHTML = this._options.nextLabel; 932 933 //previous button 934 var prevTooltipButton = document.createElement('a'); 935 936 prevTooltipButton.onclick = function() { 937 if (self._currentStep != 0) { 938 _previousStep.call(self); 939 } 940 }; 941 942 prevTooltipButton.href = 'javascript:void(0);'; 943 prevTooltipButton.innerHTML = this._options.prevLabel; 944 945 //skip button 946 var skipTooltipButton = document.createElement('a'); 947 skipTooltipButton.className = 'introjs-button introjs-skipbutton'; 948 skipTooltipButton.href = 'javascript:void(0);'; 949 skipTooltipButton.innerHTML = this._options.skipLabel; 950 951 skipTooltipButton.onclick = function() { 952 if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') { 953 self._introCompleteCallback.call(self); 954 } 955 956 if (self._introItems.length - 1 != self._currentStep && typeof (self._introExitCallback) === 'function') { 957 self._introExitCallback.call(self); 958 } 959 960 _exitIntro.call(self, self._targetElement); 961 }; 962 963 buttonsLayer.appendChild(skipTooltipButton); 964 965 //in order to prevent displaying next/previous button always 966 if (this._introItems.length > 1) { 967 buttonsLayer.appendChild(prevTooltipButton); 968 buttonsLayer.appendChild(nextTooltipButton); 969 } 970 971 tooltipLayer.appendChild(buttonsLayer); 972 973 //set proper position 974 _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer); 975 } 976 977 //disable interaction 978 if (this._options.disableInteraction === true) { 979 _disableInteraction.call(self); 980 } 981 982 prevTooltipButton.removeAttribute('tabIndex'); 983 nextTooltipButton.removeAttribute('tabIndex'); 984 985 if (this._currentStep == 0 && this._introItems.length > 1) { 986 prevTooltipButton.className = 'introjs-button introjs-prevbutton introjs-disabled'; 987 prevTooltipButton.tabIndex = '-1'; 988 nextTooltipButton.className = 'introjs-button introjs-nextbutton'; 989 skipTooltipButton.innerHTML = this._options.skipLabel; 990 } else if (this._introItems.length - 1 == this._currentStep || this._introItems.length == 1) { 991 skipTooltipButton.innerHTML = this._options.doneLabel; 992 prevTooltipButton.className = 'introjs-button introjs-prevbutton'; 993 nextTooltipButton.className = 'introjs-button introjs-nextbutton introjs-disabled'; 994 nextTooltipButton.tabIndex = '-1'; 995 } else { 996 prevTooltipButton.className = 'introjs-button introjs-prevbutton'; 997 nextTooltipButton.className = 'introjs-button introjs-nextbutton'; 998 skipTooltipButton.innerHTML = this._options.skipLabel; 999 } 1000 1001 //Set focus on "next" button, so that hitting Enter always moves you onto the next step 1002 nextTooltipButton.focus(); 1003 1004 //add target element position style 1005 targetElement.element.className += ' introjs-showElement'; 1006 1007 var currentElementPosition = _getPropValue(targetElement.element, 'position'); 1008 if (currentElementPosition !== 'absolute' && 1009 currentElementPosition !== 'relative') { 1010 //change to new intro item 1011 targetElement.element.className += ' introjs-relativePosition'; 1012 } 1013 1014 var parentElm = targetElement.element.parentNode; 1015 while (parentElm != null) { 1016 if (parentElm.tagName.toLowerCase() === 'body') break; 1017 1018 //fix The Stacking Contenxt problem. 1019 //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context 1020 var zIndex = _getPropValue(parentElm, 'z-index'); 1021 var opacity = parseFloat(_getPropValue(parentElm, 'opacity')); 1022 var transform = _getPropValue(parentElm, 'transform') || _getPropValue(parentElm, '-webkit-transform') || _getPropValue(parentElm, '-moz-transform') || _getPropValue(parentElm, '-ms-transform') || _getPropValue(parentElm, '-o-transform'); 1023 if (/[0-9]+/.test(zIndex) || opacity < 1 || (transform !== 'none' && transform !== undefined)) { 1024 parentElm.className += ' introjs-fixParent'; 1025 } 1026 1027 parentElm = parentElm.parentNode; 1028 } 1029 1030 if (!_elementInViewport(targetElement.element) && this._options.scrollToElement === true) { 1031 var rect = targetElement.element.getBoundingClientRect(), 1032 winHeight = _getWinSize().height, 1033 top = rect.bottom - (rect.bottom - rect.top), 1034 bottom = rect.bottom - winHeight; 1035 1036 //Scroll up 1037 if (top < 0 || targetElement.element.clientHeight > winHeight) { 1038 window.scrollBy(0, top - 30); // 30px padding from edge to look nice 1039 1040 //Scroll down 1041 } else { 1042 window.scrollBy(0, bottom + 100); // 70px + 30px padding from edge to look nice 1043 } 1044 } 1045 1046 if (typeof (this._introAfterChangeCallback) !== 'undefined') { 1047 this._introAfterChangeCallback.call(this, targetElement.element); 1048 } 1049 } 1050 1051 /** 1052 * Get an element CSS property on the page 1053 * Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml 1054 * 1055 * @api private 1056 * @method _getPropValue 1057 * @param {Object} element 1058 * @param {String} propName 1059 * @returns Element's property value 1060 */ 1061 function _getPropValue (element, propName) { 1062 var propValue = ''; 1063 if (element.currentStyle) { //IE 1064 propValue = element.currentStyle[propName]; 1065 } else if (document.defaultView && document.defaultView.getComputedStyle) { //Others 1066 propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName); 1067 } 1068 1069 //Prevent exception in IE 1070 if (propValue && propValue.toLowerCase) { 1071 return propValue.toLowerCase(); 1072 } else { 1073 return propValue; 1074 } 1075 }; 1076 1077 /** 1078 * Checks to see if target element (or parents) position is fixed or not 1079 * 1080 * @api private 1081 * @method _isFixed 1082 * @param {Object} element 1083 * @returns Boolean 1084 */ 1085 function _isFixed (element) { 1086 var p = element.parentNode; 1087 1088 if (p.nodeName === 'HTML') { 1089 return false;
1090 } 1091 1092 if (_getPropValue(element, 'position') == 'fixed') { 1093 return true; 1094 } 1095 1096 return _isFixed(p); 1097 }; 1098 1099 /** 1100 * Provides a cross-browser way to get the screen dimensions 1101 * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight 1102 * 1103 * @api private 1104 * @method _getWinSize 1105 * @returns {Object} width and height attributes 1106 */ 1107 function _getWinSize() { 1108 if (window.innerWidth != undefined) { 1109 return { width: window.innerWidth, height: window.innerHeight }; 1110 } else { 1111 var D = document.documentElement; 1112 return { width: D.clientWidth, height: D.clientHeight }; 1113 } 1114 } 1115 1116 /** 1117 * Add overlay layer to the page 1118 * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport 1119 * 1120 * @api private 1121 * @method _elementInViewport 1122 * @param {Object} el 1123 */ 1124 function _elementInViewport(el) { 1125 var rect = el.getBoundingClientRect(); 1126 1127 return ( 1128 rect.top >= 0 && 1129 rect.left >= 0 && 1130 (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right 1131 rect.right <= window.innerWidth 1132 ); 1133 } 1134 1135 /** 1136 * Add overlay layer to the page 1137 * 1138 * @api private 1139 * @method _addOverlayLayer 1140 * @param {Object} targetElm 1141 */ 1142 function _addOverlayLayer(targetElm) { 1143 var overlayLayer = document.createElement('div'), 1144 styleText = '', 1145 self = this; 1146 1147 //set css class name 1148 overlayLayer.className = 'introjs-overlay'; 1149 1150 //check if the target element is body, we should calculate the size of overlay layer in a better way 1151 if (targetElm.tagName.toLowerCase() === 'body') { 1152 styleText += 'top: 0;bottom: 0; left: 0;right: 0;position: fixed;'; 1153 overlayLayer.setAttribute('style', styleText); 1154 } else { 1155 //set overlay layer position 1156 var elementPosition = _getOffset(targetElm); 1157 if (elementPosition) { 1158 styleText += 'width: ' + elementPosition.width + 'px; height:' + elementPosition.height + 'px; top:' + elementPosition.top + 'px;left: ' + elementPosition.left + 'px;'; 1159 overlayLayer.setAttribute('style', styleText); 1160 } 1161 } 1162 1163 targetElm.appendChild(overlayLayer); 1164 1165 overlayLayer.onclick = function() { 1166 if (self._options.exitOnOverlayClick == true) { 1167 1168 //check if any callback is defined 1169 if (self._introExitCallback != undefined) { 1170 self._introExitCallback.call(self); 1171 } 1172 _exitIntro.call(self, targetElm); 1173 } 1174 }; 1175 1176 setTimeout(function() { 1177 styleText += 'opacity: ' + self._options.overlayOpacity.toString() + ';'; 1178 overlayLayer.setAttribute('style', styleText); 1179 }, 10); 1180 1181 return true; 1182 }; 1183 1184 /** 1185 * Removes open hint (tooltip hint) 1186 * 1187 * @api private 1188 * @method _removeHintTooltip 1189 */ 1190 function _removeHintTooltip() { 1191 var tooltip = this._targetElement.querySelector('.introjs-hintReference'); 1192 1193 1194 if (tooltip) { 1195 var step = tooltip.getAttribute('data-step'); 1196 tooltip.parentNode.removeChild(tooltip); 1197 return step; 1198 } 1199 }; 1200 1201 /** 1202 * Start parsing hint items 1203 * 1204 * @api private 1205 * @param {Object} targetElm 1206 * @method _startHint 1207 */ 1208 function _populateHints(targetElm) { 1209 var self = this; 1210 this._introItems = [] 1211 1212 if (this._options.hints) { 1213 for (var i = 0, l = this._options.hints.length; i < l; i++) { 1214 var currentItem = _cloneObject(this._options.hints[i]); 1215 1216 if (typeof(currentItem.element) === 'string') { 1217 //grab the element with given selector from the page 1218 currentItem.element = document.querySelector(currentItem.element); 1219 } 1220 1221 currentItem.hintPosition = currentItem.hintPosition || 'top-middle'; 1222 1223 if (currentItem.element != null) { 1224 this._introItems.push(currentItem); 1225 } 1226 } 1227 } else { 1228 var hints = targetElm.querySelectorAll('*[data-hint]'); 1229 1230 if (hints.length < 1) { 1231 return false; 1232 } 1233 1234 //first add intro items with data-step 1235 for (var i = 0, l = hints.length; i < l; i++) { 1236 var currentElement = hints[i]; 1237 1238 this._introItems.push({ 1239 element: currentElement, 1240 hint: currentElement.getAttribute('data-hint'), 1241 hintPosition: currentElement.getAttribute('data-hintPosition') || this._options.hintPosition, 1242 tooltipClass: currentElement.getAttribute('data-tooltipClass'),
1243 position: currentElement.getAttribute('data-position') || this._options.tooltipPosition 1244 }); 1245 } 1246 } 1247 1248 _addHints.call(this); 1249 1250 if (document.addEventListener) { 1251 document.addEventListener('click', _removeHintTooltip.bind(this), false); 1252 //for window resize 1253 window.addEventListener('resize', _reAlignHints.bind(this), true); 1254 } else if (document.attachEvent) { //IE 1255 //for window resize 1256 document.attachEvent('onclick', _removeHintTooltip.bind(this)); 1257 document.attachEvent('onresize', _reAlignHints.bind(this)); 1258 } 1259 }; 1260 1261 /** 1262 * Re-aligns all hint elements 1263 * 1264 * @api private 1265 * @method _reAlignHints 1266 */ 1267 function _reAlignHints() { 1268 for (var i = 0, l = this._introItems.length; i < l; i++) { 1269 var item = this._introItems[i]; 1270 _alignHintPosition.call(this, item.hintPosition, item.element, item.targetElement) 1271 } 1272 } 1273 1274 /** 1275 * Hide a hint 1276 * 1277 * @api private 1278 * @method _hideHint 1279 */ 1280 function _hideHint(stepId) { 1281 _removeHintTooltip.call(this); 1282 var hint = this._targetElement.querySelector('.introjs-hint[data-step="' + stepId + '"]'); 1283 1284 if (hint) { 1285 hint.className += ' introjs-hidehint'; 1286 } 1287 1288 // call the callback function (if any) 1289 if (typeof (this._hintCloseCallback) !== 'undefined') { 1290 this._hintCloseCallback.call(this, stepId); 1291 } 1292 }; 1293 1294 /** 1295 * Add all available hints to the page 1296 * 1297 * @api private 1298 * @method _addHints 1299 */ 1300 function _addHints() { 1301 var self = this; 1302 1303 var oldHintsWrapper = document.querySelector('.introjs-hints'); 1304 1305 if (oldHintsWrapper != null) { 1306 hintsWrapper = oldHintsWrapper; 1307 } else { 1308 var hintsWrapper = document.createElement('div'); 1309 hintsWrapper.className = 'introjs-hints'; 1310 } 1311 1312 for (var i = 0, l = this._introItems.length; i < l; i++) { 1313 var item = this._introItems[i]; 1314 1315 // avoid append a hint twice 1316 if (document.querySelector('.introjs-hint[data-step="' + i + '"]')) 1317 continue; 1318 1319 var hint = document.createElement('a'); 1320 hint.href = "javascript:void(0);"; 1321 1322 (function (hint, item, i) { 1323 // when user clicks on the hint element 1324 hint.onclick = function(e) { 1325 var evt = e ? e : window.event; 1326 if (evt.stopPropagation) evt.stopPropagation(); 1327 if (evt.cancelBubble != null) evt.cancelBubble = true; 1328 1329 _hintClick.call(self, hint, item, i); 1330 }; 1331 }(hint, item, i)); 1332 1333 hint.className = 'introjs-hint'; 1334 1335 // hint's position should be fixed if the target element's position is fixed 1336 if (_isFixed(item.element)) { 1337 hint.className += ' introjs-fixedhint'; 1338 } 1339 1340 var hintDot = document.createElement('div'); 1341 hintDot.className = 'introjs-hint-dot'; 1342 var hintPulse = document.createElement('div'); 1343 hintPulse.className = 'introjs-hint-pulse'; 1344 1345 hint.appendChild(hintDot); 1346 hint.appendChild(hintPulse); 1347 hint.setAttribute('data-step', i); 1348 1349 // we swap the hint element with target element 1350 // because _setHelperLayerPosition uses `element` property 1351 item.targetElement = item.element; 1352 item.element = hint; 1353 1354 // align the hint position 1355 _alignHintPosition.call(this, item.hintPosition, hint, item.targetElement); 1356 1357 hintsWrapper.appendChild(hint); 1358 } 1359 1360 // adding the hints wrapper 1361 document.body.appendChild(hintsWrapper); 1362 1363 // call the callback function (if any) 1364 if (typeof (this._hintsAddedCallback) !== 'undefined') { 1365 this._hintsAddedCallback.call(this); 1366 } 1367 }; 1368 1369 /** 1370 * Aligns hint position 1371 * 1372 * @api private 1373 * @method _alignHintPosition 1374 * @param {String} position 1375 * @param {Object} hint 1376 * @param {Object} element 1377 */ 1378 function _alignHintPosition(position, hint, element) { 1379 // get/calculate offset of target element 1380 var offset = _getOffset.call(this, element); 1381 1382 // align the hint element 1383 switch (position) { 1384 default: 1385 case 'top-left': 1386 hint.style.left = offset.left + 'px'; 1387 hint.style.top = offset.top + 'px'; 1388 break; 1389 case 'top-right': 1390 hint.style.left = (offset.left + offset.width) + 'px'; 1391 hint.style.top = offset.top + 'px'; 1392 break; 1393 case 'bottom-left': 1394 hint.style.left = offset.left + 'px'; 1395 hint.style.top = (offset.top + offset.height) + 'px'; 1396 break; 1397 case 'bottom-right': 1398 hint.style.left = (offset.left + offset.width) + 'px'; 1399 hint.style.top = (offset.top + offset.height) + 'px'; 1400 break; 1401 case 'bottom-middle': 1402 hint.style.left = (offset.left + (offset.width / 2)) + 'px'; 1403 hint.style.top = (offset.top + offset.height) + 'px'; 1404 break; 1405 case 'top-middle': 1406 hint.style.left = (offset.left + (offset.width / 2)) + 'px'; 1407 hint.style.top = offset.top + 'px'; 1408 break; 1409 } 1410 }; 1411 1412 /** 1413 * Triggers when user clicks on the hint element 1414 * 1415 * @api private 1416 * @method _hintClick 1417 * @param {Object} hintElement 1418 * @param {Object} item 1419 * @param {Number} stepId 1420 */
1421 function _hintClick(hintElement, item, stepId) { 1422 // call the callback function (if any) 1423 if (typeof (this._hintClickCallback) !== 'undefined') { 1424 this._hintClickCallback.call(this, hintElement, item, stepId); 1425 } 1426 1427 // remove all open tooltips 1428 var removedStep = _removeHintTooltip.call(this); 1429 1430 // to toggle the tooltip 1431 if (parseInt(removedStep, 10) == stepId) { 1432 return; 1433 } 1434 1435 var tooltipLayer = document.createElement('div'); 1436 var tooltipTextLayer = document.createElement('div'); 1437 var arrowLayer = document.createElement('div'); 1438 var referenceLayer = document.createElement('div'); 1439 1440 tooltipLayer.className = 'introjs-tooltip'; 1441 1442 tooltipLayer.onclick = function (e) { 1443 //IE9 & Other Browsers 1444 if (e.stopPropagation) { 1445 e.stopPropagation(); 1446 } 1447 //IE8 and Lower 1448 else { 1449 e.cancelBubble = true; 1450 } 1451 }; 1452 1453 tooltipTextLayer.className = 'introjs-tooltiptext'; 1454 1455 var tooltipWrapper = document.createElement('p'); 1456 tooltipWrapper.innerHTML = item.hint; 1457 1458 var closeButton = document.createElement('a'); 1459 closeButton.className = 'introjs-button'; 1460 closeButton.innerHTML = this._options.hintButtonLabel; 1461 closeButton.onclick = _hideHint.bind(this, stepId); 1462 1463 tooltipTextLayer.appendChild(tooltipWrapper); 1464 tooltipTextLayer.appendChild(closeButton); 1465 1466 arrowLayer.className = 'introjs-arrow'; 1467 tooltipLayer.appendChild(arrowLayer); 1468 1469 tooltipLayer.appendChild(tooltipTextLayer); 1470 1471 // set current step for _placeTooltip function 1472 this._currentStep = hintElement.getAttribute('data-step'); 1473 1474 // align reference layer position 1475 referenceLayer.className = 'introjs-tooltipReferenceLayer introjs-hintReference'; 1476 referenceLayer.setAttribute('data-step', hintElement.getAttribute('data-step')); 1477 _setHelperLayerPosition.call(this, referenceLayer); 1478 1479 referenceLayer.appendChild(tooltipLayer); 1480 document.body.appendChild(referenceLayer); 1481 1482 //set proper position 1483 _placeTooltip.call(this, hintElement, tooltipLayer, arrowLayer, null, true); 1484 }; 1485 1486 /** 1487 * Get an element position on the page 1488 * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966 1489 * 1490 * @api private 1491 * @method _getOffset 1492 * @param {Object} element 1493 * @returns Element's position info 1494 */ 1495 function _getOffset(element) { 1496 var elementPosition = {}; 1497 1498 //set width 1499 elementPosition.width = element.offsetWidth; 1500 1501 //set height 1502 elementPosition.height = element.offsetHeight; 1503 1504 //calculate element top and left 1505 var _x = 0; 1506 var _y = 0; 1507 while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) { 1508 _x += element.offsetLeft; 1509 _y += element.offsetTop; 1510 element = element.offsetParent; 1511 } 1512 //set top 1513 elementPosition.top = _y; 1514 //set left 1515 elementPosition.left = _x; 1516 1517 return elementPosition; 1518 }; 1519 1520 /** 1521 * Gets the current progress percentage 1522 * 1523 * @api private 1524 * @method _getProgress 1525 * @returns current progress percentage 1526 */ 1527 function _getProgress() { 1528 // Steps are 0 indexed 1529 var currentStep = parseInt((this._currentStep + 1), 10); 1530 return ((currentStep / this._introItems.length) * 100); 1531 }; 1532 1533 /** 1534 * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1 1535 * via: http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically 1536 * 1537 * @param obj1 1538 * @param obj2 1539 * @returns obj3 a new object based on obj1 and obj2 1540 */ 1541 function _mergeOptions(obj1,obj2) { 1542 var obj3 = {}; 1543 for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; } 1544 for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; } 1545 return obj3; 1546 }; 1547 1548 var introJs = function (targetElm) { 1549 if (typeof (targetElm) === 'object') { 1550 //Ok, create a new instance 1551 return new IntroJs(targetElm); 1552 1553 } else if (typeof (targetElm) === 'string') { 1554 //select the target element with query selector 1555 var targetElement = document.querySelector(targetElm); 1556 1557 if (targetElement) { 1558 return new IntroJs(targetElement); 1559 } else { 1560 throw new Error('There is no element with given selector.'); 1561 } 1562 } else { 1563 return new IntroJs(document.body); 1564 } 1565 }; 1566 1567 /** 1568 * Current IntroJs version 1569 * 1570 * @property version
1571 * @type String 1572 */ 1573 introJs.version = VERSION; 1574 1575 //Prototype 1576 introJs.fn = IntroJs.prototype = { 1577 clone: function () { 1578 return new IntroJs(this); 1579 }, 1580 setOption: function(option, value) { 1581 this._options[option] = value; 1582 return this; 1583 }, 1584 setOptions: function(options) { 1585 this._options = _mergeOptions(this._options, options); 1586 return this; 1587 }, 1588 start: function () { 1589 _introForElement.call(this, this._targetElement); 1590 return this; 1591 }, 1592 goToStep: function(step) { 1593 _goToStep.call(this, step); 1594 return this; 1595 }, 1596 nextStep: function() { 1597 _nextStep.call(this); 1598 return this; 1599 }, 1600 previousStep: function() { 1601 _previousStep.call(this); 1602 return this; 1603 }, 1604 exit: function() { 1605 _exitIntro.call(this, this._targetElement); 1606 return this; 1607 }, 1608 refresh: function() { 1609 _setHelperLayerPosition.call(this, document.querySelector('.introjs-helperLayer')); 1610 _setHelperLayerPosition.call(this, document.querySelector('.introjs-tooltipReferenceLayer')); 1611 return this; 1612 }, 1613 onbeforechange: function(providedCallback) { 1614 if (typeof (providedCallback) === 'function') { 1615 this._introBeforeChangeCallback = providedCallback; 1616 } else { 1617 throw new Error('Provided callback for onbeforechange was not a function'); 1618 } 1619 return this; 1620 }, 1621 onchange: function(providedCallback) { 1622 if (typeof (providedCallback) === 'function') { 1623 this._introChangeCallback = providedCallback; 1624 } else { 1625 throw new Error('Provided callback for onchange was not a function.'); 1626 } 1627 return this; 1628 }, 1629 onafterchange: function(providedCallback) { 1630 if (typeof (providedCallback) === 'function') { 1631 this._introAfterChangeCallback = providedCallback; 1632 } else { 1633 throw new Error('Provided callback for onafterchange was not a function'); 1634 } 1635 return this; 1636 }, 1637 oncomplete: function(providedCallback) { 1638 if (typeof (providedCallback) === 'function') { 1639 this._introCompleteCallback = providedCallback; 1640 } else { 1641 throw new Error('Provided callback for oncomplete was not a function.'); 1642 } 1643 return this; 1644 }, 1645 onhintsadded: function(providedCallback) { 1646 if (typeof (providedCallback) === 'function') { 1647 this._hintsAddedCallback = providedCallback; 1648 } else { 1649 throw new Error('Provided callback for onhintsadded was not a function.'); 1650 } 1651 return this; 1652 }, 1653 onhintclick: function(providedCallback) { 1654 if (typeof (providedCallback) === 'function') { 1655 this._hintClickCallback = providedCallback; 1656 } else { 1657 throw new Error('Provided callback for onhintclick was not a function.'); 1658 } 1659 return this; 1660 }, 1661 onhintclose: function(providedCallback) { 1662 if (typeof (providedCallback) === 'function') { 1663 this._hintCloseCallback = providedCallback; 1664 } else { 1665 throw new Error('Provided callback for onhintclose was not a function.'); 1666 } 1667 return this; 1668 }, 1669 onexit: function(providedCallback) { 1670 if (typeof (providedCallback) === 'function') { 1671 this._introExitCallback = providedCallback; 1672 } else { 1673 throw new Error('Provided callback for onexit was not a function.'); 1674 } 1675 return this; 1676 }, 1677 addHints: function() { 1678 _populateHints.call(this, this._targetElement); 1679 return this; 1680 } 1681 }; 1682 1683 exports.introJs = introJs; 1684 return introJs; 1685}));
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.