Template:Hong Kong UCCKE/introjs

/**

* Intro.js v2.0
* https://github.com/usablica/intro.js
* MIT licensed
*
* Copyright (C) 2013 usabli.ca - A weekend project by Afshin Mehrabani (@afshinmeh)
*/

(function (root, factory) {

 if (typeof exports === 'object') {
   // CommonJS
   factory(exports);
 } else if (typeof define === 'function' && define.amd) {
   // AMD. Register as an anonymous module.
   define(['exports'], factory);
 } else {
   // Browser globals
   factory(root);
 }

} (this, function (exports) {

 //Default config/variables
 var VERSION = '2.0';
 /**
  * IntroJs main class
  *
  * @class IntroJs
  */
 function IntroJs(obj) {
   this._targetElement = obj;
   this._introItems = [];
   this._options = {
     /* Next button label in tooltip box */
     nextLabel: 'Next →',
     /* Previous button label in tooltip box */
     prevLabel: '← Back',
     /* Skip button label in tooltip box */
     skipLabel: 'Skip',
     /* Done button label in tooltip box */
     doneLabel: 'Done',
     /* Default tooltip box position */
     tooltipPosition: 'bottom',
     /* Next CSS class for tooltip boxes */
     tooltipClass: ,
     /* CSS class that is added to the helperLayer */
     highlightClass: ,
     /* Close introduction when pressing Escape button? */
     exitOnEsc: true,
     /* Close introduction when clicking on overlay layer? */
     exitOnOverlayClick: true,
     /* Show step numbers in introduction? */
     showStepNumbers: true,
     /* Let user use keyboard to navigate the tour? */
     keyboardNavigation: true,
     /* Show tour control buttons? */
     showButtons: true,
     /* Show tour bullets? */
     showBullets: true,
     /* Show tour progress? */
     showProgress: false,
     /* Scroll to highlighted element? */
     scrollToElement: true,
     /* Set the overlay opacity */
     overlayOpacity: 0.8,
     /* Precedence of positions, when auto is enabled */
     positionPrecedence: ["bottom", "top", "right", "left"],
     /* Disable an interaction with element? */
     disableInteraction: false,
     /* Default hint position */
     hintPosition: 'top-middle',
     /* Hint button label */
     hintButtonLabel: 'Got it'
   };
 }
 /**
  * Initiate a new introduction/guide from an element in the page
  *
  * @api private
  * @method _introForElement
  * @param {Object} targetElm
  * @returns {Boolean} Success or not?
  */
 function _introForElement(targetElm) {
   var introItems = [],
       self = this;
   if (this._options.steps) {
     //use steps passed programmatically
     for (var i = 0, stepsLength = this._options.steps.length; i < stepsLength; i++) {
       var currentItem = _cloneObject(this._options.steps[i]);
       //set the step
       currentItem.step = introItems.length + 1;
       //use querySelector function only when developer used CSS selector
       if (typeof(currentItem.element) === 'string') {
         //grab the element with given selector from the page
         currentItem.element = document.querySelector(currentItem.element);
       }
       //intro without element
       if (typeof(currentItem.element) === 'undefined' || currentItem.element == null) {
         var floatingElementQuery = document.querySelector(".introjsFloatingElement");
         if (floatingElementQuery == null) {
           floatingElementQuery = document.createElement('div');
           floatingElementQuery.className = 'introjsFloatingElement';
           document.body.appendChild(floatingElementQuery);
         }
         currentItem.element  = floatingElementQuery;
         currentItem.position = 'floating';
       }
       if (currentItem.element != null) {
         introItems.push(currentItem);
       }
     }
   } else {
     //use steps from data-* annotations
     var allIntroSteps = targetElm.querySelectorAll('*[data-intro]');
     //if there's no element to intro
     if (allIntroSteps.length < 1) {
       return false;
     }
     //first add intro items with data-step
     for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
       var currentElement = allIntroSteps[i];
       var step = parseInt(currentElement.getAttribute('data-step'), 10);
       if (step > 0) {
         introItems[step - 1] = {
           element: currentElement,
           intro: currentElement.getAttribute('data-intro'),
           step: parseInt(currentElement.getAttribute('data-step'), 10),
           tooltipClass: currentElement.getAttribute('data-tooltipClass'),
           highlightClass: currentElement.getAttribute('data-highlightClass'),
           position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
         };
       }
     }
     //next add intro items without data-step
     //todo: we need a cleanup here, two loops are redundant
     var nextStep = 0;
     for (var i = 0, elmsLength = allIntroSteps.length; i < elmsLength; i++) {
       var currentElement = allIntroSteps[i];
       if (currentElement.getAttribute('data-step') == null) {
         while (true) {
           if (typeof introItems[nextStep] == 'undefined') {
             break;
           } else {
             nextStep++;
           }
         }
         introItems[nextStep] = {
           element: currentElement,
           intro: currentElement.getAttribute('data-intro'),
           step: nextStep + 1,
           tooltipClass: currentElement.getAttribute('data-tooltipClass'),
           highlightClass: currentElement.getAttribute('data-highlightClass'),
           position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
         };
       }
     }
   }
   //removing undefined/null elements
   var tempIntroItems = [];
   for (var z = 0; z < introItems.length; z++) {
     introItems[z] && tempIntroItems.push(introItems[z]);  // copy non-empty values to the end of the array
   }
   introItems = tempIntroItems;
   //Ok, sort all items with given steps
   introItems.sort(function (a, b) {
     return a.step - b.step;
   });
   //set it to the introJs object
   self._introItems = introItems;
   //add overlay layer to the page
   if(_addOverlayLayer.call(self, targetElm)) {
     //then, start the show
     _nextStep.call(self);
     var skipButton     = targetElm.querySelector('.introjs-skipbutton'),
         nextStepButton = targetElm.querySelector('.introjs-nextbutton');
     self._onKeyDown = function(e) {
       if (e.keyCode === 27 && self._options.exitOnEsc == true) {
         //escape key pressed, exit the intro
         //check if exit callback is defined
         if (self._introExitCallback != undefined) {
           self._introExitCallback.call(self);
         }
         _exitIntro.call(self, targetElm);
       } else if(e.keyCode === 37) {
         //left arrow
         _previousStep.call(self);
       } else if (e.keyCode === 39) {
         //right arrow
         _nextStep.call(self);
       } else if (e.keyCode === 13) {
         //srcElement === ie
         var target = e.target || e.srcElement;
         if (target && target.className.indexOf('introjs-prevbutton') > 0) {
           //user hit enter while focusing on previous button
           _previousStep.call(self);
         } else if (target && target.className.indexOf('introjs-skipbutton') > 0) {
           //user hit enter while focusing on skip button
           if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') {
               self._introCompleteCallback.call(self);
           }
           //check if any callback is defined
           if (self._introExitCallback != undefined) {
             self._introExitCallback.call(self);
           }
           _exitIntro.call(self, targetElm);
         } else {
           //default behavior for responding to enter
           _nextStep.call(self);
         }
         //prevent default behaviour on hitting Enter, to prevent steps being skipped in some browsers
         if(e.preventDefault) {
           e.preventDefault();
         } else {
           e.returnValue = false;
         }
       }
     };
     self._onResize = function(e) {
       _setHelperLayerPosition.call(self, document.querySelector('.introjs-helperLayer'));
       _setHelperLayerPosition.call(self, document.querySelector('.introjs-tooltipReferenceLayer'));
     };
     if (window.addEventListener) {
       if (this._options.keyboardNavigation) {
         window.addEventListener('keydown', self._onKeyDown, true);
       }
       //for window resize
       window.addEventListener('resize', self._onResize, true);
     } else if (document.attachEvent) { //IE
       if (this._options.keyboardNavigation) {
         document.attachEvent('onkeydown', self._onKeyDown);
       }
       //for window resize
       document.attachEvent('onresize', self._onResize);
     }
   }
   return false;
 }
/*
  * makes a copy of the object
  * @api private
  * @method _cloneObject
 */
 function _cloneObject(object) {
     if (object == null || typeof (object) != 'object' || typeof (object.nodeType) != 'undefined') {
       return object;
     }
     var temp = {};
     for (var key in object) {
       if (typeof (jQuery) != 'undefined' && object[key] instanceof jQuery) {
         temp[key] = object[key];
       } else {
         temp[key] = _cloneObject(object[key]);
       }
     }
     return temp;
 }
 /**
  * Go to specific step of introduction
  *
  * @api private
  * @method _goToStep
  */
 function _goToStep(step) {
   //because steps starts with zero
   this._currentStep = step - 2;
   if (typeof (this._introItems) !== 'undefined') {
     _nextStep.call(this);
   }
 }
 /**
  * Go to next step on intro
  *
  * @api private
  * @method _nextStep
  */
 function _nextStep() {
   this._direction = 'forward';
   if (typeof (this._currentStep) === 'undefined') {
     this._currentStep = 0;
   } else {
     ++this._currentStep;
   }
   if ((this._introItems.length) <= this._currentStep) {
     //end of the intro
     //check if any callback is defined
     if (typeof (this._introCompleteCallback) === 'function') {
       this._introCompleteCallback.call(this);
     }
     _exitIntro.call(this, this._targetElement);
     return;
   }
   var nextStep = this._introItems[this._currentStep];
   if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
     this._introBeforeChangeCallback.call(this, nextStep.element);
   }
   _showElement.call(this, nextStep);
 }
 /**
  * Go to previous step on intro
  *
  * @api private
  * @method _nextStep
  */
 function _previousStep() {
   this._direction = 'backward';
   if (this._currentStep === 0) {
     return false;
   }
   var nextStep = this._introItems[--this._currentStep];
   if (typeof (this._introBeforeChangeCallback) !== 'undefined') {
     this._introBeforeChangeCallback.call(this, nextStep.element);
   }
   _showElement.call(this, nextStep);
 }
 /**
  * Exit from intro
  *
  * @api private
  * @method _exitIntro
  * @param {Object} targetElement
  */
 function _exitIntro(targetElement) {
   //remove overlay layer from the page
   var overlayLayer = targetElement.querySelector('.introjs-overlay');
   //return if intro already completed or skipped
   if (overlayLayer == null) {
     return;
   }
   //for fade-out animation
   overlayLayer.style.opacity = 0;
   setTimeout(function () {
     if (overlayLayer.parentNode) {
       overlayLayer.parentNode.removeChild(overlayLayer);
     }
   }, 500);
   //remove all helper layers
   var helperLayer = targetElement.querySelector('.introjs-helperLayer');
   if (helperLayer) {
     helperLayer.parentNode.removeChild(helperLayer);
   }
   var referenceLayer = targetElement.querySelector('.introjs-tooltipReferenceLayer');
   if (referenceLayer) {
     referenceLayer.parentNode.removeChild(referenceLayer);
   }
   //remove disableInteractionLayer
   var disableInteractionLayer = targetElement.querySelector('.introjs-disableInteraction');
   if (disableInteractionLayer) {
     disableInteractionLayer.parentNode.removeChild(disableInteractionLayer);
   }
   //remove intro floating element
   var floatingElement = document.querySelector('.introjsFloatingElement');
   if (floatingElement) {
     floatingElement.parentNode.removeChild(floatingElement);
   }
   //remove `introjs-showElement` class from the element
   var showElement = document.querySelector('.introjs-showElement');
   if (showElement) {
     showElement.className = showElement.className.replace(/introjs-[a-zA-Z]+/g, ).replace(/^\s+|\s+$/g, ); // This is a manual trim.
   }
   //remove `introjs-fixParent` class from the elements
   var fixParents = document.querySelectorAll('.introjs-fixParent');
   if (fixParents && fixParents.length > 0) {
     for (var i = fixParents.length - 1; i >= 0; i--) {
       fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, ).replace(/^\s+|\s+$/g, );
     }
   }
   //clean listeners
   if (window.removeEventListener) {
     window.removeEventListener('keydown', this._onKeyDown, true);
   } else if (document.detachEvent) { //IE
     document.detachEvent('onkeydown', this._onKeyDown);
   }
   //set the step to zero
   this._currentStep = undefined;
 }
 /**
  * Render tooltip box in the page
  *
  * @api private
  * @method _placeTooltip
  * @param {HTMLElement} targetElement
  * @param {HTMLElement} tooltipLayer
  * @param {HTMLElement} arrowLayer
  * @param {HTMLElement} helperNumberLayer
  * @param {Boolean} hintMode
  */
 function _placeTooltip(targetElement, tooltipLayer, arrowLayer, helperNumberLayer, hintMode) {
   var tooltipCssClass = ,
       currentStepObj,
       tooltipOffset,
       targetOffset,
       windowSize,
       currentTooltipPosition;
   hintMode = hintMode || false;
   //reset the old style
   tooltipLayer.style.top        = null;
   tooltipLayer.style.right      = null;
   tooltipLayer.style.bottom     = null;
   tooltipLayer.style.left       = null;
   tooltipLayer.style.marginLeft = null;
   tooltipLayer.style.marginTop  = null;
   arrowLayer.style.display = 'inherit';
   if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) {
     helperNumberLayer.style.top  = null;
     helperNumberLayer.style.left = null;
   }
   //prevent error when `this._currentStep` is undefined
   if (!this._introItems[this._currentStep]) return;
   //if we have a custom css class for each step
   currentStepObj = this._introItems[this._currentStep];
   if (typeof (currentStepObj.tooltipClass) === 'string') {
     tooltipCssClass = currentStepObj.tooltipClass;
   } else {
     tooltipCssClass = this._options.tooltipClass;
   }
   tooltipLayer.className = ('introjs-tooltip ' + tooltipCssClass).replace(/^\s+|\s+$/g, );
   currentTooltipPosition = this._introItems[this._currentStep].position;
   if ((currentTooltipPosition == "auto" || this._options.tooltipPosition == "auto")) {
     if (currentTooltipPosition != "floating") { // Floating is always valid, no point in calculating
       currentTooltipPosition = _determineAutoPosition.call(this, targetElement, tooltipLayer, currentTooltipPosition);
     }
   }
   targetOffset  = _getOffset(targetElement);
   tooltipOffset = _getOffset(tooltipLayer);
   windowSize    = _getWinSize();
   switch (currentTooltipPosition) {
     case 'top':
       arrowLayer.className = 'introjs-arrow bottom';
       if (hintMode) {
         var tooltipLayerStyleLeft = 0;
       } else {
         var tooltipLayerStyleLeft = 15;
       }
       _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
       tooltipLayer.style.bottom = (targetOffset.height +  20) + 'px';
       break;
     case 'right':
       tooltipLayer.style.left = (targetOffset.width + 20) + 'px';
       if (targetOffset.top + tooltipOffset.height > windowSize.height) {
         // In this case, right would have fallen below the bottom of the screen.
         // Modify so that the bottom of the tooltip connects with the target
         arrowLayer.className = "introjs-arrow left-bottom";
         tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
       } else {
         arrowLayer.className = 'introjs-arrow left';
       }
       break;
     case 'left':
       if (!hintMode && this._options.showStepNumbers == true) {
         tooltipLayer.style.top = '15px';
       }
       if (targetOffset.top + tooltipOffset.height > windowSize.height) {
         // In this case, left would have fallen below the bottom of the screen.
         // Modify so that the bottom of the tooltip connects with the target
         tooltipLayer.style.top = "-" + (tooltipOffset.height - targetOffset.height - 20) + "px";
         arrowLayer.className = 'introjs-arrow right-bottom';
       } else {
         arrowLayer.className = 'introjs-arrow right';
       }
       tooltipLayer.style.right = (targetOffset.width + 20) + 'px';
       break;
     case 'floating':
       arrowLayer.style.display = 'none';
       //we have to adjust the top and left of layer manually for intro items without element
       tooltipLayer.style.left   = '50%';
       tooltipLayer.style.top    = '50%';
       tooltipLayer.style.marginLeft = '-' + (tooltipOffset.width / 2)  + 'px';
       tooltipLayer.style.marginTop  = '-' + (tooltipOffset.height / 2) + 'px';
       if (typeof(helperNumberLayer) != 'undefined' && helperNumberLayer != null) {
         helperNumberLayer.style.left = '-' + ((tooltipOffset.width / 2) + 18) + 'px';
         helperNumberLayer.style.top  = '-' + ((tooltipOffset.height / 2) + 18) + 'px';
       }
       break;
     case 'bottom-right-aligned':
       arrowLayer.className      = 'introjs-arrow top-right';
       var tooltipLayerStyleRight = 0;
       _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer);
       tooltipLayer.style.top    = (targetOffset.height +  20) + 'px';
       break;
     case 'bottom-middle-aligned':
       arrowLayer.className      = 'introjs-arrow top-middle';
       var tooltipLayerStyleLeftRight = targetOffset.width / 2 - tooltipOffset.width / 2;
       // a fix for middle aligned hints
       if (hintMode) {
         tooltipLayerStyleLeftRight += 5;
       }
       if (_checkLeft(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, tooltipLayer)) {
         tooltipLayer.style.right = null;
         _checkRight(targetOffset, tooltipLayerStyleLeftRight, tooltipOffset, windowSize, tooltipLayer);
       }
       tooltipLayer.style.top = (targetOffset.height + 20) + 'px';
       break;
     case 'bottom-left-aligned':
     // Bottom-left-aligned is the same as the default bottom
     case 'bottom':
     // Bottom going to follow the default behavior
     default:
       arrowLayer.className = 'introjs-arrow top';
       var tooltipLayerStyleLeft = 0;
       _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer);
       tooltipLayer.style.top    = (targetOffset.height +  20) + 'px';
       break;
   }
 }
 /**
  * Set tooltip left so it doesn't go off the right side of the window
  *
  * @return boolean true, if tooltipLayerStyleLeft is ok.  false, otherwise.
  */
 function _checkRight(targetOffset, tooltipLayerStyleLeft, tooltipOffset, windowSize, tooltipLayer) {
   if (targetOffset.left + tooltipLayerStyleLeft + tooltipOffset.width > windowSize.width) {
     // off the right side of the window
     tooltipLayer.style.left = (windowSize.width - tooltipOffset.width - targetOffset.left) + 'px';
     return false;
   }
   tooltipLayer.style.left = tooltipLayerStyleLeft + 'px';
   return true;
 }
 /**
  * Set tooltip right so it doesn't go off the left side of the window
  *
  * @return boolean true, if tooltipLayerStyleRight is ok.  false, otherwise.
  */
 function _checkLeft(targetOffset, tooltipLayerStyleRight, tooltipOffset, tooltipLayer) {
   if (targetOffset.left + targetOffset.width - tooltipLayerStyleRight - tooltipOffset.width < 0) {
     // off the left side of the window
     tooltipLayer.style.left = (-targetOffset.left) + 'px';
     return false;
   }
   tooltipLayer.style.right = tooltipLayerStyleRight + 'px';
   return true;
 }
 /**
  * Determines the position of the tooltip based on the position precedence and availability
  * of screen space.
  *
  * @param {Object} targetElement
  * @param {Object} tooltipLayer
  * @param {Object} desiredTooltipPosition
  *
  */
 function _determineAutoPosition(targetElement, tooltipLayer, desiredTooltipPosition) {
   // Take a clone of position precedence. These will be the available
   var possiblePositions = this._options.positionPrecedence.slice();
   var windowSize = _getWinSize();
   var tooltipHeight = _getOffset(tooltipLayer).height + 10;
   var tooltipWidth = _getOffset(tooltipLayer).width + 20;
   var targetOffset = _getOffset(targetElement);
   // If we check all the possible areas, and there are no valid places for the tooltip, the element
   // must take up most of the screen real estate. Show the tooltip floating in the middle of the screen.
   var calculatedPosition = "floating";
   // Check if the width of the tooltip + the starting point would spill off the right side of the screen
   // If no, neither bottom or top are valid
   if (targetOffset.left + tooltipWidth > windowSize.width || ((targetOffset.left + (targetOffset.width / 2)) - tooltipWidth) < 0) {
     _removeEntry(possiblePositions, "bottom");
     _removeEntry(possiblePositions, "top");
   } else {
     // Check for space below
     if ((targetOffset.height + targetOffset.top + tooltipHeight) > windowSize.height) {
       _removeEntry(possiblePositions, "bottom");
     }
     // Check for space above
     if (targetOffset.top - tooltipHeight < 0) {
       _removeEntry(possiblePositions, "top");
     }
   }
   // Check for space to the right
   if (targetOffset.width + targetOffset.left + tooltipWidth > windowSize.width) {
     _removeEntry(possiblePositions, "right");
   }
   // Check for space to the left
   if (targetOffset.left - tooltipWidth < 0) {
     _removeEntry(possiblePositions, "left");
   }
   // At this point, our array only has positions that are valid. Pick the first one, as it remains in order
   if (possiblePositions.length > 0) {
     calculatedPosition = possiblePositions[0];
   }
   // If the requested position is in the list, replace our calculated choice with that
   if (desiredTooltipPosition && desiredTooltipPosition != "auto") {
     if (possiblePositions.indexOf(desiredTooltipPosition) > -1) {
       calculatedPosition = desiredTooltipPosition;
     }
   }
   return calculatedPosition;
 }
 /**
  * Remove an entry from a string array if it's there, does nothing if it isn't there.
  *
  * @param {Array} stringArray
  * @param {String} stringToRemove
  */
 function _removeEntry(stringArray, stringToRemove) {
   if (stringArray.indexOf(stringToRemove) > -1) {
     stringArray.splice(stringArray.indexOf(stringToRemove), 1);
   }
 }
 /**
  * Update the position of the helper layer on the screen
  *
  * @api private
  * @method _setHelperLayerPosition
  * @param {Object} helperLayer
  */
 function _setHelperLayerPosition(helperLayer) {
   if (helperLayer) {
     //prevent error when `this._currentStep` in undefined
     if (!this._introItems[this._currentStep]) return;
     var currentElement  = this._introItems[this._currentStep],
         elementPosition = _getOffset(currentElement.element),
         widthHeightPadding = 10;
     // if the target element is fixed, the tooltip should be fixed as well.
     if (_isFixed(currentElement.element)) {
       helperLayer.className += ' introjs-fixedTooltip';
     }
     if (currentElement.position == 'floating') {
       widthHeightPadding = 0;
     }
     //set new position to helper layer
     helperLayer.setAttribute('style', 'width: ' + (elementPosition.width  + widthHeightPadding)  + 'px; ' +
                                       'height:' + (elementPosition.height + widthHeightPadding)  + 'px; ' +
                                       'top:'    + (elementPosition.top    - 5)   + 'px;' +
                                       'left: '  + (elementPosition.left   - 5)   + 'px;');
   }
 }
 /**
  * Add disableinteraction layer and adjust the size and position of the layer
  *
  * @api private
  * @method _disableInteraction
  */
 function _disableInteraction() {
   var disableInteractionLayer = document.querySelector('.introjs-disableInteraction');
   if (disableInteractionLayer === null) {
     disableInteractionLayer = document.createElement('div');
     disableInteractionLayer.className = 'introjs-disableInteraction';
     this._targetElement.appendChild(disableInteractionLayer);
   }
   _setHelperLayerPosition.call(this, disableInteractionLayer);
 }
 /**
  * Show an element on the page
  *
  * @api private
  * @method _showElement
  * @param {Object} targetElement
  */
 function _showElement(targetElement) {
   if (typeof (this._introChangeCallback) !== 'undefined') {
     this._introChangeCallback.call(this, targetElement.element);
   }
   var self = this,
       oldHelperLayer = document.querySelector('.introjs-helperLayer'),
       oldReferenceLayer = document.querySelector('.introjs-tooltipReferenceLayer'),
       highlightClass = 'introjs-helperLayer',
       elementPosition = _getOffset(targetElement.element);
   //check for a current step highlight class
   if (typeof (targetElement.highlightClass) === 'string') {
     highlightClass += (' ' + targetElement.highlightClass);
   }
   //check for options highlight class
   if (typeof (this._options.highlightClass) === 'string') {
     highlightClass += (' ' + this._options.highlightClass);
   }
   if (oldHelperLayer != null) {
     var oldHelperNumberLayer = oldReferenceLayer.querySelector('.introjs-helperNumberLayer'),
         oldtooltipLayer      = oldReferenceLayer.querySelector('.introjs-tooltiptext'),
         oldArrowLayer        = oldReferenceLayer.querySelector('.introjs-arrow'),
         oldtooltipContainer  = oldReferenceLayer.querySelector('.introjs-tooltip'),
         skipTooltipButton    = oldReferenceLayer.querySelector('.introjs-skipbutton'),
         prevTooltipButton    = oldReferenceLayer.querySelector('.introjs-prevbutton'),
         nextTooltipButton    = oldReferenceLayer.querySelector('.introjs-nextbutton');
     //update or reset the helper highlight class
     oldHelperLayer.className = highlightClass;
     //hide the tooltip
     oldtooltipContainer.style.opacity = 0;
     oldtooltipContainer.style.display = "none";
     if (oldHelperNumberLayer != null) {
       var lastIntroItem = this._introItems[(targetElement.step - 2 >= 0 ? targetElement.step - 2 : 0)];
       if (lastIntroItem != null && (this._direction == 'forward' && lastIntroItem.position == 'floating') || (this._direction == 'backward' && targetElement.position == 'floating')) {
         oldHelperNumberLayer.style.opacity = 0;
       }
     }
     //set new position to helper layer
     _setHelperLayerPosition.call(self, oldHelperLayer);
     _setHelperLayerPosition.call(self, oldReferenceLayer);
     //remove `introjs-fixParent` class from the elements
     var fixParents = document.querySelectorAll('.introjs-fixParent');
     if (fixParents && fixParents.length > 0) {
       for (var i = fixParents.length - 1; i >= 0; i--) {
         fixParents[i].className = fixParents[i].className.replace(/introjs-fixParent/g, ).replace(/^\s+|\s+$/g, );
       };
     }
     //remove old classes
     var oldShowElement = document.querySelector('.introjs-showElement');
     oldShowElement.className = oldShowElement.className.replace(/introjs-[a-zA-Z]+/g, ).replace(/^\s+|\s+$/g, );
     //we should wait until the CSS3 transition is competed (it's 0.3 sec) to prevent incorrect `height` and `width` calculation
     if (self._lastShowElementTimer) {
       clearTimeout(self._lastShowElementTimer);
     }
     self._lastShowElementTimer = setTimeout(function() {
       //set current step to the label
       if (oldHelperNumberLayer != null) {
         oldHelperNumberLayer.innerHTML = targetElement.step;
       }
       //set current tooltip text
       oldtooltipLayer.innerHTML = targetElement.intro;
       //set the tooltip position
       oldtooltipContainer.style.display = "block";
       _placeTooltip.call(self, targetElement.element, oldtooltipContainer, oldArrowLayer, oldHelperNumberLayer);
       //change active bullet
       oldReferenceLayer.querySelector('.introjs-bullets li > a.active').className = ;
       oldReferenceLayer.querySelector('.introjs-bullets li > a[data-stepnumber="' + targetElement.step + '"]').className = 'active';
       oldReferenceLayer.querySelector('.introjs-progress .introjs-progressbar').setAttribute('style', 'width:' + _getProgress.call(self) + '%;');
       //show the tooltip
       oldtooltipContainer.style.opacity = 1;
       if (oldHelperNumberLayer) oldHelperNumberLayer.style.opacity = 1;
       //reset button focus
       if (nextTooltipButton.tabIndex === -1) {
         //tabindex of -1 means we are at the end of the tour - focus on skip / done
         skipTooltipButton.focus();
       } else {
         //still in the tour, focus on next
         nextTooltipButton.focus();
       }
     }, 350);
   } else {
     var helperLayer       = document.createElement('div'),
         referenceLayer    = document.createElement('div'),
         arrowLayer        = document.createElement('div'),
         tooltipLayer      = document.createElement('div'),
         tooltipTextLayer  = document.createElement('div'),
         bulletsLayer      = document.createElement('div'),
         progressLayer     = document.createElement('div'),
         buttonsLayer      = document.createElement('div');
     helperLayer.className = highlightClass;
     referenceLayer.className = 'introjs-tooltipReferenceLayer';
     //set new position to helper layer
     _setHelperLayerPosition.call(self, helperLayer);
     _setHelperLayerPosition.call(self, referenceLayer);
     //add helper layer to target element
     this._targetElement.appendChild(helperLayer);
     this._targetElement.appendChild(referenceLayer);
     arrowLayer.className = 'introjs-arrow';
     tooltipTextLayer.className = 'introjs-tooltiptext';
     tooltipTextLayer.innerHTML = targetElement.intro;
     bulletsLayer.className = 'introjs-bullets';
     if (this._options.showBullets === false) {
       bulletsLayer.style.display = 'none';
     }
     var ulContainer = document.createElement('ul');
     for (var i = 0, stepsLength = this._introItems.length; i < stepsLength; i++) {
       var innerLi    = document.createElement('li');
       var anchorLink = document.createElement('a');
       anchorLink.onclick = function() {
         self.goToStep(this.getAttribute('data-stepnumber'));
       };
       if (i === (targetElement.step-1)) anchorLink.className = 'active';
       anchorLink.href = 'javascript:void(0);';
       anchorLink.innerHTML = " ";
       anchorLink.setAttribute('data-stepnumber', this._introItems[i].step);
       innerLi.appendChild(anchorLink);
       ulContainer.appendChild(innerLi);
     }
     bulletsLayer.appendChild(ulContainer);
     progressLayer.className = 'introjs-progress';
     if (this._options.showProgress === false) {
       progressLayer.style.display = 'none';
     }
     var progressBar = document.createElement('div');
     progressBar.className = 'introjs-progressbar';
     progressBar.setAttribute('style', 'width:' + _getProgress.call(this) + '%;');
     progressLayer.appendChild(progressBar);
     buttonsLayer.className = 'introjs-tooltipbuttons';
     if (this._options.showButtons === false) {
       buttonsLayer.style.display = 'none';
     }
     tooltipLayer.className = 'introjs-tooltip';
     tooltipLayer.appendChild(tooltipTextLayer);
     tooltipLayer.appendChild(bulletsLayer);
     tooltipLayer.appendChild(progressLayer);
     //add helper layer number
     if (this._options.showStepNumbers == true) {
       var helperNumberLayer = document.createElement('span');
       helperNumberLayer.className = 'introjs-helperNumberLayer';
       helperNumberLayer.innerHTML = targetElement.step;
       referenceLayer.appendChild(helperNumberLayer);
     }
     tooltipLayer.appendChild(arrowLayer);
     referenceLayer.appendChild(tooltipLayer);
     //next button
     var nextTooltipButton = document.createElement('a');
     nextTooltipButton.onclick = function() {
       if (self._introItems.length - 1 != self._currentStep) {
         _nextStep.call(self);
       }
     };
     nextTooltipButton.href = 'javascript:void(0);';
     nextTooltipButton.innerHTML = this._options.nextLabel;
     //previous button
     var prevTooltipButton = document.createElement('a');
     prevTooltipButton.onclick = function() {
       if (self._currentStep != 0) {
         _previousStep.call(self);
       }
     };
     prevTooltipButton.href = 'javascript:void(0);';
     prevTooltipButton.innerHTML = this._options.prevLabel;
     //skip button
     var skipTooltipButton = document.createElement('a');
     skipTooltipButton.className = 'introjs-button introjs-skipbutton';
     skipTooltipButton.href = 'javascript:void(0);';
     skipTooltipButton.innerHTML = this._options.skipLabel;
     skipTooltipButton.onclick = function() {
       if (self._introItems.length - 1 == self._currentStep && typeof (self._introCompleteCallback) === 'function') {
         self._introCompleteCallback.call(self);
       }
       if (self._introItems.length - 1 != self._currentStep && typeof (self._introExitCallback) === 'function') {
         self._introExitCallback.call(self);
       }
       _exitIntro.call(self, self._targetElement);
     };
     buttonsLayer.appendChild(skipTooltipButton);
     //in order to prevent displaying next/previous button always
     if (this._introItems.length > 1) {
       buttonsLayer.appendChild(prevTooltipButton);
       buttonsLayer.appendChild(nextTooltipButton);
     }
     tooltipLayer.appendChild(buttonsLayer);
     //set proper position
     _placeTooltip.call(self, targetElement.element, tooltipLayer, arrowLayer, helperNumberLayer);
   }
   //disable interaction
   if (this._options.disableInteraction === true) {
     _disableInteraction.call(self);
   }
   prevTooltipButton.removeAttribute('tabIndex');
   nextTooltipButton.removeAttribute('tabIndex');
   if (this._currentStep == 0 && this._introItems.length > 1) {
     prevTooltipButton.className = 'introjs-button introjs-prevbutton introjs-disabled';
     prevTooltipButton.tabIndex = '-1';
     nextTooltipButton.className = 'introjs-button introjs-nextbutton';
     skipTooltipButton.innerHTML = this._options.skipLabel;
   } else if (this._introItems.length - 1 == this._currentStep || this._introItems.length == 1) {
     skipTooltipButton.innerHTML = this._options.doneLabel;
     prevTooltipButton.className = 'introjs-button introjs-prevbutton';
     nextTooltipButton.className = 'introjs-button introjs-nextbutton introjs-disabled';
     nextTooltipButton.tabIndex = '-1';
   } else {
     prevTooltipButton.className = 'introjs-button introjs-prevbutton';
     nextTooltipButton.className = 'introjs-button introjs-nextbutton';
     skipTooltipButton.innerHTML = this._options.skipLabel;
   }
   //Set focus on "next" button, so that hitting Enter always moves you onto the next step
   nextTooltipButton.focus();
   //add target element position style
   targetElement.element.className += ' introjs-showElement';
   var currentElementPosition = _getPropValue(targetElement.element, 'position');
   if (currentElementPosition !== 'absolute' &&
       currentElementPosition !== 'relative') {
     //change to new intro item
     targetElement.element.className += ' introjs-relativePosition';
   }
   var parentElm = targetElement.element.parentNode;
   while (parentElm != null) {
     if (parentElm.tagName.toLowerCase() === 'body') break;
     //fix The Stacking Contenxt problem.
     //More detail: https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Understanding_z_index/The_stacking_context
     var zIndex = _getPropValue(parentElm, 'z-index');
     var opacity = parseFloat(_getPropValue(parentElm, 'opacity'));
     var transform = _getPropValue(parentElm, 'transform') || _getPropValue(parentElm, '-webkit-transform') || _getPropValue(parentElm, '-moz-transform') || _getPropValue(parentElm, '-ms-transform') || _getPropValue(parentElm, '-o-transform');
     if (/[0-9]+/.test(zIndex) || opacity < 1 || (transform !== 'none' && transform !== undefined)) {
       parentElm.className += ' introjs-fixParent';
     }
     parentElm = parentElm.parentNode;
   }
   if (!_elementInViewport(targetElement.element) && this._options.scrollToElement === true) {
     var rect = targetElement.element.getBoundingClientRect(),
       winHeight = _getWinSize().height,
       top = rect.bottom - (rect.bottom - rect.top),
       bottom = rect.bottom - winHeight;
     //Scroll up
     if (top < 0 || targetElement.element.clientHeight > winHeight) {
       window.scrollBy(0, top - 30); // 30px padding from edge to look nice
     //Scroll down
     } else {
       window.scrollBy(0, bottom + 100); // 70px + 30px padding from edge to look nice
     }
   }
   if (typeof (this._introAfterChangeCallback) !== 'undefined') {
     this._introAfterChangeCallback.call(this, targetElement.element);
   }
 }
 /**
  * Get an element CSS property on the page
  * Thanks to JavaScript Kit: http://www.javascriptkit.com/dhtmltutors/dhtmlcascade4.shtml
  *
  * @api private
  * @method _getPropValue
  * @param {Object} element
  * @param {String} propName
  * @returns Element's property value
  */
 function _getPropValue (element, propName) {
   var propValue = ;
   if (element.currentStyle) { //IE
     propValue = element.currentStyle[propName];
   } else if (document.defaultView && document.defaultView.getComputedStyle) { //Others
     propValue = document.defaultView.getComputedStyle(element, null).getPropertyValue(propName);
   }
   //Prevent exception in IE
   if (propValue && propValue.toLowerCase) {
     return propValue.toLowerCase();
   } else {
     return propValue;
   }
 };
 /**
  * Checks to see if target element (or parents) position is fixed or not
  *
  * @api private
  * @method _isFixed
  * @param {Object} element
  * @returns Boolean
  */
 function _isFixed (element) {
   var p = element.parentNode;
   if (p.nodeName === 'HTML') {
     return false;
   }
   if (_getPropValue(element, 'position') == 'fixed') {
     return true;
   }
   return _isFixed(p);
 };
 /**
  * Provides a cross-browser way to get the screen dimensions
  * via: http://stackoverflow.com/questions/5864467/internet-explorer-innerheight
  *
  * @api private
  * @method _getWinSize
  * @returns {Object} width and height attributes
  */
 function _getWinSize() {
   if (window.innerWidth != undefined) {
     return { width: window.innerWidth, height: window.innerHeight };
   } else {
     var D = document.documentElement;
     return { width: D.clientWidth, height: D.clientHeight };
   }
 }
 /**
  * Add overlay layer to the page
  * http://stackoverflow.com/questions/123999/how-to-tell-if-a-dom-element-is-visible-in-the-current-viewport
  *
  * @api private
  * @method _elementInViewport
  * @param {Object} el
  */
 function _elementInViewport(el) {
   var rect = el.getBoundingClientRect();
   return (
     rect.top >= 0 &&
     rect.left >= 0 &&
     (rect.bottom+80) <= window.innerHeight && // add 80 to get the text right
     rect.right <= window.innerWidth
   );
 }
 /**
  * Add overlay layer to the page
  *
  * @api private
  * @method _addOverlayLayer
  * @param {Object} targetElm
  */
 function _addOverlayLayer(targetElm) {
   var overlayLayer = document.createElement('div'),
       styleText = ,
       self = this;
   //set css class name
   overlayLayer.className = 'introjs-overlay';
   //check if the target element is body, we should calculate the size of overlay layer in a better way
   if (targetElm.tagName.toLowerCase() === 'body') {
     styleText += 'top: 0;bottom: 0; left: 0;right: 0;position: fixed;';
     overlayLayer.setAttribute('style', styleText);
   } else {
     //set overlay layer position
     var elementPosition = _getOffset(targetElm);
     if (elementPosition) {
       styleText += 'width: ' + elementPosition.width + 'px; height:' + elementPosition.height + 'px; top:' + elementPosition.top + 'px;left: ' + elementPosition.left + 'px;';
       overlayLayer.setAttribute('style', styleText);
     }
   }
   targetElm.appendChild(overlayLayer);
   overlayLayer.onclick = function() {
     if (self._options.exitOnOverlayClick == true) {
       //check if any callback is defined
       if (self._introExitCallback != undefined) {
         self._introExitCallback.call(self);
       }
       _exitIntro.call(self, targetElm);
     }
   };
   setTimeout(function() {
     styleText += 'opacity: ' + self._options.overlayOpacity.toString() + ';';
     overlayLayer.setAttribute('style', styleText);
   }, 10);
   return true;
 };
 /**
  * Removes open hint (tooltip hint)
  *
  * @api private
  * @method _removeHintTooltip
  */
 function _removeHintTooltip() {
   var tooltip = this._targetElement.querySelector('.introjs-hintReference');


   if (tooltip) {
     var step = tooltip.getAttribute('data-step');
     tooltip.parentNode.removeChild(tooltip);
     return step;
   }
 };
 /**
  * Start parsing hint items
  *
  * @api private
  * @param {Object} targetElm
  * @method _startHint
  */
 function _populateHints(targetElm) {
   var self = this;
   this._introItems = []
   if (this._options.hints) {
     for (var i = 0, l = this._options.hints.length; i < l; i++) {
       var currentItem = _cloneObject(this._options.hints[i]);
       if (typeof(currentItem.element) === 'string') {
         //grab the element with given selector from the page
         currentItem.element = document.querySelector(currentItem.element);
       }
       currentItem.hintPosition = currentItem.hintPosition || 'top-middle';
       if (currentItem.element != null) {
         this._introItems.push(currentItem);
       }
     }
   } else {
     var hints = targetElm.querySelectorAll('*[data-hint]');
     if (hints.length < 1) {
       return false;
     }
     //first add intro items with data-step
     for (var i = 0, l = hints.length; i < l; i++) {
       var currentElement = hints[i];
       this._introItems.push({
         element: currentElement,
         hint: currentElement.getAttribute('data-hint'),
         hintPosition: currentElement.getAttribute('data-hintPosition') || this._options.hintPosition,
         tooltipClass: currentElement.getAttribute('data-tooltipClass'),
         position: currentElement.getAttribute('data-position') || this._options.tooltipPosition
       });
     }
   }
   _addHints.call(this);
   if (document.addEventListener) {
     document.addEventListener('click', _removeHintTooltip.bind(this), false);
     //for window resize
     window.addEventListener('resize', _reAlignHints.bind(this), true);
   } else if (document.attachEvent) { //IE
     //for window resize
     document.attachEvent('onclick', _removeHintTooltip.bind(this));
     document.attachEvent('onresize', _reAlignHints.bind(this));
   }
 };
 /**
  * Re-aligns all hint elements
  *
  * @api private
  * @method _reAlignHints
  */
 function _reAlignHints() {
   for (var i = 0, l = this._introItems.length; i < l; i++) {
     var item = this._introItems[i];
     _alignHintPosition.call(this, item.hintPosition, item.element, item.targetElement)
   }
 }
 /**
  * Hide a hint
  *
  * @api private
  * @method _hideHint
  */
 function _hideHint(stepId) {
   _removeHintTooltip.call(this);
   var hint = this._targetElement.querySelector('.introjs-hint[data-step="' + stepId + '"]');
   if (hint) {
     hint.className += ' introjs-hidehint';
   }
   // call the callback function (if any)
   if (typeof (this._hintCloseCallback) !== 'undefined') {
     this._hintCloseCallback.call(this, stepId);
   }
 };
 /**
  * Add all available hints to the page
  *
  * @api private
  * @method _addHints
  */
 function _addHints() {
   var self = this;
   var oldHintsWrapper = document.querySelector('.introjs-hints');
   if (oldHintsWrapper != null) {
     hintsWrapper = oldHintsWrapper;
   } else {
     var hintsWrapper = document.createElement('div');
     hintsWrapper.className = 'introjs-hints';
   }
   for (var i = 0, l = this._introItems.length; i < l; i++) {
     var item = this._introItems[i];
     // avoid append a hint twice
     if (document.querySelector('.introjs-hint[data-step="' + i + '"]'))
       continue;
     var hint = document.createElement('a');
     hint.href = "javascript:void(0);";
     (function (hint, item, i) {
       // when user clicks on the hint element
       hint.onclick = function(e) {
         var evt = e ? e : window.event;
         if (evt.stopPropagation)    evt.stopPropagation();
         if (evt.cancelBubble != null) evt.cancelBubble = true;
         _hintClick.call(self, hint, item, i);
       };
     }(hint, item, i));
     hint.className = 'introjs-hint';
     // hint's position should be fixed if the target element's position is fixed
     if (_isFixed(item.element)) {
       hint.className += ' introjs-fixedhint';
     }
     var hintDot = document.createElement('div');
     hintDot.className = 'introjs-hint-dot';
     var hintPulse = document.createElement('div');
     hintPulse.className = 'introjs-hint-pulse';
     hint.appendChild(hintDot);
     hint.appendChild(hintPulse);
     hint.setAttribute('data-step', i);
     // we swap the hint element with target element
     // because _setHelperLayerPosition uses `element` property
     item.targetElement = item.element;
     item.element = hint;
     // align the hint position
     _alignHintPosition.call(this, item.hintPosition, hint, item.targetElement);
     hintsWrapper.appendChild(hint);
   }
   // adding the hints wrapper
   document.body.appendChild(hintsWrapper);
   // call the callback function (if any)
   if (typeof (this._hintsAddedCallback) !== 'undefined') {
     this._hintsAddedCallback.call(this);
   }
 };
 /**
  * Aligns hint position
  *
  * @api private
  * @method _alignHintPosition
  * @param {String} position
  * @param {Object} hint
  * @param {Object} element
  */
 function _alignHintPosition(position, hint, element) {
   // get/calculate offset of target element
   var offset = _getOffset.call(this, element);
   // align the hint element
   switch (position) {
     default:
     case 'top-left':
       hint.style.left = offset.left + 'px';
       hint.style.top = offset.top + 'px';
       break;
     case 'top-right':
       hint.style.left = (offset.left + offset.width) + 'px';
       hint.style.top = offset.top + 'px';
       break;
     case 'bottom-left':
       hint.style.left = offset.left + 'px';
       hint.style.top = (offset.top + offset.height) + 'px';
       break;
     case 'bottom-right':
       hint.style.left = (offset.left + offset.width) + 'px';
       hint.style.top = (offset.top + offset.height) + 'px';
       break;
     case 'bottom-middle':
       hint.style.left = (offset.left + (offset.width / 2)) + 'px';
       hint.style.top = (offset.top + offset.height) + 'px';
       break;
     case 'top-middle':
       hint.style.left = (offset.left + (offset.width / 2)) + 'px';
       hint.style.top = offset.top + 'px';
       break;
   }
 };
 /**
  * Triggers when user clicks on the hint element
  *
  * @api private
  * @method _hintClick
  * @param {Object} hintElement
  * @param {Object} item
  * @param {Number} stepId
  */
 function _hintClick(hintElement, item, stepId) {
   // call the callback function (if any)
   if (typeof (this._hintClickCallback) !== 'undefined') {
     this._hintClickCallback.call(this, hintElement, item, stepId);
   }
   // remove all open tooltips
   var removedStep = _removeHintTooltip.call(this);
   // to toggle the tooltip
   if (parseInt(removedStep, 10) == stepId) {
     return;
   }
   var tooltipLayer = document.createElement('div');
   var tooltipTextLayer = document.createElement('div');
   var arrowLayer = document.createElement('div');
   var referenceLayer = document.createElement('div');
   tooltipLayer.className = 'introjs-tooltip';
   tooltipLayer.onclick = function (e) {
     //IE9 & Other Browsers
     if (e.stopPropagation) {
       e.stopPropagation();
     }
     //IE8 and Lower
     else {
       e.cancelBubble = true;
     }
   };
   tooltipTextLayer.className = 'introjs-tooltiptext';
   var tooltipWrapper = document.createElement('p');
   tooltipWrapper.innerHTML = item.hint;
   var closeButton = document.createElement('a');
   closeButton.className = 'introjs-button';
   closeButton.innerHTML = this._options.hintButtonLabel;
   closeButton.onclick = _hideHint.bind(this, stepId);
   tooltipTextLayer.appendChild(tooltipWrapper);
   tooltipTextLayer.appendChild(closeButton);
   arrowLayer.className = 'introjs-arrow';
   tooltipLayer.appendChild(arrowLayer);
   tooltipLayer.appendChild(tooltipTextLayer);
   // set current step for _placeTooltip function
   this._currentStep = hintElement.getAttribute('data-step');
   // align reference layer position
   referenceLayer.className = 'introjs-tooltipReferenceLayer introjs-hintReference';
   referenceLayer.setAttribute('data-step', hintElement.getAttribute('data-step'));
   _setHelperLayerPosition.call(this, referenceLayer);
   referenceLayer.appendChild(tooltipLayer);
   document.body.appendChild(referenceLayer);
   //set proper position
   _placeTooltip.call(this, hintElement, tooltipLayer, arrowLayer, null, true);
 };
 /**
  * Get an element position on the page
  * Thanks to `meouw`: http://stackoverflow.com/a/442474/375966
  *
  * @api private
  * @method _getOffset
  * @param {Object} element
  * @returns Element's position info
  */
 function _getOffset(element) {
   var elementPosition = {};
   //set width
   elementPosition.width = element.offsetWidth;
   //set height
   elementPosition.height = element.offsetHeight;
   //calculate element top and left
   var _x = 0;
   var _y = 0;
   while (element && !isNaN(element.offsetLeft) && !isNaN(element.offsetTop)) {
     _x += element.offsetLeft;
     _y += element.offsetTop;
     element = element.offsetParent;
   }
   //set top
   elementPosition.top = _y;
   //set left
   elementPosition.left = _x;
   return elementPosition;
 };
 /**
  * Gets the current progress percentage
  *
  * @api private
  * @method _getProgress
  * @returns current progress percentage
  */
 function _getProgress() {
   // Steps are 0 indexed
   var currentStep = parseInt((this._currentStep + 1), 10);
   return ((currentStep / this._introItems.length) * 100);
 };
 /**
  * Overwrites obj1's values with obj2's and adds obj2's if non existent in obj1
  * via: http://stackoverflow.com/questions/171251/how-can-i-merge-properties-of-two-javascript-objects-dynamically
  *
  * @param obj1
  * @param obj2
  * @returns obj3 a new object based on obj1 and obj2
  */
 function _mergeOptions(obj1,obj2) {
   var obj3 = {};
   for (var attrname in obj1) { obj3[attrname] = obj1[attrname]; }
   for (var attrname in obj2) { obj3[attrname] = obj2[attrname]; }
   return obj3;
 };
 var introJs = function (targetElm) {
   if (typeof (targetElm) === 'object') {
     //Ok, create a new instance
     return new IntroJs(targetElm);
   } else if (typeof (targetElm) === 'string') {
     //select the target element with query selector
     var targetElement = document.querySelector(targetElm);
     if (targetElement) {
       return new IntroJs(targetElement);
     } else {
       throw new Error('There is no element with given selector.');
     }
   } else {
     return new IntroJs(document.body);
   }
 };
 /**
  * Current IntroJs version
  *
  * @property version
  * @type String
  */
 introJs.version = VERSION;
 //Prototype
 introJs.fn = IntroJs.prototype = {
   clone: function () {
     return new IntroJs(this);
   },
   setOption: function(option, value) {
     this._options[option] = value;
     return this;
   },
   setOptions: function(options) {
     this._options = _mergeOptions(this._options, options);
     return this;
   },
   start: function () {
     _introForElement.call(this, this._targetElement);
     return this;
   },
   goToStep: function(step) {
     _goToStep.call(this, step);
     return this;
   },
   nextStep: function() {
     _nextStep.call(this);
     return this;
   },
   previousStep: function() {
     _previousStep.call(this);
     return this;
   },
   exit: function() {
     _exitIntro.call(this, this._targetElement);
     return this;
   },
   refresh: function() {
     _setHelperLayerPosition.call(this, document.querySelector('.introjs-helperLayer'));
     _setHelperLayerPosition.call(this, document.querySelector('.introjs-tooltipReferenceLayer'));
     return this;
   },
   onbeforechange: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._introBeforeChangeCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onbeforechange was not a function');
     }
     return this;
   },
   onchange: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._introChangeCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onchange was not a function.');
     }
     return this;
   },
   onafterchange: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._introAfterChangeCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onafterchange was not a function');
     }
     return this;
   },
   oncomplete: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._introCompleteCallback = providedCallback;
     } else {
       throw new Error('Provided callback for oncomplete was not a function.');
     }
     return this;
   },
   onhintsadded: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._hintsAddedCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onhintsadded was not a function.');
     }
     return this;
   },
   onhintclick: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._hintClickCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onhintclick was not a function.');
     }
     return this;
   },
   onhintclose: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._hintCloseCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onhintclose was not a function.');
     }
     return this;
   },
   onexit: function(providedCallback) {
     if (typeof (providedCallback) === 'function') {
       this._introExitCallback = providedCallback;
     } else {
       throw new Error('Provided callback for onexit was not a function.');
     }
     return this;
   },
   addHints: function() {
     _populateHints.call(this, this._targetElement);
     return this;
   }
 };
 exports.introJs = introJs;
 return introJs;

}));