|
1 | 1 | /**
|
2 |
| - * Intro.js v0.6.0 |
| 2 | + * Intro.js v0.8.0 |
3 | 3 | * https://github.com/usablica/intro.js
|
4 | 4 | * MIT licensed
|
5 | 5 | *
|
|
19 | 19 | }
|
20 | 20 | } (this, function (exports) {
|
21 | 21 | //Default config/variables
|
22 |
| - var VERSION = '0.6.0'; |
| 22 | + var VERSION = '0.8.0'; |
23 | 23 |
|
24 | 24 | /**
|
25 | 25 | * IntroJs main class
|
|
53 | 53 | /* Show tour control buttons? */
|
54 | 54 | showButtons: true,
|
55 | 55 | /* Show tour bullets? */
|
56 |
| - showBullets: true |
| 56 | + showBullets: true, |
| 57 | + /* Scroll to highlighted element? */ |
| 58 | + scrollToElement: true |
57 | 59 | };
|
58 | 60 | }
|
59 | 61 |
|
|
74 | 76 | var allIntroSteps = [];
|
75 | 77 |
|
76 | 78 | for (var i = 0, stepsLength = this._options.steps.length; i < stepsLength; i++) {
|
77 |
| - var currentItem = this._options.steps[i]; |
| 79 | + var currentItem = _cloneObject(this._options.steps[i]); |
78 | 80 | //set the step
|
79 |
| - currentItem.step = i + 1; |
| 81 | + currentItem.step = introItems.length + 1; |
80 | 82 | //use querySelector function only when developer used CSS selector
|
81 | 83 | if (typeof(currentItem.element) === 'string') {
|
82 | 84 | //grab the element with given selector from the page
|
83 | 85 | currentItem.element = document.querySelector(currentItem.element);
|
84 | 86 | }
|
85 |
| - introItems.push(currentItem); |
| 87 | + |
| 88 | + //intro without element |
| 89 | + if (typeof(currentItem.element) === 'undefined' || currentItem.element == null) { |
| 90 | + var floatingElementQuery = document.querySelector(".introjsFloatingElement"); |
| 91 | + |
| 92 | + if (floatingElementQuery == null) { |
| 93 | + floatingElementQuery = document.createElement('div'); |
| 94 | + floatingElementQuery.className = 'introjsFloatingElement'; |
| 95 | + |
| 96 | + document.body.appendChild(floatingElementQuery); |
| 97 | + } |
| 98 | + |
| 99 | + currentItem.element = floatingElementQuery; |
| 100 | + currentItem.position = 'floating'; |
| 101 | + } |
| 102 | + |
| 103 | + if (currentItem.element != null) { |
| 104 | + introItems.push(currentItem); |
| 105 | + } |
86 | 106 | }
|
87 | 107 |
|
88 | 108 | } else {
|
|
103 | 123 | element: currentElement,
|
104 | 124 | intro: currentElement.getAttribute('data-intro'),
|
105 | 125 | step: parseInt(currentElement.getAttribute('data-step'), 10),
|
106 |
| - tooltipClass: currentElement.getAttribute('data-tooltipClass'), |
| 126 | + tooltipClass: currentElement.getAttribute('data-tooltipClass'), |
107 | 127 | position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
|
108 | 128 | };
|
109 | 129 | }
|
|
116 | 136 | var currentElement = allIntroSteps[i];
|
117 | 137 |
|
118 | 138 | if (currentElement.getAttribute('data-step') == null) {
|
119 |
| - |
| 139 | + |
120 | 140 | while (true) {
|
121 | 141 | if (typeof introItems[nextStep] == 'undefined') {
|
122 | 142 | break;
|
|
129 | 149 | element: currentElement,
|
130 | 150 | intro: currentElement.getAttribute('data-intro'),
|
131 | 151 | step: nextStep + 1,
|
132 |
| - tooltipClass: currentElement.getAttribute('data-tooltipClass'), |
| 152 | + tooltipClass: currentElement.getAttribute('data-tooltipClass'), |
133 | 153 | position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
|
134 | 154 | };
|
135 | 155 | }
|
|
141 | 161 | for (var z = 0; z < introItems.length; z++) {
|
142 | 162 | introItems[z] && tempIntroItems.push(introItems[z]); // copy non-empty values to the end of the array
|
143 | 163 | }
|
144 |
| - |
| 164 | + |
145 | 165 | introItems = tempIntroItems;
|
146 | 166 |
|
147 | 167 | //Ok, sort all items with given steps
|
|
204 | 224 | return false;
|
205 | 225 | }
|
206 | 226 |
|
| 227 | + /* |
| 228 | + * makes a copy of the object |
| 229 | + * @api private |
| 230 | + * @method _cloneObject |
| 231 | + */ |
| 232 | + function _cloneObject(object) { |
| 233 | + if (object == null || typeof (object) != 'object' || typeof (object.nodeType) != 'undefined') { |
| 234 | + return object; |
| 235 | + } |
| 236 | + var temp = {}; |
| 237 | + for (var key in object) { |
| 238 | + temp[key] = _cloneObject(object[key]); |
| 239 | + } |
| 240 | + return temp; |
| 241 | + } |
207 | 242 | /**
|
208 | 243 | * Go to specific step of introduction
|
209 | 244 | *
|
|
225 | 260 | * @method _nextStep
|
226 | 261 | */
|
227 | 262 | function _nextStep() {
|
| 263 | + this._direction = 'forward'; |
| 264 | + |
228 | 265 | if (typeof (this._currentStep) === 'undefined') {
|
229 | 266 | this._currentStep = 0;
|
230 | 267 | } else {
|
|
256 | 293 | * @method _nextStep
|
257 | 294 | */
|
258 | 295 | function _previousStep() {
|
| 296 | + this._direction = 'backward'; |
| 297 | + |
259 | 298 | if (this._currentStep === 0) {
|
260 | 299 | return false;
|
261 | 300 | }
|
|
278 | 317 | function _exitIntro(targetElement) {
|
279 | 318 | //remove overlay layer from the page
|
280 | 319 | var overlayLayer = targetElement.querySelector('.introjs-overlay');
|
| 320 | + |
| 321 | + //return if intro already completed or skipped |
| 322 | + if (overlayLayer == null) { |
| 323 | + return; |
| 324 | + } |
| 325 | + |
281 | 326 | //for fade-out animation
|
282 | 327 | overlayLayer.style.opacity = 0;
|
283 | 328 | setTimeout(function () {
|
284 | 329 | if (overlayLayer.parentNode) {
|
285 | 330 | overlayLayer.parentNode.removeChild(overlayLayer);
|
286 | 331 | }
|
287 | 332 | }, 500);
|
| 333 | + |
288 | 334 | //remove all helper layers
|
289 | 335 | var helperLayer = targetElement.querySelector('.introjs-helperLayer');
|
290 | 336 | if (helperLayer) {
|
291 | 337 | helperLayer.parentNode.removeChild(helperLayer);
|
292 | 338 | }
|
| 339 | + |
| 340 | + //remove intro floating element |
| 341 | + var floatingElement = document.querySelector('.introjsFloatingElement'); |
| 342 | + if (floatingElement) { |
| 343 | + floatingElement.parentNode.removeChild(floatingElement); |
| 344 | + } |
| 345 | + |
293 | 346 | //remove `introjs-showElement` class from the element
|
294 | 347 | var showElement = document.querySelector('.introjs-showElement');
|
295 | 348 | if (showElement) {
|
|
303 | 356 | fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, '').replace(/^\s+|\s+$/g, '');
|
304 | 357 | };
|
305 | 358 | }
|
| 359 | + |
306 | 360 | //clean listeners
|
307 | 361 | if (window.removeEventListener) {
|
308 | 362 | window.removeEventListener('keydown', this._onKeyDown, true);
|
309 | 363 | } else if (document.detachEvent) { //IE
|
310 | 364 | document.detachEvent('onkeydown', this._onKeyDown);
|
311 | 365 | }
|
| 366 | + |
312 | 367 | //set the step to zero
|
313 | 368 | this._currentStep = undefined;
|
314 | 369 | }
|
|
322 | 377 | * @param {Object} tooltipLayer
|
323 | 378 | * @param {Object} arrowLayer
|
324 | 379 | */
|
325 |
| - function _placeTooltip(targetElement, tooltipLayer, arrowLayer) { |
| 380 | + function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer) { |
326 | 381 | //reset the old style
|
327 |
| - tooltipLayer.style.top = null; |
328 |
| - tooltipLayer.style.right = null; |
329 |
| - tooltipLayer.style.bottom = null; |
330 |
| - tooltipLayer.style.left = null; |
| 382 | + tooltipLayer.style.top = null; |
| 383 | + tooltipLayer.style.right = null; |
| 384 | + tooltipLayer.style.bottom = null; |
| 385 | + tooltipLayer.style.left = null; |
| 386 | + tooltipLayer.style.marginLeft = null; |
| 387 | + tooltipLayer.style.marginTop = null; |
| 388 | + |
| 389 | + arrowLayer.style.display = 'inherit'; |
| 390 | + |
| 391 | + if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) { |
| 392 | + helperNumberLayer.style.top = null; |
| 393 | + helperNumberLayer.style.left = null; |
| 394 | + } |
331 | 395 |
|
332 | 396 | //prevent error when `this._currentStep` is undefined
|
333 | 397 | if (!this._introItems[this._currentStep]) return;
|
|
359 | 423 | arrowLayer.className = 'introjs-arrow left';
|
360 | 424 | break;
|
361 | 425 | case 'left':
|
362 |
| - tooltipLayer.style.top = '15px'; |
| 426 | + if (this._options.showStepNumbers == true) { |
| 427 | + tooltipLayer.style.top = '15px'; |
| 428 | + } |
363 | 429 | tooltipLayer.style.right = (_getOffset(targetElement).width + 20) + 'px';
|
364 | 430 | arrowLayer.className = 'introjs-arrow right';
|
| 431 | + break; |
| 432 | + case 'floating': |
| 433 | + arrowLayer.style.display = 'none'; |
| 434 | + |
| 435 | + //we have to adjust the top and left of layer manually for intro items without element{ |
| 436 | + var tooltipOffset = _getOffset(tooltipLayer); |
| 437 | + |
| 438 | + tooltipLayer.style.left = '50%'; |
| 439 | + tooltipLayer.style.top = '50%'; |
| 440 | + tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2) + 'px'; |
| 441 | + tooltipLayer.style.marginTop = '-' + (tooltipOffset.height / 2) + 'px'; |
| 442 | + |
| 443 | + if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) { |
| 444 | + helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px'; |
| 445 | + helperNumberLayer.style.top = '-' + ((tooltipOffset.height / 2) + 18) + 'px'; |
| 446 | + } |
| 447 | + |
365 | 448 | break;
|
366 | 449 | case 'bottom':
|
367 | 450 | // Bottom going to follow the default behavior
|
|
384 | 467 | //prevent error when `this._currentStep` in undefined
|
385 | 468 | if (!this._introItems[this._currentStep]) return;
|
386 | 469 |
|
387 |
| - var elementPosition = _getOffset(this._introItems[this._currentStep].element); |
| 470 | + var currentElement = this._introItems[this._currentStep]; |
| 471 | + var elementPosition = _getOffset(currentElement.element); |
| 472 | + |
| 473 | + var widthHeightPadding = 10; |
| 474 | + if (currentElement.position == 'floating') { |
| 475 | + widthHeightPadding = 0; |
| 476 | + } |
| 477 | + |
388 | 478 | //set new position to helper layer
|
389 |
| - helperLayer.setAttribute('style', 'width: ' + (elementPosition.width + 10) + 'px; ' + |
390 |
| - 'height:' + (elementPosition.height + 10) + 'px; ' + |
| 479 | + helperLayer.setAttribute('style', 'width: ' + (elementPosition.width + widthHeightPadding) + 'px; ' + |
| 480 | + 'height:' + (elementPosition.height + widthHeightPadding) + 'px; ' + |
391 | 481 | 'top:' + (elementPosition.top - 5) + 'px;' +
|
392 | 482 | 'left: ' + (elementPosition.left - 5) + 'px;');
|
393 | 483 | }
|
|
418 | 508 | skipTooltipButton = oldHelperLayer.querySelector('.introjs-skipbutton'),
|
419 | 509 | prevTooltipButton = oldHelperLayer.querySelector('.introjs-prevbutton'),
|
420 | 510 | nextTooltipButton = oldHelperLayer.querySelector('.introjs-nextbutton');
|
421 |
| - |
| 511 | + |
422 | 512 | //hide the tooltip
|
423 | 513 | oldtooltipContainer.style.opacity = 0;
|
424 | 514 |
|
| 515 | + if (oldHelperNumberLayer != null) { |
| 516 | + var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)]; |
| 517 | + |
| 518 | + if (lastIntroItem != null && (this._direction == 'forward' && lastIntroItem.position == 'floating') || (this._direction == 'backward' && targetElement.position == 'floating')) { |
| 519 | + oldHelperNumberLayer.style.opacity = 0; |
| 520 | + } |
| 521 | + } |
| 522 | + |
425 | 523 | //set new position to helper layer
|
426 | 524 | _setHelperLayerPosition.call(self, oldHelperLayer);
|
427 | 525 |
|
|
448 | 546 | //set current tooltip text
|
449 | 547 | oldtooltipLayer.innerHTML = targetElement.intro;
|
450 | 548 | //set the tooltip position
|
451 |
| - _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer); |
452 |
| - |
| 549 | + _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer); |
| 550 | + |
453 | 551 | //change active bullet
|
454 | 552 | oldHelperLayer.querySelector('.introjs-bullets li > a.active').className = '';
|
455 | 553 | oldHelperLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active';
|
456 | 554 |
|
457 | 555 | //show the tooltip
|
458 | 556 | oldtooltipContainer.style.opacity = 1;
|
| 557 | + oldHelperNumberLayer.style.opacity = 1; |
459 | 558 | }, 350);
|
460 | 559 |
|
461 | 560 | } else {
|
|
478 | 577 |
|
479 | 578 | tooltipTextLayer.className = 'introjs-tooltiptext';
|
480 | 579 | tooltipTextLayer.innerHTML = targetElement.intro;
|
481 |
| - |
| 580 | + |
482 | 581 | bulletsLayer.className = 'introjs-bullets';
|
483 | 582 |
|
484 | 583 | if (this._options.showBullets === false) {
|
|
579 | 678 | tooltipLayer.appendChild(buttonsLayer);
|
580 | 679 |
|
581 | 680 | //set proper position
|
582 |
| - _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer); |
| 681 | + _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer); |
583 | 682 | }
|
584 | 683 |
|
585 |
| - if (this._currentStep == 0) { |
| 684 | + if (this._currentStep == 0 && this._introItems.length > 1) { |
586 | 685 | prevTooltipButton.className = 'introjs-button introjs-prevbutton introjs-disabled';
|
587 | 686 | nextTooltipButton.className = 'introjs-button introjs-nextbutton';
|
588 | 687 | skipTooltipButton.innerHTML = this._options.skipLabel;
|
589 |
| - } else if (this._introItems.length - 1 == this._currentStep) { |
| 688 | + } else if (this._introItems.length - 1 == this._currentStep || this._introItems.length == 1) { |
590 | 689 | skipTooltipButton.innerHTML = this._options.doneLabel;
|
591 | 690 | prevTooltipButton.className = 'introjs-button introjs-prevbutton';
|
592 | 691 | nextTooltipButton.className = 'introjs-button introjs-nextbutton introjs-disabled';
|
|
613 | 712 | while (parentElm != null) {
|
614 | 713 | if (parentElm.tagName.toLowerCase() === 'body') break;
|
615 | 714 |
|
| 715 | + //fix The Stacking Contenxt problem. |
| 716 | + //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context |
616 | 717 | var zIndex = _getPropValue(parentElm, 'z-index');
|
617 |
| - if (/[0-9]+/.test(zIndex)) { |
| 718 | + var opacity = parseFloat(_getPropValue(parentElm, 'opacity')); |
| 719 | + if (/[0-9]+/.test(zIndex) || opacity < 1) { |
618 | 720 | parentElm.className += ' introjs-fixParent';
|
619 | 721 | }
|
| 722 | + |
620 | 723 | parentElm = parentElm.parentNode;
|
621 | 724 | }
|
622 | 725 |
|
623 |
| - if (!_elementInViewport(targetElement.element)) { |
| 726 | + if (!_elementInViewport(targetElement.element) && this._options.scrollToElement === true) { |
624 | 727 | var rect = targetElement.element.getBoundingClientRect(),
|
625 | 728 | winHeight=_getWinSize().height,
|
626 | 729 | top = rect.bottom - (rect.bottom - rect.top),
|
|
635 | 738 | window.scrollBy(0, bottom + 100); // 70px + 30px padding from edge to look nice
|
636 | 739 | }
|
637 | 740 | }
|
| 741 | + |
| 742 | + if (typeof (this._introAfterChangeCallback) !== 'undefined') { |
| 743 | + this._introAfterChangeCallback.call(this, targetElement.element); |
| 744 | + } |
638 | 745 | }
|
639 | 746 |
|
640 | 747 | /**
|
|
844 | 951 | _goToStep.call(this, step);
|
845 | 952 | return this;
|
846 | 953 | },
|
| 954 | + nextStep: function() { |
| 955 | + _nextStep.call(this); |
| 956 | + return this; |
| 957 | + }, |
| 958 | + previousStep: function() { |
| 959 | + _previousStep.call(this); |
| 960 | + return this; |
| 961 | + }, |
847 | 962 | exit: function() {
|
848 | 963 | _exitIntro.call(this, this._targetElement);
|
849 | 964 | },
|
|
867 | 982 | }
|
868 | 983 | return this;
|
869 | 984 | },
|
| 985 | + onafterchange: function(providedCallback) { |
| 986 | + if (typeof (providedCallback) === 'function') { |
| 987 | + this._introAfterChangeCallback = providedCallback; |
| 988 | + } else { |
| 989 | + throw new Error('Provided callback for onafterchange was not a function'); |
| 990 | + } |
| 991 | + return this; |
| 992 | + }, |
870 | 993 | oncomplete: function(providedCallback) {
|
871 | 994 | if (typeof (providedCallback) === 'function') {
|
872 | 995 | this._introCompleteCallback = providedCallback;
|
|
0 commit comments