diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js
index 7221cad..132370e 100644
--- a/dist/angular-tour-tpls.js
+++ b/dist/angular-tour-tpls.js
@@ -1,7 +1,7 @@
/**
* An AngularJS directive for showcasing features of your website
- * @version v0.2.5 - 2016-03-08
- * @link https://github.com/DaftMonk/angular-tour
+ * @version v0.3.0 - 2016-03-12
+ * @link https://github.com/david-meza/angular-tour
* @author Tyler Henkel
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
@@ -19,559 +19,561 @@
$templateCache.put('tour/tour.tpl.html', '
\n' + '
\n' + '
\n' + '
\n' + '
\n' + '
\n' + '
×\n' + '
\n' + '
\n' + '');
}
]);
- angular.module('angular-tour.tour', []).constant('tourConfig', {
- placement: 'top',
- animation: true,
- nextLabel: 'Next',
- scrollSpeed: 500,
- margin: 28,
- backDrop: false,
- useSourceScope: false,
- containerElement: 'body'
- }).controller('TourController', [
- '$scope',
- 'orderedList',
- function ($scope, orderedList) {
- var self = this, steps = self.steps = orderedList(), firstCurrentStepChange = true;
- // we'll pass these in from the directive
- self.postTourCallback = angular.noop;
- self.postStepCallback = angular.noop;
- self.showStepCallback = angular.noop;
- self.currentStep = -1;
- // if currentStep changes, select the new step
- $scope.$watch(function () {
- return self.currentStep;
- }, function (val) {
- if (firstCurrentStepChange)
- firstCurrentStepChange = false;
- else
- self.select(val);
- });
- self.select = function (nextIndex) {
- if (!angular.isNumber(nextIndex))
- return;
- self.unselectAllSteps();
- var step = steps.get(nextIndex);
- if (step) {
- step.ttOpen = true;
- }
- // update currentStep if we manually selected this index
- if (self.currentStep !== nextIndex) {
- self.currentStep = nextIndex;
- }
- if (self.currentStep > -1) {
- self.showStepCallback();
- }
- if (nextIndex >= steps.getCount()) {
- self.postTourCallback(true);
- }
- self.postStepCallback();
- };
- self.addStep = function (step) {
- if (angular.isNumber(step.index) && !isNaN(step.index))
- steps.set(step.index, step);
- else
- steps.push(step);
- };
- self.unselectAllSteps = function () {
- steps.forEach(function (step) {
- step.ttOpen = false;
+ (function (angular) {
+ angular.module('angular-tour.tour', []).constant('tourConfig', {
+ placement: 'top',
+ animation: true,
+ nextLabel: 'Next',
+ scrollSpeed: 500,
+ margin: 28,
+ backDrop: false,
+ useSourceScope: false,
+ containerElement: 'body'
+ }).controller('TourController', [
+ '$scope',
+ 'orderedList',
+ function ($scope, orderedList) {
+ var self = this, steps = self.steps = orderedList(), firstCurrentStepChange = true;
+ // we'll pass these in from the directive
+ self.postTourCallback = angular.noop;
+ self.postStepCallback = angular.noop;
+ self.showStepCallback = angular.noop;
+ self.currentStep = -1;
+ // if currentStep changes, select the new step
+ $scope.$watch(function () {
+ return self.currentStep;
+ }, function (val) {
+ if (firstCurrentStepChange)
+ firstCurrentStepChange = false;
+ else
+ self.select(val);
});
- };
- self.cancelTour = function () {
- self.unselectAllSteps();
- self.postTourCallback(false);
- };
- $scope.openTour = function () {
- // open at first step if we've already finished tour
- var startStep = self.currentStep >= steps.getCount() || self.currentStep < 0 ? 0 : self.currentStep;
- self.select(startStep);
- };
- $scope.closeTour = function () {
- self.cancelTour();
- };
- }
- ]).directive('tour', [
- '$parse',
- '$timeout',
- 'tourConfig',
- function ($parse, $timeout, tourConfig) {
- return {
- controller: 'TourController',
- restrict: 'EA',
- scope: true,
- link: function (scope, element, attrs, ctrl) {
- if (!angular.isDefined(attrs.step)) {
- throw 'The directive requires a `step` attribute to bind the current step to.';
+ self.select = function (nextIndex) {
+ if (!angular.isNumber(nextIndex))
+ return;
+ self.unselectAllSteps();
+ var step = steps.get(nextIndex);
+ if (step) {
+ step.ttOpen = true;
}
- var model = $parse(attrs.step);
- var backDrop = false;
- // Watch current step view model and update locally
- scope.$watch(attrs.step, function (newVal) {
- ctrl.currentStep = newVal;
+ // update currentStep if we manually selected this index
+ if (self.currentStep !== nextIndex) {
+ self.currentStep = nextIndex;
+ }
+ if (self.currentStep > -1) {
+ self.showStepCallback();
+ }
+ if (nextIndex >= steps.getCount()) {
+ self.postTourCallback(true);
+ }
+ self.postStepCallback();
+ };
+ self.addStep = function (step) {
+ if (angular.isNumber(step.index) && !isNaN(step.index))
+ steps.set(step.index, step);
+ else
+ steps.push(step);
+ };
+ self.unselectAllSteps = function () {
+ steps.forEach(function (step) {
+ step.ttOpen = false;
});
- ctrl.postTourCallback = function (completed) {
- var backdropEle = document.getElementsByClassName('tour-backdrop');
- var active = document.getElementsByClassName('tour-element-active');
- angular.element(backdropEle).remove();
- backDrop = false;
- angular.element(active).removeClass('tour-element-active');
- if (completed && angular.isDefined(attrs.tourComplete)) {
- scope.$parent.$eval(attrs.tourComplete);
- }
- if (angular.isDefined(attrs.postTour)) {
- scope.$parent.$eval(attrs.postTour);
- }
- };
- ctrl.postStepCallback = function () {
- if (angular.isDefined(attrs.postStep)) {
- scope.$parent.$eval(attrs.postStep);
- }
- };
- ctrl.showStepCallback = function () {
- if (tourConfig.backDrop) {
- $timeout(function () {
- var backdrop = document.getElementsByClassName('tour-backdrop');
- var tooltip = document.getElementsByClassName('tour-tip')[0];
- var div = document.createElement('div');
- div.className = 'tour-backdrop';
- angular.element(backdrop).remove();
- // When the tour ends simply remove the backdrop and return.
- if (!angular.isDefined(tooltip)) {
- return;
- }
- tooltip.parentNode.insertBefore(div, tooltip);
- }, 501);
- backDrop = true;
+ };
+ self.cancelTour = function () {
+ self.unselectAllSteps();
+ self.postTourCallback(false);
+ };
+ $scope.openTour = function () {
+ // open at first step if we've already finished tour
+ var startStep = self.currentStep >= steps.getCount() || self.currentStep < 0 ? 0 : self.currentStep;
+ self.select(startStep);
+ };
+ $scope.closeTour = function () {
+ self.cancelTour();
+ };
+ }
+ ]).directive('tour', [
+ '$parse',
+ '$timeout',
+ 'tourConfig',
+ function ($parse, $timeout, tourConfig) {
+ return {
+ controller: 'TourController',
+ restrict: 'EA',
+ scope: true,
+ link: function (scope, element, attrs, ctrl) {
+ if (!angular.isDefined(attrs.step)) {
+ throw 'The directive requires a `step` attribute to bind the current step to.';
}
- };
- // update the current step in the view as well as in our controller
- scope.setCurrentStep = function (val) {
- model.assign(scope.$parent, val);
- ctrl.currentStep = val;
- };
- scope.getCurrentStep = function () {
- return ctrl.currentStep;
- };
- }
- };
- }
- ]).directive('tourtip', [
- '$window',
- '$compile',
- '$interpolate',
- '$timeout',
- 'scrollTo',
- 'tourConfig',
- 'debounce',
- '$q',
- function ($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
- var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol();
- var template = '';
- return {
- require: '^tour',
- restrict: 'EA',
- scope: true,
- link: function (scope, element, attrs, tourCtrl) {
- attrs.$observe('tourtip', function (val) {
- scope.ttContent = val;
- });
- //defaults: tourConfig.placement
- attrs.$observe('tourtipPlacement', function (val) {
- scope.ttPlacement = (val || tourConfig.placement).toLowerCase().trim();
- scope.centered = scope.ttPlacement.indexOf('center') === 0;
- });
- attrs.$observe('tourtipNextLabel', function (val) {
- scope.ttNextLabel = val || tourConfig.nextLabel;
- });
- attrs.$observe('tourtipContainerElement', function (val) {
- scope.ttContainerElement = val || tourConfig.containerElement;
- });
- attrs.$observe('tourtipMargin', function (val) {
- scope.ttMargin = parseInt(val, 10) || tourConfig.margin;
- });
- attrs.$observe('tourtipOffsetVertical', function (val) {
- scope.offsetVertical = parseInt(val, 10) || 0;
- });
- attrs.$observe('tourtipOffsetHorizontal', function (val) {
- scope.offsetHorizontal = parseInt(val, 10) || 0;
- });
- //defaults: null
- attrs.$observe('onShow', function (val) {
- scope.onStepShow = val || null;
- });
- //defaults: null
- attrs.$observe('onProceed', function (val) {
- scope.onStepProceed = val || null;
- });
- //defaults: null
- attrs.$observe('tourtipElement', function (val) {
- scope.ttElement = val || null;
- });
- //defaults: null
- attrs.$observe('tourtipTitle', function (val) {
- scope.ttTitle = val || null;
- });
- //defaults: tourConfig.useSourceScope
- attrs.$observe('useSourceScope', function (val) {
- scope.ttSourceScope = !val ? tourConfig.useSourceScope : val === 'true';
- });
- //Init assignments (fix for Angular 1.3+)
- scope.ttNextLabel = tourConfig.nextLabel;
- scope.ttContainerElement = tourConfig.containerElement;
- scope.ttPlacement = tourConfig.placement.toLowerCase().trim();
- scope.centered = false;
- scope.ttMargin = tourConfig.margin;
- scope.offsetHorizontal = 0;
- scope.offsetVertical = 0;
- scope.ttSourceScope = tourConfig.useSourceScope;
- scope.ttOpen = false;
- scope.ttAnimation = tourConfig.animation;
- scope.index = parseInt(attrs.tourtipStep, 10);
- var tourtip = $compile(template)(scope);
- tourCtrl.addStep(scope);
- // wrap this in a time out because the tourtip won't compile right away
- $timeout(function () {
- scope.$watch('ttOpen', function (val) {
- if (val)
- show();
- else
- hide();
+ var model = $parse(attrs.step);
+ var backDrop = false;
+ // Watch current step view model and update locally
+ scope.$watch(attrs.step, function (newVal) {
+ ctrl.currentStep = newVal;
});
- }, 500);
- //determining target scope. It's used only when using virtual steps and there
- //is some action performed like on-show or on-progress. Without virtual steps
- //action would performed on element's scope and that would work just fine
- //however, when using virtual steps, whose steps can be placed in different
- //controller, so it affects scope, which will be used to run this action against.
- function getTargetScope() {
- var target = document.querySelectorAll(scope.ttElement);
- var targetElement = scope.ttElement ? angular.element(target) : element;
- var targetScope = scope;
- if (targetElement !== element && !scope.ttSourceScope) {
- targetScope = targetElement.scope();
- }
- return targetScope;
+ ctrl.postTourCallback = function (completed) {
+ var backdropEle = document.getElementsByClassName('tour-backdrop');
+ var active = document.getElementsByClassName('tour-element-active');
+ angular.element(backdropEle).remove();
+ backDrop = false;
+ angular.element(active).removeClass('tour-element-active');
+ if (completed && angular.isDefined(attrs.tourComplete)) {
+ scope.$parent.$eval(attrs.tourComplete);
+ }
+ if (angular.isDefined(attrs.postTour)) {
+ scope.$parent.$eval(attrs.postTour);
+ }
+ };
+ ctrl.postStepCallback = function () {
+ if (angular.isDefined(attrs.postStep)) {
+ scope.$parent.$eval(attrs.postStep);
+ }
+ };
+ ctrl.showStepCallback = function () {
+ if (tourConfig.backDrop) {
+ $timeout(function () {
+ var backdrop = document.getElementsByClassName('tour-backdrop');
+ var tooltip = document.getElementsByClassName('tour-tip')[0];
+ var div = document.createElement('div');
+ div.className = 'tour-backdrop';
+ angular.element(backdrop).remove();
+ // When the tour ends simply remove the backdrop and return.
+ if (!angular.isDefined(tooltip)) {
+ return;
+ }
+ tooltip.parentNode.insertBefore(div, tooltip);
+ }, 501);
+ backDrop = true;
+ }
+ };
+ // update the current step in the view as well as in our controller
+ scope.setCurrentStep = function (val) {
+ model.assign(scope.$parent, val);
+ ctrl.currentStep = val;
+ };
+ scope.getCurrentStep = function () {
+ return ctrl.currentStep;
+ };
}
- function calculatePosition(element, container) {
- var minimumLeft = 0;
- // minimum left position of tour tip
- var restrictRight;
- var ttPosition;
- var tourtipWidth = tourtip[0].offsetWidth;
- var tourtipHeight = tourtip[0].offsetHeight;
- // Get the position of the directive element
- var position = element[0].getBoundingClientRect();
- //make it relative against page or fixed container, not the window
- var top = position.top + window.pageYOffset;
- var containerLeft = 0;
- if (container && container[0]) {
- top = top - container[0].getBoundingClientRect().top + container[0].scrollTop;
- // if container is fixed, position tour tip relative to fixed container
- if (container.css('position') === 'fixed') {
- containerLeft = container[0].getBoundingClientRect().left;
+ };
+ }
+ ]).directive('tourtip', [
+ '$window',
+ '$compile',
+ '$interpolate',
+ '$timeout',
+ 'scrollTo',
+ 'tourConfig',
+ 'debounce',
+ '$q',
+ function ($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
+ var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol();
+ var template = '';
+ return {
+ require: '^tour',
+ restrict: 'EA',
+ scope: true,
+ link: function (scope, element, attrs, tourCtrl) {
+ attrs.$observe('tourtip', function (val) {
+ scope.ttContent = val;
+ });
+ //defaults: tourConfig.placement
+ attrs.$observe('tourtipPlacement', function (val) {
+ scope.ttPlacement = (val || tourConfig.placement).toLowerCase().trim();
+ scope.centered = scope.ttPlacement.indexOf('center') === 0;
+ });
+ attrs.$observe('tourtipNextLabel', function (val) {
+ scope.ttNextLabel = val || tourConfig.nextLabel;
+ });
+ attrs.$observe('tourtipContainerElement', function (val) {
+ scope.ttContainerElement = val || tourConfig.containerElement;
+ });
+ attrs.$observe('tourtipMargin', function (val) {
+ scope.ttMargin = parseInt(val, 10) || tourConfig.margin;
+ });
+ attrs.$observe('tourtipOffsetVertical', function (val) {
+ scope.offsetVertical = parseInt(val, 10) || 0;
+ });
+ attrs.$observe('tourtipOffsetHorizontal', function (val) {
+ scope.offsetHorizontal = parseInt(val, 10) || 0;
+ });
+ //defaults: null
+ attrs.$observe('onShow', function (val) {
+ scope.onStepShow = val || null;
+ });
+ //defaults: null
+ attrs.$observe('onProceed', function (val) {
+ scope.onStepProceed = val || null;
+ });
+ //defaults: null
+ attrs.$observe('tourtipElement', function (val) {
+ scope.ttElement = val || null;
+ });
+ //defaults: null
+ attrs.$observe('tourtipTitle', function (val) {
+ scope.ttTitle = val || null;
+ });
+ //defaults: tourConfig.useSourceScope
+ attrs.$observe('useSourceScope', function (val) {
+ scope.ttSourceScope = !val ? tourConfig.useSourceScope : val === 'true';
+ });
+ //Init assignments (fix for Angular 1.3+)
+ scope.ttNextLabel = tourConfig.nextLabel;
+ scope.ttContainerElement = tourConfig.containerElement;
+ scope.ttPlacement = tourConfig.placement.toLowerCase().trim();
+ scope.centered = false;
+ scope.ttMargin = tourConfig.margin;
+ scope.offsetHorizontal = 0;
+ scope.offsetVertical = 0;
+ scope.ttSourceScope = tourConfig.useSourceScope;
+ scope.ttOpen = false;
+ scope.ttAnimation = tourConfig.animation;
+ scope.index = parseInt(attrs.tourtipStep, 10);
+ var tourtip = $compile(template)(scope);
+ tourCtrl.addStep(scope);
+ // wrap this in a time out because the tourtip won't compile right away
+ $timeout(function () {
+ scope.$watch('ttOpen', function (val) {
+ if (val)
+ show();
+ else
+ hide();
+ });
+ }, 500);
+ //determining target scope. It's used only when using virtual steps and there
+ //is some action performed like on-show or on-progress. Without virtual steps
+ //action would performed on element's scope and that would work just fine
+ //however, when using virtual steps, whose steps can be placed in different
+ //controller, so it affects scope, which will be used to run this action against.
+ function getTargetScope() {
+ var target = document.querySelectorAll(scope.ttElement);
+ var targetElement = scope.ttElement ? angular.element(target) : element;
+ var targetScope = scope;
+ if (targetElement !== element && !scope.ttSourceScope) {
+ targetScope = targetElement.scope();
}
- // restrict right position if the tourtip doesn't fit in the container
- var containerWidth = container[0].getBoundingClientRect().width;
- if (tourtipWidth + position.width > containerWidth) {
- restrictRight = containerWidth - position.left + scope.ttMargin;
+ return targetScope;
+ }
+ function calculatePosition(element, container) {
+ var minimumLeft = 0;
+ // minimum left position of tour tip
+ var restrictRight;
+ var ttPosition;
+ var tourtipWidth = tourtip[0].offsetWidth;
+ var tourtipHeight = tourtip[0].offsetHeight;
+ // Get the position of the directive element
+ var position = element[0].getBoundingClientRect();
+ //make it relative against page or fixed container, not the window
+ var top = position.top + window.pageYOffset;
+ var containerLeft = 0;
+ if (container && container[0]) {
+ top = top - container[0].getBoundingClientRect().top + container[0].scrollTop;
+ // if container is fixed, position tour tip relative to fixed container
+ if (container.css('position') === 'fixed') {
+ containerLeft = container[0].getBoundingClientRect().left;
+ }
+ // restrict right position if the tourtip doesn't fit in the container
+ var containerWidth = container[0].getBoundingClientRect().width;
+ if (tourtipWidth + position.width > containerWidth) {
+ restrictRight = containerWidth - position.left + scope.ttMargin;
+ }
}
+ var ttWidth = tourtipWidth;
+ var ttHeight = tourtipHeight;
+ // Calculate the tourtip's top and left coordinates to center it
+ var _left;
+ switch (scope.ttPlacement) {
+ case 'right':
+ _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'bottom':
+ _left = position.left - containerLeft + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + position.height + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'center':
+ _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + 0.5 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'center-top':
+ _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + 0.1 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'left':
+ _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft,
+ right: restrictRight
+ };
+ break;
+ default:
+ _left = position.left - containerLeft + scope.offsetHorizontal;
+ ttPosition = {
+ top: top - ttHeight - scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ }
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+ return ttPosition;
}
- var ttWidth = tourtipWidth;
- var ttHeight = tourtipHeight;
- // Calculate the tourtip's top and left coordinates to center it
- var _left;
- switch (scope.ttPlacement) {
- case 'right':
- _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'bottom':
- _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top + position.height + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center':
- _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
- ttPosition = {
- top: top + 0.5 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center-top':
- _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
- ttPosition = {
- top: top + 0.1 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'left':
- _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft,
- right: restrictRight
- };
- break;
- default:
- _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top - ttHeight - scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
+ function show() {
+ if (!scope.ttContent) {
+ return;
+ }
+ var target = document.querySelectorAll(scope.ttElement);
+ var targetElement = scope.ttElement ? angular.element(target) : element;
+ if (targetElement === null || targetElement.length === 0)
+ throw 'Target element could not be found. Selector: ' + scope.ttElement;
+ var containerEle = document.querySelectorAll(scope.ttContainerElement);
+ angular.element(containerEle).append(tourtip);
+ var updatePosition = function () {
+ var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(containerEle);
+ var ttPosition = calculatePosition(targetElement, offsetElement);
+ // Now set the calculated positioning.
+ tourtip.css(ttPosition);
+ // Scroll to the tour tip
+ scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed);
};
- break;
- }
- ttPosition.top += 'px';
- ttPosition.left += 'px';
- return ttPosition;
- }
- function show() {
- if (!scope.ttContent) {
- return;
+ if (tourConfig.backDrop) {
+ focusActiveElement(targetElement);
+ }
+ angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
+ updatePosition();
+ // CSS class must be added after the element is already on the DOM otherwise it won't animate (fade in).
+ tourtip.addClass('show');
+ if (scope.onStepShow) {
+ var targetScope = getTargetScope();
+ //fancy! Let's make on show action not instantly, but after a small delay
+ $timeout(function () {
+ targetScope.$eval(scope.onStepShow);
+ }, 300);
+ }
}
- var target = document.querySelectorAll(scope.ttElement);
- var targetElement = scope.ttElement ? angular.element(target) : element;
- if (targetElement === null || targetElement.length === 0)
- throw 'Target element could not be found. Selector: ' + scope.ttElement;
- var containerEle = document.querySelectorAll(scope.ttContainerElement);
- angular.element(containerEle).append(tourtip);
- var updatePosition = function () {
- var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(containerEle);
- var ttPosition = calculatePosition(targetElement, offsetElement);
- // Now set the calculated positioning.
- tourtip.css(ttPosition);
- // Scroll to the tour tip
- scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed);
- };
- if (tourConfig.backDrop) {
- focusActiveElement(targetElement);
+ function hide() {
+ tourtip.removeClass('show');
+ tourtip.detach();
+ angular.element($window).unbind('resize.' + scope.$id);
}
- angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
- updatePosition();
- // CSS class must be added after the element is already on the DOM otherwise it won't animate (fade in).
- tourtip.addClass('show');
- if (scope.onStepShow) {
- var targetScope = getTargetScope();
- //fancy! Let's make on show action not instantly, but after a small delay
- $timeout(function () {
- targetScope.$eval(scope.onStepShow);
- }, 300);
+ function focusActiveElement(el) {
+ var activeEle = document.getElementsByClassName('tour-element-active');
+ angular.element(activeEle).removeClass('tour-element-active');
+ if (!scope.centered) {
+ el.addClass('tour-element-active');
+ }
}
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTourtip() {
+ angular.element($window).unbind('resize.' + scope.$id);
+ tourtip.remove();
+ tourtip = null;
+ });
+ scope.proceed = function () {
+ if (scope.onStepProceed) {
+ var targetScope = getTargetScope();
+ var onProceedResult = targetScope.$eval(scope.onStepProceed);
+ $q.resolve(onProceedResult).then(function () {
+ scope.setCurrentStep(scope.getCurrentStep() + 1);
+ });
+ } else {
+ scope.setCurrentStep(scope.getCurrentStep() + 1);
+ }
+ };
}
- function hide() {
- tourtip.removeClass('show');
- tourtip.detach();
- angular.element($window).unbind('resize.' + scope.$id);
- }
- function focusActiveElement(el) {
- var activeEle = document.getElementsByClassName('tour-element-active');
- angular.element(activeEle).removeClass('tour-element-active');
- if (!scope.centered) {
- el.addClass('tour-element-active');
- }
+ };
+ }
+ ]).directive('tourPopup', function () {
+ return {
+ replace: true,
+ templateUrl: 'tour/tour.tpl.html',
+ scope: true,
+ restrict: 'EA',
+ link: function (scope, element, attrs) {
+ }
+ };
+ }).factory('orderedList', function () {
+ var OrderedList = function () {
+ this.map = {};
+ this._array = [];
+ };
+ OrderedList.prototype.set = function (key, value) {
+ if (!angular.isNumber(key))
+ return;
+ if (key in this.map) {
+ this.map[key] = value;
+ } else {
+ if (key < this._array.length) {
+ var insertIndex = key - 1 > 0 ? key - 1 : 0;
+ this._array.splice(insertIndex, 0, key);
+ } else {
+ this._array.push(key);
}
- // Make sure tooltip is destroyed and removed.
- scope.$on('$destroy', function onDestroyTourtip() {
- angular.element($window).unbind('resize.' + scope.$id);
- tourtip.remove();
- tourtip = null;
+ this.map[key] = value;
+ this._array.sort(function (a, b) {
+ return a - b;
});
- scope.proceed = function () {
- if (scope.onStepProceed) {
- var targetScope = getTargetScope();
- var onProceedResult = targetScope.$eval(scope.onStepProceed);
- $q.resolve(onProceedResult).then(function () {
- scope.setCurrentStep(scope.getCurrentStep() + 1);
- });
- } else {
- scope.setCurrentStep(scope.getCurrentStep() + 1);
- }
- };
}
};
- }
- ]).directive('tourPopup', function () {
- return {
- replace: true,
- templateUrl: 'tour/tour.tpl.html',
- scope: true,
- restrict: 'EA',
- link: function (scope, element, attrs) {
- }
- };
- }).factory('orderedList', function () {
- var OrderedList = function () {
- this.map = {};
- this._array = [];
- };
- OrderedList.prototype.set = function (key, value) {
- if (!angular.isNumber(key))
- return;
- if (key in this.map) {
- this.map[key] = value;
- } else {
- if (key < this._array.length) {
- var insertIndex = key - 1 > 0 ? key - 1 : 0;
- this._array.splice(insertIndex, 0, key);
- } else {
- this._array.push(key);
+ OrderedList.prototype.indexOf = function (value) {
+ for (var prop in this.map) {
+ if (this.map.hasOwnProperty(prop)) {
+ if (this.map[prop] === value)
+ return Number(prop);
+ }
}
+ };
+ OrderedList.prototype.push = function (value) {
+ var key = this._array[this._array.length - 1] + 1 || 0;
+ this._array.push(key);
this.map[key] = value;
this._array.sort(function (a, b) {
return a - b;
});
- }
- };
- OrderedList.prototype.indexOf = function (value) {
- for (var prop in this.map) {
- if (this.map.hasOwnProperty(prop)) {
- if (this.map[prop] === value)
- return Number(prop);
- }
- }
- };
- OrderedList.prototype.push = function (value) {
- var key = this._array[this._array.length - 1] + 1 || 0;
- this._array.push(key);
- this.map[key] = value;
- this._array.sort(function (a, b) {
- return a - b;
- });
- };
- OrderedList.prototype.remove = function (key) {
- var index = this._array.indexOf(key);
- if (index === -1) {
- throw new Error('key does not exist');
- }
- this._array.splice(index, 1);
- delete this.map[key];
- };
- OrderedList.prototype.get = function (key) {
- return this.map[key];
- };
- OrderedList.prototype.getCount = function () {
- return this._array.length;
- };
- OrderedList.prototype.forEach = function (f) {
- var key, value;
- for (var i = 0; i < this._array.length; i++) {
- key = this._array[i];
- value = this.map[key];
- f(value, key);
- }
- };
- OrderedList.prototype.first = function () {
- var key, value;
- key = this._array[0];
- value = this.map[key];
- return value;
- };
- var orderedListFactory = function () {
- return new OrderedList();
- };
- return orderedListFactory;
- }).factory('scrollTo', [
- '$interval',
- function ($interval) {
- var animationInProgress = false;
- function getEasingPattern(time) {
- return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // default easeInOutCubic transition
- }
- function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) {
- if (animationInProgress) {
- return;
+ };
+ OrderedList.prototype.remove = function (key) {
+ var index = this._array.indexOf(key);
+ if (index === -1) {
+ throw new Error('key does not exist');
}
- speed = speed || 500;
- offsetY = offsetY || 0;
- offsetX = offsetX || 0;
- // Set some boundaries in case the offset wants us to scroll to impossible locations
- var finalY = endTop + offsetY;
- if (finalY < 0) {
- finalY = 0;
- } else if (finalY > container.scrollHeight) {
- finalY = container.scrollHeight;
+ this._array.splice(index, 1);
+ delete this.map[key];
+ };
+ OrderedList.prototype.get = function (key) {
+ return this.map[key];
+ };
+ OrderedList.prototype.getCount = function () {
+ return this._array.length;
+ };
+ OrderedList.prototype.forEach = function (f) {
+ var key, value;
+ for (var i = 0; i < this._array.length; i++) {
+ key = this._array[i];
+ value = this.map[key];
+ f(value, key);
}
- var finalX = endLeft + offsetX;
- if (finalX < 0) {
- finalX = 0;
- } else if (finalX > container.scrollWidth) {
- finalX = container.scrollWidth;
+ };
+ OrderedList.prototype.first = function () {
+ var key, value;
+ key = this._array[0];
+ value = this.map[key];
+ return value;
+ };
+ var orderedListFactory = function () {
+ return new OrderedList();
+ };
+ return orderedListFactory;
+ }).factory('scrollTo', [
+ '$interval',
+ function ($interval) {
+ var animationInProgress = false;
+ function getEasingPattern(time) {
+ return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // default easeInOutCubic transition
}
- var startTop = container.scrollTop, startLeft = container.scrollLeft, timeLapsed = 0, distanceY = finalY - startTop,
- // If we're going up, this will be a negative number
- distanceX = finalX - startLeft, currentPositionY, currentPositionX, timeProgress;
- function stopAnimation() {
- // If we have reached our destination clear the interval
- if (currentPositionY === finalY && currentPositionX === finalX) {
- $interval.cancel(runAnimation);
- animationInProgress = false;
+ function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) {
+ if (animationInProgress) {
+ return;
}
+ speed = speed || 500;
+ offsetY = offsetY || 0;
+ offsetX = offsetX || 0;
+ // Set some boundaries in case the offset wants us to scroll to impossible locations
+ var finalY = endTop + offsetY;
+ if (finalY < 0) {
+ finalY = 0;
+ } else if (finalY > container.scrollHeight) {
+ finalY = container.scrollHeight;
+ }
+ var finalX = endLeft + offsetX;
+ if (finalX < 0) {
+ finalX = 0;
+ } else if (finalX > container.scrollWidth) {
+ finalX = container.scrollWidth;
+ }
+ var startTop = container.scrollTop, startLeft = container.scrollLeft, timeLapsed = 0, distanceY = finalY - startTop,
+ // If we're going up, this will be a negative number
+ distanceX = finalX - startLeft, currentPositionY, currentPositionX, timeProgress;
+ function stopAnimation() {
+ // If we have reached our destination clear the interval
+ if (currentPositionY === finalY && currentPositionX === finalX) {
+ $interval.cancel(runAnimation);
+ animationInProgress = false;
+ }
+ }
+ function animateScroll() {
+ console.log('called');
+ timeLapsed += 16;
+ // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1
+ timeProgress = timeLapsed / speed;
+ // Make a check and set back to 1 if we went over (e.g. 512/500)
+ timeProgress = timeProgress > 1 ? 1 : timeProgress;
+ // Number between 0 and 1 corresponding to the animation pattern
+ var multiplier = getEasingPattern(timeProgress);
+ // Calculate the distance to travel in this step. It is the total distance times a percentage of what we will move
+ var translateY = distanceY * multiplier;
+ var translateX = distanceX * multiplier;
+ // Assign to the shorthand variables
+ currentPositionY = startTop + translateY;
+ currentPositionX = startLeft + translateX;
+ // Move slightly following the easing pattern
+ container.scrollTop = currentPositionY;
+ container.scrollLeft = currentPositionX;
+ // Check if we have reached our destination
+ stopAnimation();
+ }
+ animationInProgress = true;
+ // Kicks off the function
+ var runAnimation = $interval(animateScroll, 16);
}
- function animateScroll() {
- console.log('called');
- timeLapsed += 16;
- // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1
- timeProgress = timeLapsed / speed;
- // Make a check and set back to 1 if we went over (e.g. 512/500)
- timeProgress = timeProgress > 1 ? 1 : timeProgress;
- // Number between 0 and 1 corresponding to the animation pattern
- var multiplier = getEasingPattern(timeProgress);
- // Calculate the distance to travel in this step. It is the total distance times a percentage of what we will move
- var translateY = distanceY * multiplier;
- var translateX = distanceX * multiplier;
- // Assign to the shorthand variables
- currentPositionY = startTop + translateY;
- currentPositionX = startLeft + translateX;
- // Move slightly following the easing pattern
- container.scrollTop = currentPositionY;
- container.scrollLeft = currentPositionX;
- // Check if we have reached our destination
- stopAnimation();
- }
- animationInProgress = true;
- // Kicks off the function
- var runAnimation = $interval(animateScroll, 16);
+ return function (target, containerSelector, offsetY, offsetX, speed) {
+ var container = document.querySelectorAll(containerSelector);
+ offsetY = offsetY || -100;
+ offsetX = offsetX || -100;
+ _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed);
+ };
}
- return function (target, containerSelector, offsetY, offsetX, speed) {
- var container = document.querySelectorAll(containerSelector);
- offsetY = offsetY || -100;
- offsetX = offsetX || -100;
- _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed);
- };
- }
- ]).factory('debounce', [
- '$timeout',
- '$q',
- function ($timeout, $q) {
- return function (func, wait, immediate) {
- var timeout;
- var deferred = $q.defer();
- return function () {
- var context = this, args = arguments;
- var later = function () {
- timeout = null;
- if (!immediate) {
+ ]).factory('debounce', [
+ '$timeout',
+ '$q',
+ function ($timeout, $q) {
+ return function (func, wait, immediate) {
+ var timeout;
+ var deferred = $q.defer();
+ return function () {
+ var context = this, args = arguments;
+ var later = function () {
+ timeout = null;
+ if (!immediate) {
+ deferred.resolve(func.apply(context, args));
+ deferred = $q.defer();
+ }
+ };
+ var callNow = immediate && !timeout;
+ if (timeout) {
+ $timeout.cancel(timeout);
+ }
+ timeout = $timeout(later, wait);
+ if (callNow) {
deferred.resolve(func.apply(context, args));
deferred = $q.defer();
}
+ return deferred.promise;
};
- var callNow = immediate && !timeout;
- if (timeout) {
- $timeout.cancel(timeout);
- }
- timeout = $timeout(later, wait);
- if (callNow) {
- deferred.resolve(func.apply(context, args));
- deferred = $q.defer();
- }
- return deferred.promise;
};
- };
- }
- ]);
+ }
+ ]);
+ }(angular));
}(window, document));
\ No newline at end of file
diff --git a/dist/angular-tour-tpls.min.js b/dist/angular-tour-tpls.min.js
index e83cc8c..99beecf 100644
--- a/dist/angular-tour-tpls.min.js
+++ b/dist/angular-tour-tpls.min.js
@@ -1 +1 @@
-!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document);
\ No newline at end of file
+!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'\n')}]),function(d){d.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,e=c.steps=b(),f=!0;c.postTourCallback=d.noop,c.postStepCallback=d.noop,c.showStepCallback=d.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){f?f=!1:c.select(a)}),c.select=function(a){if(d.isNumber(a)){c.unselectAllSteps();var b=e.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=e.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){d.isNumber(a.index)&&!isNaN(a.index)?e.set(a.index,a):e.push(a)},c.unselectAllSteps=function(){e.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=e.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,e){return{controller:"TourController",restrict:"EA",scope:!0,link:function(f,g,h,i){if(!d.isDefined(h.step))throw"The directive requires a `step` attribute to bind the current step to.";var j=a(h.step),k=!1;f.$watch(h.step,function(a){i.currentStep=a}),i.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),e=b.getElementsByClassName("tour-element-active");d.element(c).remove(),k=!1,d.element(e).removeClass("tour-element-active"),a&&d.isDefined(h.tourComplete)&&f.$parent.$eval(h.tourComplete),d.isDefined(h.postTour)&&f.$parent.$eval(h.postTour)},i.postStepCallback=function(){d.isDefined(h.postStep)&&f.$parent.$eval(h.postStep)},i.showStepCallback=function(){e.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],e=b.createElement("div");e.className="tour-backdrop",d.element(a).remove(),d.isDefined(c)&&c.parentNode.insertBefore(e,c)},501),k=!0)},f.setCurrentStep=function(a){j.assign(f.$parent,a),i.currentStep=a},f.getCurrentStep=function(){return i.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(e,f,g,h,i,j,k,l){var m=(g.startSymbol(),g.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(g,n,o,p){function q(){var a=b.querySelectorAll(g.ttElement),c=g.ttElement?d.element(a):n,e=g;return c===n||g.ttSourceScope||(e=c.scope()),e}function r(b,c){var d,e,f=0,h=v[0].offsetWidth,i=v[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+g.ttMargin)}var n,o=h,p=i;switch(g.ttPlacement){case"right":n=j.left-l+j.width+g.ttMargin+g.offsetHorizontal,e={top:k+g.offsetVertical,left:n>0?n:f};break;case"bottom":n=j.left-l+g.offsetHorizontal,e={top:k+j.height+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"center":n=j.left-l+.5*(j.width-o)+g.offsetHorizontal,e={top:k+.5*(j.height-p)+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"center-top":n=j.left-l+.5*(j.width-o)+g.offsetHorizontal,e={top:k+.1*(j.height-p)+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"left":n=j.left-l-o-g.ttMargin+g.offsetHorizontal,e={top:k+g.offsetVertical,left:n>0?n:f,right:d};break;default:n=j.left-l+g.offsetHorizontal,e={top:k-p-g.ttMargin+g.offsetVertical,left:n>0?n:f}}return e.top+="px",e.left+="px",e}function s(){if(g.ttContent){var a=b.querySelectorAll(g.ttElement),f=g.ttElement?d.element(a):n;if(null===f||0===f.length)throw"Target element could not be found. Selector: "+g.ttElement;var l=b.querySelectorAll(g.ttContainerElement);d.element(l).append(v);var m=function(){var a="body"===g.ttContainerElement?c:d.element(l),b=r(f,a);v.css(b),i(v,g.ttContainerElement,-150,-300,j.scrollSpeed)};if(j.backDrop&&u(f),d.element(e).bind("resize."+g.$id,k(m,50)),m(),v.addClass("show"),g.onStepShow){var o=q();h(function(){o.$eval(g.onStepShow)},300)}}}function t(){v.removeClass("show"),v.detach(),d.element(e).unbind("resize."+g.$id)}function u(a){var c=b.getElementsByClassName("tour-element-active");d.element(c).removeClass("tour-element-active"),g.centered||a.addClass("tour-element-active")}o.$observe("tourtip",function(a){g.ttContent=a}),o.$observe("tourtipPlacement",function(a){g.ttPlacement=(a||j.placement).toLowerCase().trim(),g.centered=0===g.ttPlacement.indexOf("center")}),o.$observe("tourtipNextLabel",function(a){g.ttNextLabel=a||j.nextLabel}),o.$observe("tourtipContainerElement",function(a){g.ttContainerElement=a||j.containerElement}),o.$observe("tourtipMargin",function(a){g.ttMargin=parseInt(a,10)||j.margin}),o.$observe("tourtipOffsetVertical",function(a){g.offsetVertical=parseInt(a,10)||0}),o.$observe("tourtipOffsetHorizontal",function(a){g.offsetHorizontal=parseInt(a,10)||0}),o.$observe("onShow",function(a){g.onStepShow=a||null}),o.$observe("onProceed",function(a){g.onStepProceed=a||null}),o.$observe("tourtipElement",function(a){g.ttElement=a||null}),o.$observe("tourtipTitle",function(a){g.ttTitle=a||null}),o.$observe("useSourceScope",function(a){g.ttSourceScope=a?"true"===a:j.useSourceScope}),g.ttNextLabel=j.nextLabel,g.ttContainerElement=j.containerElement,g.ttPlacement=j.placement.toLowerCase().trim(),g.centered=!1,g.ttMargin=j.margin,g.offsetHorizontal=0,g.offsetVertical=0,g.ttSourceScope=j.useSourceScope,g.ttOpen=!1,g.ttAnimation=j.animation,g.index=parseInt(o.tourtipStep,10);var v=f(m)(g);p.addStep(g),h(function(){g.$watch("ttOpen",function(a){a?s():t()})},500),g.$on("$destroy",function(){d.element(e).unbind("resize."+g.$id),v.remove(),v=null}),g.proceed=function(){if(g.onStepProceed){var a=q(),b=a.$eval(g.onStepProceed);l.resolve(b).then(function(){g.setCurrentStep(g.getCurrentStep()+1)})}else g.setCurrentStep(g.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(d.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(angular)}(window,document);
\ No newline at end of file
diff --git a/dist/angular-tour.js b/dist/angular-tour.js
index c5899ad..88c8e4e 100644
--- a/dist/angular-tour.js
+++ b/dist/angular-tour.js
@@ -1,7 +1,7 @@
/**
* An AngularJS directive for showcasing features of your website
- * @version v0.2.5 - 2016-03-08
- * @link https://github.com/DaftMonk/angular-tour
+ * @version v0.3.0 - 2016-03-12
+ * @link https://github.com/david-meza/angular-tour
* @author Tyler Henkel
* @license MIT License, http://www.opensource.org/licenses/MIT
*/
@@ -9,559 +9,561 @@
(function (window, document, undefined) {
'use strict';
angular.module('angular-tour', ['angular-tour.tour']);
- angular.module('angular-tour.tour', []).constant('tourConfig', {
- placement: 'top',
- animation: true,
- nextLabel: 'Next',
- scrollSpeed: 500,
- margin: 28,
- backDrop: false,
- useSourceScope: false,
- containerElement: 'body'
- }).controller('TourController', [
- '$scope',
- 'orderedList',
- function ($scope, orderedList) {
- var self = this, steps = self.steps = orderedList(), firstCurrentStepChange = true;
- // we'll pass these in from the directive
- self.postTourCallback = angular.noop;
- self.postStepCallback = angular.noop;
- self.showStepCallback = angular.noop;
- self.currentStep = -1;
- // if currentStep changes, select the new step
- $scope.$watch(function () {
- return self.currentStep;
- }, function (val) {
- if (firstCurrentStepChange)
- firstCurrentStepChange = false;
- else
- self.select(val);
- });
- self.select = function (nextIndex) {
- if (!angular.isNumber(nextIndex))
- return;
- self.unselectAllSteps();
- var step = steps.get(nextIndex);
- if (step) {
- step.ttOpen = true;
- }
- // update currentStep if we manually selected this index
- if (self.currentStep !== nextIndex) {
- self.currentStep = nextIndex;
- }
- if (self.currentStep > -1) {
- self.showStepCallback();
- }
- if (nextIndex >= steps.getCount()) {
- self.postTourCallback(true);
- }
- self.postStepCallback();
- };
- self.addStep = function (step) {
- if (angular.isNumber(step.index) && !isNaN(step.index))
- steps.set(step.index, step);
- else
- steps.push(step);
- };
- self.unselectAllSteps = function () {
- steps.forEach(function (step) {
- step.ttOpen = false;
+ (function (angular) {
+ angular.module('angular-tour.tour', []).constant('tourConfig', {
+ placement: 'top',
+ animation: true,
+ nextLabel: 'Next',
+ scrollSpeed: 500,
+ margin: 28,
+ backDrop: false,
+ useSourceScope: false,
+ containerElement: 'body'
+ }).controller('TourController', [
+ '$scope',
+ 'orderedList',
+ function ($scope, orderedList) {
+ var self = this, steps = self.steps = orderedList(), firstCurrentStepChange = true;
+ // we'll pass these in from the directive
+ self.postTourCallback = angular.noop;
+ self.postStepCallback = angular.noop;
+ self.showStepCallback = angular.noop;
+ self.currentStep = -1;
+ // if currentStep changes, select the new step
+ $scope.$watch(function () {
+ return self.currentStep;
+ }, function (val) {
+ if (firstCurrentStepChange)
+ firstCurrentStepChange = false;
+ else
+ self.select(val);
});
- };
- self.cancelTour = function () {
- self.unselectAllSteps();
- self.postTourCallback(false);
- };
- $scope.openTour = function () {
- // open at first step if we've already finished tour
- var startStep = self.currentStep >= steps.getCount() || self.currentStep < 0 ? 0 : self.currentStep;
- self.select(startStep);
- };
- $scope.closeTour = function () {
- self.cancelTour();
- };
- }
- ]).directive('tour', [
- '$parse',
- '$timeout',
- 'tourConfig',
- function ($parse, $timeout, tourConfig) {
- return {
- controller: 'TourController',
- restrict: 'EA',
- scope: true,
- link: function (scope, element, attrs, ctrl) {
- if (!angular.isDefined(attrs.step)) {
- throw 'The directive requires a `step` attribute to bind the current step to.';
+ self.select = function (nextIndex) {
+ if (!angular.isNumber(nextIndex))
+ return;
+ self.unselectAllSteps();
+ var step = steps.get(nextIndex);
+ if (step) {
+ step.ttOpen = true;
+ }
+ // update currentStep if we manually selected this index
+ if (self.currentStep !== nextIndex) {
+ self.currentStep = nextIndex;
}
- var model = $parse(attrs.step);
- var backDrop = false;
- // Watch current step view model and update locally
- scope.$watch(attrs.step, function (newVal) {
- ctrl.currentStep = newVal;
+ if (self.currentStep > -1) {
+ self.showStepCallback();
+ }
+ if (nextIndex >= steps.getCount()) {
+ self.postTourCallback(true);
+ }
+ self.postStepCallback();
+ };
+ self.addStep = function (step) {
+ if (angular.isNumber(step.index) && !isNaN(step.index))
+ steps.set(step.index, step);
+ else
+ steps.push(step);
+ };
+ self.unselectAllSteps = function () {
+ steps.forEach(function (step) {
+ step.ttOpen = false;
});
- ctrl.postTourCallback = function (completed) {
- var backdropEle = document.getElementsByClassName('tour-backdrop');
- var active = document.getElementsByClassName('tour-element-active');
- angular.element(backdropEle).remove();
- backDrop = false;
- angular.element(active).removeClass('tour-element-active');
- if (completed && angular.isDefined(attrs.tourComplete)) {
- scope.$parent.$eval(attrs.tourComplete);
- }
- if (angular.isDefined(attrs.postTour)) {
- scope.$parent.$eval(attrs.postTour);
- }
- };
- ctrl.postStepCallback = function () {
- if (angular.isDefined(attrs.postStep)) {
- scope.$parent.$eval(attrs.postStep);
- }
- };
- ctrl.showStepCallback = function () {
- if (tourConfig.backDrop) {
- $timeout(function () {
- var backdrop = document.getElementsByClassName('tour-backdrop');
- var tooltip = document.getElementsByClassName('tour-tip')[0];
- var div = document.createElement('div');
- div.className = 'tour-backdrop';
- angular.element(backdrop).remove();
- // When the tour ends simply remove the backdrop and return.
- if (!angular.isDefined(tooltip)) {
- return;
- }
- tooltip.parentNode.insertBefore(div, tooltip);
- }, 501);
- backDrop = true;
+ };
+ self.cancelTour = function () {
+ self.unselectAllSteps();
+ self.postTourCallback(false);
+ };
+ $scope.openTour = function () {
+ // open at first step if we've already finished tour
+ var startStep = self.currentStep >= steps.getCount() || self.currentStep < 0 ? 0 : self.currentStep;
+ self.select(startStep);
+ };
+ $scope.closeTour = function () {
+ self.cancelTour();
+ };
+ }
+ ]).directive('tour', [
+ '$parse',
+ '$timeout',
+ 'tourConfig',
+ function ($parse, $timeout, tourConfig) {
+ return {
+ controller: 'TourController',
+ restrict: 'EA',
+ scope: true,
+ link: function (scope, element, attrs, ctrl) {
+ if (!angular.isDefined(attrs.step)) {
+ throw 'The directive requires a `step` attribute to bind the current step to.';
}
- };
- // update the current step in the view as well as in our controller
- scope.setCurrentStep = function (val) {
- model.assign(scope.$parent, val);
- ctrl.currentStep = val;
- };
- scope.getCurrentStep = function () {
- return ctrl.currentStep;
- };
- }
- };
- }
- ]).directive('tourtip', [
- '$window',
- '$compile',
- '$interpolate',
- '$timeout',
- 'scrollTo',
- 'tourConfig',
- 'debounce',
- '$q',
- function ($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
- var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol();
- var template = '';
- return {
- require: '^tour',
- restrict: 'EA',
- scope: true,
- link: function (scope, element, attrs, tourCtrl) {
- attrs.$observe('tourtip', function (val) {
- scope.ttContent = val;
- });
- //defaults: tourConfig.placement
- attrs.$observe('tourtipPlacement', function (val) {
- scope.ttPlacement = (val || tourConfig.placement).toLowerCase().trim();
- scope.centered = scope.ttPlacement.indexOf('center') === 0;
- });
- attrs.$observe('tourtipNextLabel', function (val) {
- scope.ttNextLabel = val || tourConfig.nextLabel;
- });
- attrs.$observe('tourtipContainerElement', function (val) {
- scope.ttContainerElement = val || tourConfig.containerElement;
- });
- attrs.$observe('tourtipMargin', function (val) {
- scope.ttMargin = parseInt(val, 10) || tourConfig.margin;
- });
- attrs.$observe('tourtipOffsetVertical', function (val) {
- scope.offsetVertical = parseInt(val, 10) || 0;
- });
- attrs.$observe('tourtipOffsetHorizontal', function (val) {
- scope.offsetHorizontal = parseInt(val, 10) || 0;
- });
- //defaults: null
- attrs.$observe('onShow', function (val) {
- scope.onStepShow = val || null;
- });
- //defaults: null
- attrs.$observe('onProceed', function (val) {
- scope.onStepProceed = val || null;
- });
- //defaults: null
- attrs.$observe('tourtipElement', function (val) {
- scope.ttElement = val || null;
- });
- //defaults: null
- attrs.$observe('tourtipTitle', function (val) {
- scope.ttTitle = val || null;
- });
- //defaults: tourConfig.useSourceScope
- attrs.$observe('useSourceScope', function (val) {
- scope.ttSourceScope = !val ? tourConfig.useSourceScope : val === 'true';
- });
- //Init assignments (fix for Angular 1.3+)
- scope.ttNextLabel = tourConfig.nextLabel;
- scope.ttContainerElement = tourConfig.containerElement;
- scope.ttPlacement = tourConfig.placement.toLowerCase().trim();
- scope.centered = false;
- scope.ttMargin = tourConfig.margin;
- scope.offsetHorizontal = 0;
- scope.offsetVertical = 0;
- scope.ttSourceScope = tourConfig.useSourceScope;
- scope.ttOpen = false;
- scope.ttAnimation = tourConfig.animation;
- scope.index = parseInt(attrs.tourtipStep, 10);
- var tourtip = $compile(template)(scope);
- tourCtrl.addStep(scope);
- // wrap this in a time out because the tourtip won't compile right away
- $timeout(function () {
- scope.$watch('ttOpen', function (val) {
- if (val)
- show();
- else
- hide();
+ var model = $parse(attrs.step);
+ var backDrop = false;
+ // Watch current step view model and update locally
+ scope.$watch(attrs.step, function (newVal) {
+ ctrl.currentStep = newVal;
});
- }, 500);
- //determining target scope. It's used only when using virtual steps and there
- //is some action performed like on-show or on-progress. Without virtual steps
- //action would performed on element's scope and that would work just fine
- //however, when using virtual steps, whose steps can be placed in different
- //controller, so it affects scope, which will be used to run this action against.
- function getTargetScope() {
- var target = document.querySelectorAll(scope.ttElement);
- var targetElement = scope.ttElement ? angular.element(target) : element;
- var targetScope = scope;
- if (targetElement !== element && !scope.ttSourceScope) {
- targetScope = targetElement.scope();
- }
- return targetScope;
+ ctrl.postTourCallback = function (completed) {
+ var backdropEle = document.getElementsByClassName('tour-backdrop');
+ var active = document.getElementsByClassName('tour-element-active');
+ angular.element(backdropEle).remove();
+ backDrop = false;
+ angular.element(active).removeClass('tour-element-active');
+ if (completed && angular.isDefined(attrs.tourComplete)) {
+ scope.$parent.$eval(attrs.tourComplete);
+ }
+ if (angular.isDefined(attrs.postTour)) {
+ scope.$parent.$eval(attrs.postTour);
+ }
+ };
+ ctrl.postStepCallback = function () {
+ if (angular.isDefined(attrs.postStep)) {
+ scope.$parent.$eval(attrs.postStep);
+ }
+ };
+ ctrl.showStepCallback = function () {
+ if (tourConfig.backDrop) {
+ $timeout(function () {
+ var backdrop = document.getElementsByClassName('tour-backdrop');
+ var tooltip = document.getElementsByClassName('tour-tip')[0];
+ var div = document.createElement('div');
+ div.className = 'tour-backdrop';
+ angular.element(backdrop).remove();
+ // When the tour ends simply remove the backdrop and return.
+ if (!angular.isDefined(tooltip)) {
+ return;
+ }
+ tooltip.parentNode.insertBefore(div, tooltip);
+ }, 501);
+ backDrop = true;
+ }
+ };
+ // update the current step in the view as well as in our controller
+ scope.setCurrentStep = function (val) {
+ model.assign(scope.$parent, val);
+ ctrl.currentStep = val;
+ };
+ scope.getCurrentStep = function () {
+ return ctrl.currentStep;
+ };
}
- function calculatePosition(element, container) {
- var minimumLeft = 0;
- // minimum left position of tour tip
- var restrictRight;
- var ttPosition;
- var tourtipWidth = tourtip[0].offsetWidth;
- var tourtipHeight = tourtip[0].offsetHeight;
- // Get the position of the directive element
- var position = element[0].getBoundingClientRect();
- //make it relative against page or fixed container, not the window
- var top = position.top + window.pageYOffset;
- var containerLeft = 0;
- if (container && container[0]) {
- top = top - container[0].getBoundingClientRect().top + container[0].scrollTop;
- // if container is fixed, position tour tip relative to fixed container
- if (container.css('position') === 'fixed') {
- containerLeft = container[0].getBoundingClientRect().left;
+ };
+ }
+ ]).directive('tourtip', [
+ '$window',
+ '$compile',
+ '$interpolate',
+ '$timeout',
+ 'scrollTo',
+ 'tourConfig',
+ 'debounce',
+ '$q',
+ function ($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
+ var startSym = $interpolate.startSymbol(), endSym = $interpolate.endSymbol();
+ var template = '';
+ return {
+ require: '^tour',
+ restrict: 'EA',
+ scope: true,
+ link: function (scope, element, attrs, tourCtrl) {
+ attrs.$observe('tourtip', function (val) {
+ scope.ttContent = val;
+ });
+ //defaults: tourConfig.placement
+ attrs.$observe('tourtipPlacement', function (val) {
+ scope.ttPlacement = (val || tourConfig.placement).toLowerCase().trim();
+ scope.centered = scope.ttPlacement.indexOf('center') === 0;
+ });
+ attrs.$observe('tourtipNextLabel', function (val) {
+ scope.ttNextLabel = val || tourConfig.nextLabel;
+ });
+ attrs.$observe('tourtipContainerElement', function (val) {
+ scope.ttContainerElement = val || tourConfig.containerElement;
+ });
+ attrs.$observe('tourtipMargin', function (val) {
+ scope.ttMargin = parseInt(val, 10) || tourConfig.margin;
+ });
+ attrs.$observe('tourtipOffsetVertical', function (val) {
+ scope.offsetVertical = parseInt(val, 10) || 0;
+ });
+ attrs.$observe('tourtipOffsetHorizontal', function (val) {
+ scope.offsetHorizontal = parseInt(val, 10) || 0;
+ });
+ //defaults: null
+ attrs.$observe('onShow', function (val) {
+ scope.onStepShow = val || null;
+ });
+ //defaults: null
+ attrs.$observe('onProceed', function (val) {
+ scope.onStepProceed = val || null;
+ });
+ //defaults: null
+ attrs.$observe('tourtipElement', function (val) {
+ scope.ttElement = val || null;
+ });
+ //defaults: null
+ attrs.$observe('tourtipTitle', function (val) {
+ scope.ttTitle = val || null;
+ });
+ //defaults: tourConfig.useSourceScope
+ attrs.$observe('useSourceScope', function (val) {
+ scope.ttSourceScope = !val ? tourConfig.useSourceScope : val === 'true';
+ });
+ //Init assignments (fix for Angular 1.3+)
+ scope.ttNextLabel = tourConfig.nextLabel;
+ scope.ttContainerElement = tourConfig.containerElement;
+ scope.ttPlacement = tourConfig.placement.toLowerCase().trim();
+ scope.centered = false;
+ scope.ttMargin = tourConfig.margin;
+ scope.offsetHorizontal = 0;
+ scope.offsetVertical = 0;
+ scope.ttSourceScope = tourConfig.useSourceScope;
+ scope.ttOpen = false;
+ scope.ttAnimation = tourConfig.animation;
+ scope.index = parseInt(attrs.tourtipStep, 10);
+ var tourtip = $compile(template)(scope);
+ tourCtrl.addStep(scope);
+ // wrap this in a time out because the tourtip won't compile right away
+ $timeout(function () {
+ scope.$watch('ttOpen', function (val) {
+ if (val)
+ show();
+ else
+ hide();
+ });
+ }, 500);
+ //determining target scope. It's used only when using virtual steps and there
+ //is some action performed like on-show or on-progress. Without virtual steps
+ //action would performed on element's scope and that would work just fine
+ //however, when using virtual steps, whose steps can be placed in different
+ //controller, so it affects scope, which will be used to run this action against.
+ function getTargetScope() {
+ var target = document.querySelectorAll(scope.ttElement);
+ var targetElement = scope.ttElement ? angular.element(target) : element;
+ var targetScope = scope;
+ if (targetElement !== element && !scope.ttSourceScope) {
+ targetScope = targetElement.scope();
+ }
+ return targetScope;
+ }
+ function calculatePosition(element, container) {
+ var minimumLeft = 0;
+ // minimum left position of tour tip
+ var restrictRight;
+ var ttPosition;
+ var tourtipWidth = tourtip[0].offsetWidth;
+ var tourtipHeight = tourtip[0].offsetHeight;
+ // Get the position of the directive element
+ var position = element[0].getBoundingClientRect();
+ //make it relative against page or fixed container, not the window
+ var top = position.top + window.pageYOffset;
+ var containerLeft = 0;
+ if (container && container[0]) {
+ top = top - container[0].getBoundingClientRect().top + container[0].scrollTop;
+ // if container is fixed, position tour tip relative to fixed container
+ if (container.css('position') === 'fixed') {
+ containerLeft = container[0].getBoundingClientRect().left;
+ }
+ // restrict right position if the tourtip doesn't fit in the container
+ var containerWidth = container[0].getBoundingClientRect().width;
+ if (tourtipWidth + position.width > containerWidth) {
+ restrictRight = containerWidth - position.left + scope.ttMargin;
+ }
}
- // restrict right position if the tourtip doesn't fit in the container
- var containerWidth = container[0].getBoundingClientRect().width;
- if (tourtipWidth + position.width > containerWidth) {
- restrictRight = containerWidth - position.left + scope.ttMargin;
+ var ttWidth = tourtipWidth;
+ var ttHeight = tourtipHeight;
+ // Calculate the tourtip's top and left coordinates to center it
+ var _left;
+ switch (scope.ttPlacement) {
+ case 'right':
+ _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'bottom':
+ _left = position.left - containerLeft + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + position.height + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'center':
+ _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + 0.5 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'center-top':
+ _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + 0.1 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
+ case 'left':
+ _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
+ ttPosition = {
+ top: top + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft,
+ right: restrictRight
+ };
+ break;
+ default:
+ _left = position.left - containerLeft + scope.offsetHorizontal;
+ ttPosition = {
+ top: top - ttHeight - scope.ttMargin + scope.offsetVertical,
+ left: _left > 0 ? _left : minimumLeft
+ };
+ break;
}
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
+ return ttPosition;
}
- var ttWidth = tourtipWidth;
- var ttHeight = tourtipHeight;
- // Calculate the tourtip's top and left coordinates to center it
- var _left;
- switch (scope.ttPlacement) {
- case 'right':
- _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'bottom':
- _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top + position.height + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center':
- _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
- ttPosition = {
- top: top + 0.5 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center-top':
- _left = position.left - containerLeft + 0.5 * (position.width - ttWidth) + scope.offsetHorizontal;
- ttPosition = {
- top: top + 0.1 * (position.height - ttHeight) + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'left':
- _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft,
- right: restrictRight
- };
- break;
- default:
- _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top - ttHeight - scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
+ function show() {
+ if (!scope.ttContent) {
+ return;
+ }
+ var target = document.querySelectorAll(scope.ttElement);
+ var targetElement = scope.ttElement ? angular.element(target) : element;
+ if (targetElement === null || targetElement.length === 0)
+ throw 'Target element could not be found. Selector: ' + scope.ttElement;
+ var containerEle = document.querySelectorAll(scope.ttContainerElement);
+ angular.element(containerEle).append(tourtip);
+ var updatePosition = function () {
+ var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(containerEle);
+ var ttPosition = calculatePosition(targetElement, offsetElement);
+ // Now set the calculated positioning.
+ tourtip.css(ttPosition);
+ // Scroll to the tour tip
+ scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed);
};
- break;
+ if (tourConfig.backDrop) {
+ focusActiveElement(targetElement);
+ }
+ angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
+ updatePosition();
+ // CSS class must be added after the element is already on the DOM otherwise it won't animate (fade in).
+ tourtip.addClass('show');
+ if (scope.onStepShow) {
+ var targetScope = getTargetScope();
+ //fancy! Let's make on show action not instantly, but after a small delay
+ $timeout(function () {
+ targetScope.$eval(scope.onStepShow);
+ }, 300);
+ }
}
- ttPosition.top += 'px';
- ttPosition.left += 'px';
- return ttPosition;
- }
- function show() {
- if (!scope.ttContent) {
- return;
+ function hide() {
+ tourtip.removeClass('show');
+ tourtip.detach();
+ angular.element($window).unbind('resize.' + scope.$id);
}
- var target = document.querySelectorAll(scope.ttElement);
- var targetElement = scope.ttElement ? angular.element(target) : element;
- if (targetElement === null || targetElement.length === 0)
- throw 'Target element could not be found. Selector: ' + scope.ttElement;
- var containerEle = document.querySelectorAll(scope.ttContainerElement);
- angular.element(containerEle).append(tourtip);
- var updatePosition = function () {
- var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(containerEle);
- var ttPosition = calculatePosition(targetElement, offsetElement);
- // Now set the calculated positioning.
- tourtip.css(ttPosition);
- // Scroll to the tour tip
- scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed);
- };
- if (tourConfig.backDrop) {
- focusActiveElement(targetElement);
- }
- angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
- updatePosition();
- // CSS class must be added after the element is already on the DOM otherwise it won't animate (fade in).
- tourtip.addClass('show');
- if (scope.onStepShow) {
- var targetScope = getTargetScope();
- //fancy! Let's make on show action not instantly, but after a small delay
- $timeout(function () {
- targetScope.$eval(scope.onStepShow);
- }, 300);
+ function focusActiveElement(el) {
+ var activeEle = document.getElementsByClassName('tour-element-active');
+ angular.element(activeEle).removeClass('tour-element-active');
+ if (!scope.centered) {
+ el.addClass('tour-element-active');
+ }
}
+ // Make sure tooltip is destroyed and removed.
+ scope.$on('$destroy', function onDestroyTourtip() {
+ angular.element($window).unbind('resize.' + scope.$id);
+ tourtip.remove();
+ tourtip = null;
+ });
+ scope.proceed = function () {
+ if (scope.onStepProceed) {
+ var targetScope = getTargetScope();
+ var onProceedResult = targetScope.$eval(scope.onStepProceed);
+ $q.resolve(onProceedResult).then(function () {
+ scope.setCurrentStep(scope.getCurrentStep() + 1);
+ });
+ } else {
+ scope.setCurrentStep(scope.getCurrentStep() + 1);
+ }
+ };
}
- function hide() {
- tourtip.removeClass('show');
- tourtip.detach();
- angular.element($window).unbind('resize.' + scope.$id);
- }
- function focusActiveElement(el) {
- var activeEle = document.getElementsByClassName('tour-element-active');
- angular.element(activeEle).removeClass('tour-element-active');
- if (!scope.centered) {
- el.addClass('tour-element-active');
- }
+ };
+ }
+ ]).directive('tourPopup', function () {
+ return {
+ replace: true,
+ templateUrl: 'tour/tour.tpl.html',
+ scope: true,
+ restrict: 'EA',
+ link: function (scope, element, attrs) {
+ }
+ };
+ }).factory('orderedList', function () {
+ var OrderedList = function () {
+ this.map = {};
+ this._array = [];
+ };
+ OrderedList.prototype.set = function (key, value) {
+ if (!angular.isNumber(key))
+ return;
+ if (key in this.map) {
+ this.map[key] = value;
+ } else {
+ if (key < this._array.length) {
+ var insertIndex = key - 1 > 0 ? key - 1 : 0;
+ this._array.splice(insertIndex, 0, key);
+ } else {
+ this._array.push(key);
}
- // Make sure tooltip is destroyed and removed.
- scope.$on('$destroy', function onDestroyTourtip() {
- angular.element($window).unbind('resize.' + scope.$id);
- tourtip.remove();
- tourtip = null;
+ this.map[key] = value;
+ this._array.sort(function (a, b) {
+ return a - b;
});
- scope.proceed = function () {
- if (scope.onStepProceed) {
- var targetScope = getTargetScope();
- var onProceedResult = targetScope.$eval(scope.onStepProceed);
- $q.resolve(onProceedResult).then(function () {
- scope.setCurrentStep(scope.getCurrentStep() + 1);
- });
- } else {
- scope.setCurrentStep(scope.getCurrentStep() + 1);
- }
- };
}
};
- }
- ]).directive('tourPopup', function () {
- return {
- replace: true,
- templateUrl: 'tour/tour.tpl.html',
- scope: true,
- restrict: 'EA',
- link: function (scope, element, attrs) {
- }
- };
- }).factory('orderedList', function () {
- var OrderedList = function () {
- this.map = {};
- this._array = [];
- };
- OrderedList.prototype.set = function (key, value) {
- if (!angular.isNumber(key))
- return;
- if (key in this.map) {
- this.map[key] = value;
- } else {
- if (key < this._array.length) {
- var insertIndex = key - 1 > 0 ? key - 1 : 0;
- this._array.splice(insertIndex, 0, key);
- } else {
- this._array.push(key);
+ OrderedList.prototype.indexOf = function (value) {
+ for (var prop in this.map) {
+ if (this.map.hasOwnProperty(prop)) {
+ if (this.map[prop] === value)
+ return Number(prop);
+ }
}
+ };
+ OrderedList.prototype.push = function (value) {
+ var key = this._array[this._array.length - 1] + 1 || 0;
+ this._array.push(key);
this.map[key] = value;
this._array.sort(function (a, b) {
return a - b;
});
- }
- };
- OrderedList.prototype.indexOf = function (value) {
- for (var prop in this.map) {
- if (this.map.hasOwnProperty(prop)) {
- if (this.map[prop] === value)
- return Number(prop);
- }
- }
- };
- OrderedList.prototype.push = function (value) {
- var key = this._array[this._array.length - 1] + 1 || 0;
- this._array.push(key);
- this.map[key] = value;
- this._array.sort(function (a, b) {
- return a - b;
- });
- };
- OrderedList.prototype.remove = function (key) {
- var index = this._array.indexOf(key);
- if (index === -1) {
- throw new Error('key does not exist');
- }
- this._array.splice(index, 1);
- delete this.map[key];
- };
- OrderedList.prototype.get = function (key) {
- return this.map[key];
- };
- OrderedList.prototype.getCount = function () {
- return this._array.length;
- };
- OrderedList.prototype.forEach = function (f) {
- var key, value;
- for (var i = 0; i < this._array.length; i++) {
- key = this._array[i];
- value = this.map[key];
- f(value, key);
- }
- };
- OrderedList.prototype.first = function () {
- var key, value;
- key = this._array[0];
- value = this.map[key];
- return value;
- };
- var orderedListFactory = function () {
- return new OrderedList();
- };
- return orderedListFactory;
- }).factory('scrollTo', [
- '$interval',
- function ($interval) {
- var animationInProgress = false;
- function getEasingPattern(time) {
- return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // default easeInOutCubic transition
- }
- function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) {
- if (animationInProgress) {
- return;
+ };
+ OrderedList.prototype.remove = function (key) {
+ var index = this._array.indexOf(key);
+ if (index === -1) {
+ throw new Error('key does not exist');
}
- speed = speed || 500;
- offsetY = offsetY || 0;
- offsetX = offsetX || 0;
- // Set some boundaries in case the offset wants us to scroll to impossible locations
- var finalY = endTop + offsetY;
- if (finalY < 0) {
- finalY = 0;
- } else if (finalY > container.scrollHeight) {
- finalY = container.scrollHeight;
+ this._array.splice(index, 1);
+ delete this.map[key];
+ };
+ OrderedList.prototype.get = function (key) {
+ return this.map[key];
+ };
+ OrderedList.prototype.getCount = function () {
+ return this._array.length;
+ };
+ OrderedList.prototype.forEach = function (f) {
+ var key, value;
+ for (var i = 0; i < this._array.length; i++) {
+ key = this._array[i];
+ value = this.map[key];
+ f(value, key);
}
- var finalX = endLeft + offsetX;
- if (finalX < 0) {
- finalX = 0;
- } else if (finalX > container.scrollWidth) {
- finalX = container.scrollWidth;
+ };
+ OrderedList.prototype.first = function () {
+ var key, value;
+ key = this._array[0];
+ value = this.map[key];
+ return value;
+ };
+ var orderedListFactory = function () {
+ return new OrderedList();
+ };
+ return orderedListFactory;
+ }).factory('scrollTo', [
+ '$interval',
+ function ($interval) {
+ var animationInProgress = false;
+ function getEasingPattern(time) {
+ return time < 0.5 ? 4 * time * time * time : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; // default easeInOutCubic transition
}
- var startTop = container.scrollTop, startLeft = container.scrollLeft, timeLapsed = 0, distanceY = finalY - startTop,
- // If we're going up, this will be a negative number
- distanceX = finalX - startLeft, currentPositionY, currentPositionX, timeProgress;
- function stopAnimation() {
- // If we have reached our destination clear the interval
- if (currentPositionY === finalY && currentPositionX === finalX) {
- $interval.cancel(runAnimation);
- animationInProgress = false;
+ function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) {
+ if (animationInProgress) {
+ return;
}
+ speed = speed || 500;
+ offsetY = offsetY || 0;
+ offsetX = offsetX || 0;
+ // Set some boundaries in case the offset wants us to scroll to impossible locations
+ var finalY = endTop + offsetY;
+ if (finalY < 0) {
+ finalY = 0;
+ } else if (finalY > container.scrollHeight) {
+ finalY = container.scrollHeight;
+ }
+ var finalX = endLeft + offsetX;
+ if (finalX < 0) {
+ finalX = 0;
+ } else if (finalX > container.scrollWidth) {
+ finalX = container.scrollWidth;
+ }
+ var startTop = container.scrollTop, startLeft = container.scrollLeft, timeLapsed = 0, distanceY = finalY - startTop,
+ // If we're going up, this will be a negative number
+ distanceX = finalX - startLeft, currentPositionY, currentPositionX, timeProgress;
+ function stopAnimation() {
+ // If we have reached our destination clear the interval
+ if (currentPositionY === finalY && currentPositionX === finalX) {
+ $interval.cancel(runAnimation);
+ animationInProgress = false;
+ }
+ }
+ function animateScroll() {
+ console.log('called');
+ timeLapsed += 16;
+ // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1
+ timeProgress = timeLapsed / speed;
+ // Make a check and set back to 1 if we went over (e.g. 512/500)
+ timeProgress = timeProgress > 1 ? 1 : timeProgress;
+ // Number between 0 and 1 corresponding to the animation pattern
+ var multiplier = getEasingPattern(timeProgress);
+ // Calculate the distance to travel in this step. It is the total distance times a percentage of what we will move
+ var translateY = distanceY * multiplier;
+ var translateX = distanceX * multiplier;
+ // Assign to the shorthand variables
+ currentPositionY = startTop + translateY;
+ currentPositionX = startLeft + translateX;
+ // Move slightly following the easing pattern
+ container.scrollTop = currentPositionY;
+ container.scrollLeft = currentPositionX;
+ // Check if we have reached our destination
+ stopAnimation();
+ }
+ animationInProgress = true;
+ // Kicks off the function
+ var runAnimation = $interval(animateScroll, 16);
}
- function animateScroll() {
- console.log('called');
- timeLapsed += 16;
- // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1
- timeProgress = timeLapsed / speed;
- // Make a check and set back to 1 if we went over (e.g. 512/500)
- timeProgress = timeProgress > 1 ? 1 : timeProgress;
- // Number between 0 and 1 corresponding to the animation pattern
- var multiplier = getEasingPattern(timeProgress);
- // Calculate the distance to travel in this step. It is the total distance times a percentage of what we will move
- var translateY = distanceY * multiplier;
- var translateX = distanceX * multiplier;
- // Assign to the shorthand variables
- currentPositionY = startTop + translateY;
- currentPositionX = startLeft + translateX;
- // Move slightly following the easing pattern
- container.scrollTop = currentPositionY;
- container.scrollLeft = currentPositionX;
- // Check if we have reached our destination
- stopAnimation();
- }
- animationInProgress = true;
- // Kicks off the function
- var runAnimation = $interval(animateScroll, 16);
+ return function (target, containerSelector, offsetY, offsetX, speed) {
+ var container = document.querySelectorAll(containerSelector);
+ offsetY = offsetY || -100;
+ offsetX = offsetX || -100;
+ _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed);
+ };
}
- return function (target, containerSelector, offsetY, offsetX, speed) {
- var container = document.querySelectorAll(containerSelector);
- offsetY = offsetY || -100;
- offsetX = offsetX || -100;
- _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed);
- };
- }
- ]).factory('debounce', [
- '$timeout',
- '$q',
- function ($timeout, $q) {
- return function (func, wait, immediate) {
- var timeout;
- var deferred = $q.defer();
- return function () {
- var context = this, args = arguments;
- var later = function () {
- timeout = null;
- if (!immediate) {
+ ]).factory('debounce', [
+ '$timeout',
+ '$q',
+ function ($timeout, $q) {
+ return function (func, wait, immediate) {
+ var timeout;
+ var deferred = $q.defer();
+ return function () {
+ var context = this, args = arguments;
+ var later = function () {
+ timeout = null;
+ if (!immediate) {
+ deferred.resolve(func.apply(context, args));
+ deferred = $q.defer();
+ }
+ };
+ var callNow = immediate && !timeout;
+ if (timeout) {
+ $timeout.cancel(timeout);
+ }
+ timeout = $timeout(later, wait);
+ if (callNow) {
deferred.resolve(func.apply(context, args));
deferred = $q.defer();
}
+ return deferred.promise;
};
- var callNow = immediate && !timeout;
- if (timeout) {
- $timeout.cancel(timeout);
- }
- timeout = $timeout(later, wait);
- if (callNow) {
- deferred.resolve(func.apply(context, args));
- deferred = $q.defer();
- }
- return deferred.promise;
};
- };
- }
- ]);
+ }
+ ]);
+ }(angular));
}(window, document));
\ No newline at end of file
diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js
index 04c0a52..3b6fcf4 100644
--- a/dist/angular-tour.min.js
+++ b/dist/angular-tour.min.js
@@ -1 +1 @@
-!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document);
\ No newline at end of file
+!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),function(d){d.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,e=c.steps=b(),f=!0;c.postTourCallback=d.noop,c.postStepCallback=d.noop,c.showStepCallback=d.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){f?f=!1:c.select(a)}),c.select=function(a){if(d.isNumber(a)){c.unselectAllSteps();var b=e.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=e.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){d.isNumber(a.index)&&!isNaN(a.index)?e.set(a.index,a):e.push(a)},c.unselectAllSteps=function(){e.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=e.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,e){return{controller:"TourController",restrict:"EA",scope:!0,link:function(f,g,h,i){if(!d.isDefined(h.step))throw"The directive requires a `step` attribute to bind the current step to.";var j=a(h.step),k=!1;f.$watch(h.step,function(a){i.currentStep=a}),i.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),e=b.getElementsByClassName("tour-element-active");d.element(c).remove(),k=!1,d.element(e).removeClass("tour-element-active"),a&&d.isDefined(h.tourComplete)&&f.$parent.$eval(h.tourComplete),d.isDefined(h.postTour)&&f.$parent.$eval(h.postTour)},i.postStepCallback=function(){d.isDefined(h.postStep)&&f.$parent.$eval(h.postStep)},i.showStepCallback=function(){e.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],e=b.createElement("div");e.className="tour-backdrop",d.element(a).remove(),d.isDefined(c)&&c.parentNode.insertBefore(e,c)},501),k=!0)},f.setCurrentStep=function(a){j.assign(f.$parent,a),i.currentStep=a},f.getCurrentStep=function(){return i.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(e,f,g,h,i,j,k,l){var m=(g.startSymbol(),g.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(g,n,o,p){function q(){var a=b.querySelectorAll(g.ttElement),c=g.ttElement?d.element(a):n,e=g;return c===n||g.ttSourceScope||(e=c.scope()),e}function r(b,c){var d,e,f=0,h=v[0].offsetWidth,i=v[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+g.ttMargin)}var n,o=h,p=i;switch(g.ttPlacement){case"right":n=j.left-l+j.width+g.ttMargin+g.offsetHorizontal,e={top:k+g.offsetVertical,left:n>0?n:f};break;case"bottom":n=j.left-l+g.offsetHorizontal,e={top:k+j.height+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"center":n=j.left-l+.5*(j.width-o)+g.offsetHorizontal,e={top:k+.5*(j.height-p)+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"center-top":n=j.left-l+.5*(j.width-o)+g.offsetHorizontal,e={top:k+.1*(j.height-p)+g.ttMargin+g.offsetVertical,left:n>0?n:f};break;case"left":n=j.left-l-o-g.ttMargin+g.offsetHorizontal,e={top:k+g.offsetVertical,left:n>0?n:f,right:d};break;default:n=j.left-l+g.offsetHorizontal,e={top:k-p-g.ttMargin+g.offsetVertical,left:n>0?n:f}}return e.top+="px",e.left+="px",e}function s(){if(g.ttContent){var a=b.querySelectorAll(g.ttElement),f=g.ttElement?d.element(a):n;if(null===f||0===f.length)throw"Target element could not be found. Selector: "+g.ttElement;var l=b.querySelectorAll(g.ttContainerElement);d.element(l).append(v);var m=function(){var a="body"===g.ttContainerElement?c:d.element(l),b=r(f,a);v.css(b),i(v,g.ttContainerElement,-150,-300,j.scrollSpeed)};if(j.backDrop&&u(f),d.element(e).bind("resize."+g.$id,k(m,50)),m(),v.addClass("show"),g.onStepShow){var o=q();h(function(){o.$eval(g.onStepShow)},300)}}}function t(){v.removeClass("show"),v.detach(),d.element(e).unbind("resize."+g.$id)}function u(a){var c=b.getElementsByClassName("tour-element-active");d.element(c).removeClass("tour-element-active"),g.centered||a.addClass("tour-element-active")}o.$observe("tourtip",function(a){g.ttContent=a}),o.$observe("tourtipPlacement",function(a){g.ttPlacement=(a||j.placement).toLowerCase().trim(),g.centered=0===g.ttPlacement.indexOf("center")}),o.$observe("tourtipNextLabel",function(a){g.ttNextLabel=a||j.nextLabel}),o.$observe("tourtipContainerElement",function(a){g.ttContainerElement=a||j.containerElement}),o.$observe("tourtipMargin",function(a){g.ttMargin=parseInt(a,10)||j.margin}),o.$observe("tourtipOffsetVertical",function(a){g.offsetVertical=parseInt(a,10)||0}),o.$observe("tourtipOffsetHorizontal",function(a){g.offsetHorizontal=parseInt(a,10)||0}),o.$observe("onShow",function(a){g.onStepShow=a||null}),o.$observe("onProceed",function(a){g.onStepProceed=a||null}),o.$observe("tourtipElement",function(a){g.ttElement=a||null}),o.$observe("tourtipTitle",function(a){g.ttTitle=a||null}),o.$observe("useSourceScope",function(a){g.ttSourceScope=a?"true"===a:j.useSourceScope}),g.ttNextLabel=j.nextLabel,g.ttContainerElement=j.containerElement,g.ttPlacement=j.placement.toLowerCase().trim(),g.centered=!1,g.ttMargin=j.margin,g.offsetHorizontal=0,g.offsetVertical=0,g.ttSourceScope=j.useSourceScope,g.ttOpen=!1,g.ttAnimation=j.animation,g.index=parseInt(o.tourtipStep,10);var v=f(m)(g);p.addStep(g),h(function(){g.$watch("ttOpen",function(a){a?s():t()})},500),g.$on("$destroy",function(){d.element(e).unbind("resize."+g.$id),v.remove(),v=null}),g.proceed=function(){if(g.onStepProceed){var a=q(),b=a.$eval(g.onStepProceed);l.resolve(b).then(function(){g.setCurrentStep(g.getCurrentStep()+1)})}else g.setCurrentStep(g.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(d.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(angular)}(window,document);
\ No newline at end of file