PageSourceSearch

https://ckiafm.org/assets/vendor/intro-js/intro.js

js ckiafm.org collected 2026-10-02 06:00:25 UTC 56,988 bytes, 1,685 lines download raw bytes

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 = "&nbsp;";
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.