PluginProbe
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder / 1.1.1
Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder v1.1.1
3.6.0 3.5.0 trunk 1.0.10 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.6.1 1.0.7 1.0.8 1.0.9 1.1 1.1.1 1.2 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 All 110 releases
buttonizer-multifunctional-button / js / intro.js

intro.js in Buttonizer – Floating Menus, Sticky Buttons, & Popup Builder 1.1.1, at js/intro.js

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