diff --git a/bower.json b/bower.json
index be7da60..426752b 100644
--- a/bower.json
+++ b/bower.json
@@ -6,7 +6,9 @@
"angularjs",
"directive",
"angular",
- "module"
+ "module",
+ "tour",
+ "walkthrough"
],
"homepage": "https://github.com/DaftMonk/angular-tour",
"bugs": "https://github.com/DaftMonk/angular-tour/issues",
@@ -17,10 +19,10 @@
},
"repository": {
"type": "git",
- "url": "git://github.com/DaftMonk/angular-tour.git"
+ "url": "git@github.com:DaftMonk/angular-tour.git"
},
"main": [
- "./dist/angular-tour-tpls.min.js",
+ "./dist/angular-tour-tpls.js",
"./dist/angular-tour.css"
],
"ignore": [
@@ -34,15 +36,11 @@
"package.json"
],
"dependencies": {
- "angular": "~1.4.6",
- "jquery": "~2.0.3"
+ "angular": "^1.5.0"
},
"devDependencies": {
- "angular-mocks": "~1.4.6",
- "angular-cookie": "~4.0.1"
+ "angular-mocks": "^1.5.0",
+ "angular-cookie": "^4.0.1"
},
- "license": "MIT",
- "resolutions": {
- "angular": "~1.4.6"
- }
+ "license": "MIT"
}
diff --git a/demo/index.html b/demo/index.html
index 181bf86..7f58d03 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -18,9 +18,6 @@
-
-
-
@@ -51,7 +48,7 @@
});
-
+
diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js
index 97ccc41..f081258 100644
--- a/dist/angular-tour-tpls.js
+++ b/dist/angular-tour-tpls.js
@@ -1,6 +1,6 @@
/**
* An AngularJS directive for showcasing features of your website
- * @version v0.2.5 - 2015-12-10
+ * @version v0.2.5 - 2016-03-15
* @link https://github.com/DaftMonk/angular-tour
* @author Tyler Henkel
* @license MIT License, http://www.opensource.org/licenses/MIT
@@ -19,490 +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;
+ }
+ // 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) {
- angular.element('.tour-backdrop').remove();
- backDrop = false;
- angular.element('.tour-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) {
- angular.element(tourConfig.containerElement).append(angular.element(''));
- $timeout(function () {
- $('.tour-backdrop').remove();
- angular.element('').insertBefore('.tour-tip');
- }, 1000);
- 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 targetElement = scope.ttElement ? angular.element(scope.ttElement) : 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;
- // 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 (tourtip.width() + 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 = tourtip.width();
- var ttHeight = tourtip.height();
- // Calculate the tourtip's top and left coordinates to center it
- switch (scope.ttPlacement) {
- case 'right':
- var _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'bottom':
- var _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top + position.height + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center':
- var _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':
- var _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':
- var _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft,
- right: restrictRight
- };
- break;
- default:
- var _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);
}
- if (scope.ttAnimation)
- tourtip.fadeIn();
- else {
- tourtip.css({ display: 'block' });
+ 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');
+ }
}
- var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element;
- if (targetElement == null || targetElement.length === 0)
- throw 'Target element could not be found. Selector: ' + scope.ttElement;
- angular.element(scope.ttContainerElement).append(tourtip);
- var updatePosition = function () {
- var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement);
- var ttPosition = calculatePosition(targetElement, offsetElement);
- // Now set the calculated positioning.
- tourtip.css(ttPosition);
- // Scroll to the tour tip
- var ttPositionTop = parseInt(ttPosition.top), ttPositionLeft = parseInt(ttPosition.left);
- scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft);
+ // 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);
+ }
};
- if (tourConfig.backDrop)
- focusActiveElement(targetElement);
- angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
- updatePosition();
- 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 hide() {
- tourtip.detach();
- angular.element($window).unbind('resize.' + scope.$id);
}
- function focusActiveElement(el) {
- angular.element('.tour-element-active').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.remove = function (key) {
+ var index = this._array.indexOf(key);
+ if (index === -1) {
+ throw new Error('key does not exist');
}
- }
- };
- 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];
+ 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];
- 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', function () {
- return function (target, containerElement, offsetY, offsetX, speed, ttPositionTop, ttPositionLeft) {
- if (target) {
- offsetY = offsetY || -100;
- offsetX = offsetX || -100;
- speed = speed || 500;
- $('html,' + containerElement).stop().animate({
- scrollTop: ttPositionTop + offsetY,
- scrollLeft: ttPositionLeft + offsetX
- }, speed);
- } else {
- $('html,' + containerElement).stop().animate({ scrollTop: 0 }, speed);
+ 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;
+ }
+ 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);
+ }
+ 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 aa1728c..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,b,c){return{controller:"TourController",restrict:"EA",scope:!0,link:function(d,e,f,g){if(!angular.isDefined(f.step))throw"The directive requires a `step` attribute to bind the current step to.";var h=a(f.step),i=!1;d.$watch(f.step,function(a){g.currentStep=a}),g.postTourCallback=function(a){angular.element(".tour-backdrop").remove(),i=!1,angular.element(".tour-element-active").removeClass("tour-element-active"),a&&angular.isDefined(f.tourComplete)&&d.$parent.$eval(f.tourComplete),angular.isDefined(f.postTour)&&d.$parent.$eval(f.postTour)},g.postStepCallback=function(){angular.isDefined(f.postStep)&&d.$parent.$eval(f.postStep)},g.showStepCallback=function(){c.backDrop&&(angular.element(c.containerElement).append(angular.element('')),b(function(){$(".tour-backdrop").remove(),angular.element('').insertBefore(".tour-tip")},1e3),i=!0)},d.setCurrentStep=function(a){h.assign(d.$parent,a),g.currentStep=a},d.getCurrentStep=function(){return g.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(b,d,e,f,g,h,i,j){var k=(e.startSymbol(),e.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(e,l,m,n){function o(){var a=e.ttElement?angular.element(e.ttElement):l,b=e;return a===l||e.ttSourceScope||(b=a.scope()),b}function p(b,c){var d,f,g=0,h=b[0].getBoundingClientRect(),i=h.top+a.pageYOffset,j=0;if(c&&c[0]){i=i-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(j=c[0].getBoundingClientRect().left);var k=c[0].getBoundingClientRect().width;t.width()+h.width>k&&(d=k-h.left+e.ttMargin)}var l=t.width(),m=t.height();switch(e.ttPlacement){case"right":var n=h.left-j+h.width+e.ttMargin+e.offsetHorizontal;f={top:i+e.offsetVertical,left:n>0?n:g};break;case"bottom":var n=h.left-j+e.offsetHorizontal;f={top:i+h.height+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"center":var n=h.left-j+.5*(h.width-l)+e.offsetHorizontal;f={top:i+.5*(h.height-m)+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"center-top":var n=h.left-j+.5*(h.width-l)+e.offsetHorizontal;f={top:i+.1*(h.height-m)+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"left":var n=h.left-j-l-e.ttMargin+e.offsetHorizontal;f={top:i+e.offsetVertical,left:n>0?n:g,right:d};break;default:var n=h.left-j+e.offsetHorizontal;f={top:i-m-e.ttMargin+e.offsetVertical,left:n>0?n:g}}return f.top+="px",f.left+="px",f}function q(){if(e.ttContent){e.ttAnimation?t.fadeIn():t.css({display:"block"});var a=e.ttElement?angular.element(e.ttElement):l;if(null==a||0===a.length)throw"Target element could not be found. Selector: "+e.ttElement;angular.element(e.ttContainerElement).append(t);var d=function(){var b="body"===e.ttContainerElement?c:angular.element(e.ttContainerElement),d=p(a,b);t.css(d);var f=parseInt(d.top),i=parseInt(d.left);g(t,e.ttContainerElement,-150,-300,h.scrollSpeed,f,i)};if(h.backDrop&&s(a),angular.element(b).bind("resize."+e.$id,i(d,50)),d(),e.onStepShow){var j=o();f(function(){j.$eval(e.onStepShow)},300)}}}function r(){t.detach(),angular.element(b).unbind("resize."+e.$id)}function s(a){angular.element(".tour-element-active").removeClass("tour-element-active"),e.centered||a.addClass("tour-element-active")}m.$observe("tourtip",function(a){e.ttContent=a}),m.$observe("tourtipPlacement",function(a){e.ttPlacement=(a||h.placement).toLowerCase().trim(),e.centered=0===e.ttPlacement.indexOf("center")}),m.$observe("tourtipNextLabel",function(a){e.ttNextLabel=a||h.nextLabel}),m.$observe("tourtipContainerElement",function(a){e.ttContainerElement=a||h.containerElement}),m.$observe("tourtipMargin",function(a){e.ttMargin=parseInt(a,10)||h.margin}),m.$observe("tourtipOffsetVertical",function(a){e.offsetVertical=parseInt(a,10)||0}),m.$observe("tourtipOffsetHorizontal",function(a){e.offsetHorizontal=parseInt(a,10)||0}),m.$observe("onShow",function(a){e.onStepShow=a||null}),m.$observe("onProceed",function(a){e.onStepProceed=a||null}),m.$observe("tourtipElement",function(a){e.ttElement=a||null}),m.$observe("tourtipTitle",function(a){e.ttTitle=a||null}),m.$observe("useSourceScope",function(a){e.ttSourceScope=a?"true"===a:h.useSourceScope}),e.ttNextLabel=h.nextLabel,e.ttContainerElement=h.containerElement,e.ttPlacement=h.placement.toLowerCase().trim(),e.centered=!1,e.ttMargin=h.margin,e.offsetHorizontal=0,e.offsetVertical=0,e.ttSourceScope=h.useSourceScope,e.ttOpen=!1,e.ttAnimation=h.animation,e.index=parseInt(m.tourtipStep,10);var t=d(k)(e);n.addStep(e),f(function(){e.$watch("ttOpen",function(a){a?q():r()})},500),e.$on("$destroy",function(){angular.element(b).unbind("resize."+e.$id),t.remove(),t=null}),e.proceed=function(){if(e.onStepProceed){var a=o(),b=a.$eval(e.onStepProceed);j.resolve(b).then(function(){e.setCurrentStep(e.getCurrentStep()+1)})}else e.setCurrentStep(e.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;d\n \n \n\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.css b/dist/angular-tour.css
index 3725810..b723980 100644
--- a/dist/angular-tour.css
+++ b/dist/angular-tour.css
@@ -1,9 +1,12 @@
.tour-tip {
- display: none;
+ opacity: 0;
+ visibility: hidden;
+ display: block;
+ transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
position: absolute;
background: #1C252E;
color: #FFF;
- z-index: 101;
+ z-index: 121 !important;
top: 0;
left: 2.5%;
font-family: inherit;
@@ -12,6 +15,11 @@
border-radius: 10px;
}
+.tour-tip.show {
+ opacity: 1;
+ visibility: visible;
+}
+
.tour-tip p {
color: #CBD0D4;
font-size: .9em;
diff --git a/dist/angular-tour.js b/dist/angular-tour.js
index 0ff1e06..a70ee03 100644
--- a/dist/angular-tour.js
+++ b/dist/angular-tour.js
@@ -1,6 +1,6 @@
/**
* An AngularJS directive for showcasing features of your website
- * @version v0.2.5 - 2015-12-10
+ * @version v0.2.5 - 2016-03-15
* @link https://github.com/DaftMonk/angular-tour
* @author Tyler Henkel
* @license MIT License, http://www.opensource.org/licenses/MIT
@@ -9,490 +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) {
- angular.element('.tour-backdrop').remove();
- backDrop = false;
- angular.element('.tour-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) {
- angular.element(tourConfig.containerElement).append(angular.element(''));
- $timeout(function () {
- $('.tour-backdrop').remove();
- angular.element('').insertBefore('.tour-tip');
- }, 1000);
- 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 targetElement = scope.ttElement ? angular.element(scope.ttElement) : 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;
- // 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 (tourtip.width() + 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 = tourtip.width();
- var ttHeight = tourtip.height();
- // Calculate the tourtip's top and left coordinates to center it
- switch (scope.ttPlacement) {
- case 'right':
- var _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'bottom':
- var _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top + position.height + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center':
- var _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':
- var _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':
- var _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft,
- right: restrictRight
- };
- break;
- default:
- var _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);
}
- if (scope.ttAnimation)
- tourtip.fadeIn();
- else {
- tourtip.css({ display: 'block' });
+ 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');
+ }
}
- var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element;
- if (targetElement == null || targetElement.length === 0)
- throw 'Target element could not be found. Selector: ' + scope.ttElement;
- angular.element(scope.ttContainerElement).append(tourtip);
- var updatePosition = function () {
- var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement);
- var ttPosition = calculatePosition(targetElement, offsetElement);
- // Now set the calculated positioning.
- tourtip.css(ttPosition);
- // Scroll to the tour tip
- var ttPositionTop = parseInt(ttPosition.top), ttPositionLeft = parseInt(ttPosition.left);
- scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft);
+ // 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);
+ }
};
- if (tourConfig.backDrop)
- focusActiveElement(targetElement);
- angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
- updatePosition();
- 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 hide() {
- tourtip.detach();
- angular.element($window).unbind('resize.' + scope.$id);
- }
- function focusActiveElement(el) {
- angular.element('.tour-element-active').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.remove = function (key) {
+ var index = this._array.indexOf(key);
+ if (index === -1) {
+ throw new Error('key does not exist');
}
- }
- };
- 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];
+ 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];
- 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', function () {
- return function (target, containerElement, offsetY, offsetX, speed, ttPositionTop, ttPositionLeft) {
- if (target) {
- offsetY = offsetY || -100;
- offsetX = offsetX || -100;
- speed = speed || 500;
- $('html,' + containerElement).stop().animate({
- scrollTop: ttPositionTop + offsetY,
- scrollLeft: ttPositionLeft + offsetX
- }, speed);
- } else {
- $('html,' + containerElement).stop().animate({ scrollTop: 0 }, speed);
+ 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;
+ }
+ 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);
+ }
+ 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 2bd71d5..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,b,c){return{controller:"TourController",restrict:"EA",scope:!0,link:function(d,e,f,g){if(!angular.isDefined(f.step))throw"The directive requires a `step` attribute to bind the current step to.";var h=a(f.step),i=!1;d.$watch(f.step,function(a){g.currentStep=a}),g.postTourCallback=function(a){angular.element(".tour-backdrop").remove(),i=!1,angular.element(".tour-element-active").removeClass("tour-element-active"),a&&angular.isDefined(f.tourComplete)&&d.$parent.$eval(f.tourComplete),angular.isDefined(f.postTour)&&d.$parent.$eval(f.postTour)},g.postStepCallback=function(){angular.isDefined(f.postStep)&&d.$parent.$eval(f.postStep)},g.showStepCallback=function(){c.backDrop&&(angular.element(c.containerElement).append(angular.element('')),b(function(){$(".tour-backdrop").remove(),angular.element('').insertBefore(".tour-tip")},1e3),i=!0)},d.setCurrentStep=function(a){h.assign(d.$parent,a),g.currentStep=a},d.getCurrentStep=function(){return g.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(b,d,e,f,g,h,i,j){var k=(e.startSymbol(),e.endSymbol(),"");return{require:"^tour",restrict:"EA",scope:!0,link:function(e,l,m,n){function o(){var a=e.ttElement?angular.element(e.ttElement):l,b=e;return a===l||e.ttSourceScope||(b=a.scope()),b}function p(b,c){var d,f,g=0,h=b[0].getBoundingClientRect(),i=h.top+a.pageYOffset,j=0;if(c&&c[0]){i=i-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(j=c[0].getBoundingClientRect().left);var k=c[0].getBoundingClientRect().width;t.width()+h.width>k&&(d=k-h.left+e.ttMargin)}var l=t.width(),m=t.height();switch(e.ttPlacement){case"right":var n=h.left-j+h.width+e.ttMargin+e.offsetHorizontal;f={top:i+e.offsetVertical,left:n>0?n:g};break;case"bottom":var n=h.left-j+e.offsetHorizontal;f={top:i+h.height+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"center":var n=h.left-j+.5*(h.width-l)+e.offsetHorizontal;f={top:i+.5*(h.height-m)+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"center-top":var n=h.left-j+.5*(h.width-l)+e.offsetHorizontal;f={top:i+.1*(h.height-m)+e.ttMargin+e.offsetVertical,left:n>0?n:g};break;case"left":var n=h.left-j-l-e.ttMargin+e.offsetHorizontal;f={top:i+e.offsetVertical,left:n>0?n:g,right:d};break;default:var n=h.left-j+e.offsetHorizontal;f={top:i-m-e.ttMargin+e.offsetVertical,left:n>0?n:g}}return f.top+="px",f.left+="px",f}function q(){if(e.ttContent){e.ttAnimation?t.fadeIn():t.css({display:"block"});var a=e.ttElement?angular.element(e.ttElement):l;if(null==a||0===a.length)throw"Target element could not be found. Selector: "+e.ttElement;angular.element(e.ttContainerElement).append(t);var d=function(){var b="body"===e.ttContainerElement?c:angular.element(e.ttContainerElement),d=p(a,b);t.css(d);var f=parseInt(d.top),i=parseInt(d.left);g(t,e.ttContainerElement,-150,-300,h.scrollSpeed,f,i)};if(h.backDrop&&s(a),angular.element(b).bind("resize."+e.$id,i(d,50)),d(),e.onStepShow){var j=o();f(function(){j.$eval(e.onStepShow)},300)}}}function r(){t.detach(),angular.element(b).unbind("resize."+e.$id)}function s(a){angular.element(".tour-element-active").removeClass("tour-element-active"),e.centered||a.addClass("tour-element-active")}m.$observe("tourtip",function(a){e.ttContent=a}),m.$observe("tourtipPlacement",function(a){e.ttPlacement=(a||h.placement).toLowerCase().trim(),e.centered=0===e.ttPlacement.indexOf("center")}),m.$observe("tourtipNextLabel",function(a){e.ttNextLabel=a||h.nextLabel}),m.$observe("tourtipContainerElement",function(a){e.ttContainerElement=a||h.containerElement}),m.$observe("tourtipMargin",function(a){e.ttMargin=parseInt(a,10)||h.margin}),m.$observe("tourtipOffsetVertical",function(a){e.offsetVertical=parseInt(a,10)||0}),m.$observe("tourtipOffsetHorizontal",function(a){e.offsetHorizontal=parseInt(a,10)||0}),m.$observe("onShow",function(a){e.onStepShow=a||null}),m.$observe("onProceed",function(a){e.onStepProceed=a||null}),m.$observe("tourtipElement",function(a){e.ttElement=a||null}),m.$observe("tourtipTitle",function(a){e.ttTitle=a||null}),m.$observe("useSourceScope",function(a){e.ttSourceScope=a?"true"===a:h.useSourceScope}),e.ttNextLabel=h.nextLabel,e.ttContainerElement=h.containerElement,e.ttPlacement=h.placement.toLowerCase().trim(),e.centered=!1,e.ttMargin=h.margin,e.offsetHorizontal=0,e.offsetVertical=0,e.ttSourceScope=h.useSourceScope,e.ttOpen=!1,e.ttAnimation=h.animation,e.index=parseInt(m.tourtipStep,10);var t=d(k)(e);n.addStep(e),f(function(){e.$watch("ttOpen",function(a){a?q():r()})},500),e.$on("$destroy",function(){angular.element(b).unbind("resize."+e.$id),t.remove(),t=null}),e.proceed=function(){if(e.onStepProceed){var a=o(),b=a.$eval(e.onStepProceed);j.resolve(b).then(function(){e.setCurrentStep(e.getCurrentStep()+1)})}else e.setCurrentStep(e.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;d-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/karma.conf.js b/karma.conf.js
index 7e40d48..7b5ea8f 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -9,7 +9,6 @@ module.exports = function (config) {
frameworks: ['jasmine'],
files : [
- 'bower_components/jquery/jquery.js',
'bower_components/angular/angular.js',
'bower_components/angular-cookie/angular-cookie.js',
'bower_components/angular-mocks/angular-mocks.js',
diff --git a/package.json b/package.json
index bf38be7..4b3cce8 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,9 @@
"angularjs",
"directive",
"angular",
- "module"
+ "module",
+ "tour",
+ "walkthrough"
],
"homepage": "https://github.com/DaftMonk/angular-tour",
"bugs": "https://github.com/DaftMonk/angular-tour/issues",
diff --git a/src/tour/tour.js b/src/tour/tour.js
index 85581e3..b7de041 100644
--- a/src/tour/tour.js
+++ b/src/tour/tour.js
@@ -1,586 +1,655 @@
-'use strict';
-
-angular.module('angular-tour.tour', [])
-
-/**
- * tourConfig
- * Default configuration, can be customized by injecting tourConfig into your app and modifying it
- */
-.constant('tourConfig', {
- placement: 'top', // default placement relative to target. 'top', 'right', 'left', 'bottom'
- animation: true, // if tips fade in
- nextLabel: 'Next', // default text in the next tip button
- scrollSpeed: 500, // page scrolling speed in milliseconds
- margin: 28, // how many pixels margin the tip is from the target
- backDrop: false, // if there is a backdrop (gray overlay) when tour starts
- useSourceScope: false, // only target scope should be used (only when using virtual steps)
- containerElement: 'body' // default container element to parent tourtips to
-})
-
-/**
- * TourController
- * the logic for the tour, which manages all the steps
- */
-.controller('TourController', 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);
- }
+(function(angular){
+
+ 'use strict';
+
+ angular.module('angular-tour.tour', [])
+
+ /**
+ * tourConfig
+ * Default configuration, can be customized by injecting tourConfig into your app and modifying it
+ */
+ .constant('tourConfig', {
+ placement: 'top', // default placement relative to target. 'top', 'right', 'left', 'bottom'
+ animation: true, // if tips fade in
+ nextLabel: 'Next', // default text in the next tip button
+ scrollSpeed: 500, // page scrolling speed in milliseconds
+ margin: 28, // how many pixels margin the tip is from the target
+ backDrop: false, // if there is a backdrop (gray overlay) when tour starts
+ useSourceScope: false, // only target scope should be used (only when using virtual steps)
+ containerElement: 'body' // default container element to parent tourtips to
+ })
+
+ /**
+ * TourController
+ * the logic for the tour, which manages all the steps
+ */
+ .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) {
+ self.select = function(nextIndex) {
if (!angular.isNumber(nextIndex)) return;
self.unselectAllSteps();
var step = steps.get(nextIndex);
- if (step) {
- step.ttOpen = true;
- }
+ if (step) { step.ttOpen = true; }
// update currentStep if we manually selected this index
- if (self.currentStep !== nextIndex) {
- self.currentStep = nextIndex;
- }
+ if (self.currentStep !== nextIndex) { self.currentStep = nextIndex; }
- if (self.currentStep > -1)
- self.showStepCallback();
+ if (self.currentStep > -1) { self.showStepCallback(); }
+
+ if (nextIndex >= steps.getCount()) { self.postTourCallback(true); }
- 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.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;
+ self.unselectAllSteps = function() {
+ steps.forEach(function (step) {
+ step.ttOpen = false;
});
- };
+ };
- self.cancelTour = function() {
+ self.cancelTour = function() {
self.unselectAllSteps();
self.postTourCallback(false);
- };
+ };
- $scope.openTour = function() {
+ $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() {
+ $scope.closeTour = function() {
self.cancelTour();
- };
-})
-
-/**
- * Tour
- * directive that allows you to control the tour
- */
-.directive('tour', function($parse, $timeout, tourConfig) {
- return {
+ };
+ }])
+
+ /**
+ * Tour
+ * directive that allows you to control the tour
+ */
+ .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.');
+ if (!angular.isDefined(attrs.step)) {
+ throw ('The directive requires a `step` attribute to bind the current step to.');
+ }
+ 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;
+ });
+
+ 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);
}
- 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;
- });
-
- ctrl.postTourCallback = function(completed) {
- angular.element('.tour-backdrop').remove();
- backDrop = false;
- angular.element('.tour-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) {
- angular.element(tourConfig.containerElement).append(angular.element(''));
-
- $timeout(function() {
- $('.tour-backdrop').remove();
- angular.element('').insertBefore('.tour-tip');
- }, 1000)
+ if (angular.isDefined(attrs.postTour)) {
+ scope.$parent.$eval(attrs.postTour);
+ }
+ };
- backDrop = true;
- }
- };
+ 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;
- };
+ // 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;
- };
+ scope.getCurrentStep = function() {
+ return ctrl.currentStep;
+ };
}
- };
-})
+ };
+ }])
-/**
- * Tourtip
- * tourtip manages the state of the tour-popup directive
- */
-.directive('tourtip', function($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
- var startSym = $interpolate.startSymbol(),
- endSym = $interpolate.endSymbol();
+ /**
+ * Tourtip
+ * tourtip manages the state of the tour-popup directive
+ */
+ .directive('tourtip', ['$window', '$compile', '$interpolate', '$timeout', 'scrollTo', 'tourConfig', 'debounce', '$q',
+ function($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) {
- var template = '';
+ var startSym = $interpolate.startSymbol(),
+ endSym = $interpolate.endSymbol();
- return {
+ 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';
+ 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();
});
-
- //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 targetElement = scope.ttElement ? angular.element(scope.ttElement) : element;
-
- var targetScope = scope;
- if (targetElement !== element && !scope.ttSourceScope)
- targetScope = targetElement.scope();
-
- return targetScope;
+ }, 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;
+ }
}
- function calculatePosition(element, container) {
- var minimumLeft = 0; // minimum left position of tour tip
- var restrictRight;
- var ttPosition;
-
- // 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 (tourtip.width() + position.width > containerWidth) {
- restrictRight = containerWidth - position.left + scope.ttMargin;
- }
- }
-
- var ttWidth = tourtip.width();
- var ttHeight = tourtip.height();
-
- // Calculate the tourtip's top and left coordinates to center it
- switch (scope.ttPlacement) {
- case 'right':
- var _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'bottom':
- var _left = position.left - containerLeft + scope.offsetHorizontal;
- ttPosition = {
- top: top + position.height + scope.ttMargin + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft
- };
- break;
- case 'center':
- var _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':
- var _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':
- var _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal;
- ttPosition = {
- top: top + scope.offsetVertical,
- left: _left > 0 ? _left : minimumLeft,
- right: restrictRight
- };
- break;
- default:
- var _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
+ };
+ break;
}
- function show() {
- if (!scope.ttContent) {
- return;
- }
+ ttPosition.top += 'px';
+ ttPosition.left += 'px';
- if (scope.ttAnimation)
- tourtip.fadeIn();
- else {
- tourtip.css({
- display: 'block'
- });
- }
+ return ttPosition;
+ }
- var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element;
+ function show() {
+ if (!scope.ttContent) { return; }
- if (targetElement == null || targetElement.length === 0)
- throw 'Target element could not be found. Selector: ' + scope.ttElement;
+ var target = document.querySelectorAll(scope.ttElement);
+ var targetElement = scope.ttElement ? angular.element(target) : element;
- angular.element(scope.ttContainerElement).append(tourtip);
+ if (targetElement === null || targetElement.length === 0)
+ throw 'Target element could not be found. Selector: ' + scope.ttElement;
- var updatePosition = function() {
+ var containerEle = document.querySelectorAll(scope.ttContainerElement);
+ angular.element(containerEle).append(tourtip);
- var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement);
- var ttPosition = calculatePosition(targetElement, offsetElement);
+ var updatePosition = function() {
- // Now set the calculated positioning.
- tourtip.css(ttPosition);
+ var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(containerEle);
+ var ttPosition = calculatePosition(targetElement, offsetElement);
- // Scroll to the tour tip
- var ttPositionTop = parseInt(ttPosition.top),
- ttPositionLeft = parseInt(ttPosition.left);
- scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft);
- };
+ // Now set the calculated positioning.
+ tourtip.css(ttPosition);
- if (tourConfig.backDrop)
- focusActiveElement(targetElement);
+ // Scroll to the tour tip
+ scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed);
+ };
- angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
+ if (tourConfig.backDrop) { focusActiveElement(targetElement); }
- updatePosition();
+ angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50));
- if (scope.onStepShow) {
- var targetScope = getTargetScope();
+ updatePosition();
- //fancy! Let's make on show action not instantly, but after a small delay
- $timeout(function() {
- targetScope.$eval(scope.onStepShow);
- }, 300);
- }
- }
+ // CSS class must be added after the element is already on the DOM otherwise it won't animate (fade in).
+ tourtip.addClass('show');
- function hide() {
- tourtip.detach();
- angular.element($window).unbind('resize.' + scope.$id);
- }
+ if (scope.onStepShow) {
+ var targetScope = getTargetScope();
- function focusActiveElement(el) {
- angular.element('.tour-element-active').removeClass('tour-element-active');
-
- if (!scope.centered)
- el.addClass('tour-element-active');
+ //fancy! Let's make on show action not instantly, but after a small delay
+ $timeout(function() {
+ targetScope.$eval(scope.onStepShow);
+ }, 300);
}
-
- // 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'); }
+ }
+
+ // 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);
+ }
+ };
}
- };
-})
-
-/**
- * TourPopup
- * the directive that actually has the template for the tip
- */
-.directive('tourPopup', function() {
- return {
+ };
+ }])
+
+ /**
+ * TourPopup
+ * the directive that actually has the template for the tip
+ */
+ .directive('tourPopup', function() {
+ return {
replace: true,
templateUrl: 'tour/tour.tpl.html',
scope: true,
restrict: 'EA',
link: function(scope, element, attrs) {}
- };
-})
-
-/**
- * OrderedList
- * Used for keeping steps in order
- */
-.factory('orderedList', function() {
- var OrderedList = function() {
+ };
+ })
+
+ /**
+ * OrderedList
+ * Used for keeping steps in order
+ */
+ .factory('orderedList', function() {
+ var OrderedList = function() {
this.map = {};
this._array = [];
- };
+ };
- OrderedList.prototype.set = function(key, value) {
+ OrderedList.prototype.set = function(key, value) {
if (!angular.isNumber(key))
- return;
+ return;
if (key in this.map) {
- this.map[key] = value;
+ 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);
- }
- this.map[key] = value;
- this._array.sort(function(a, b) {
- return a - b;
- });
+ if (key < this._array.length) {
+ var insertIndex = key - 1 > 0 ? key - 1 : 0;
+ this._array.splice(insertIndex, 0, key);
+ } else {
+ this._array.push(key);
+ }
+ this.map[key] = value;
+ this._array.sort(function(a, b) {
+ return a - b;
+ });
}
- };
- OrderedList.prototype.indexOf = function(value) {
+ };
+
+ OrderedList.prototype.indexOf = function(value) {
for (var prop in this.map) {
- if (this.map.hasOwnProperty(prop)) {
- if (this.map[prop] === value)
- return Number(prop);
- }
+ if (this.map.hasOwnProperty(prop)) {
+ if (this.map[prop] === value)
+ return Number(prop);
+ }
}
- };
- OrderedList.prototype.push = function(value) {
+ };
+
+ 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;
+ return a - b;
});
- };
- OrderedList.prototype.remove = function(key) {
+ };
+
+ OrderedList.prototype.remove = function(key) {
var index = this._array.indexOf(key);
if (index === -1) {
- throw new Error('key does not exist');
+ throw new Error('key does not exist');
}
this._array.splice(index, 1);
delete this.map[key];
- };
- OrderedList.prototype.get = function(key) {
+ };
+
+ OrderedList.prototype.get = function(key) {
return this.map[key];
- };
- OrderedList.prototype.getCount = function() {
+ };
+
+ OrderedList.prototype.getCount = function() {
return this._array.length;
- };
- OrderedList.prototype.forEach = function(f) {
+ };
+
+ 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);
+ key = this._array[i];
+ value = this.map[key];
+ f(value, key);
}
- };
- OrderedList.prototype.first = function() {
+ };
+
+ OrderedList.prototype.first = function() {
var key, value;
key = this._array[0];
value = this.map[key];
return value;
- };
+ };
- var orderedListFactory = function() {
+ var orderedListFactory = function() {
return new OrderedList();
- };
-
- return orderedListFactory;
-})
-
-/**
- * ScrollTo
- * Smoothly scroll to a dom element
- */
-.factory('scrollTo', function() {
- return function(target, containerElement, offsetY, offsetX, speed, ttPositionTop, ttPositionLeft) {
- if (target) {
- offsetY = offsetY || -100;
- offsetX = offsetX || -100;
- speed = speed || 500;
- $('html,' + containerElement).stop().animate({
- scrollTop: ttPositionTop + offsetY,
- scrollLeft: ttPositionLeft + offsetX
- }, speed);
- } else {
- $('html,' + containerElement).stop().animate({
- scrollTop: 0
- }, speed);
- }
- };
-})
-.factory('debounce', 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);
+
+ return orderedListFactory;
+ })
+
+ /**
+ * ScrollTo
+ * Smoothly scroll to a dom element
+ */
+ .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
}
- timeout = $timeout(later, wait);
- if (callNow) {
- deferred.resolve(func.apply(context,args));
- deferred = $q.defer();
+
+ 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);
}
- return deferred.promise;
- };
- };
-});
+
+ 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) {
+ 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;
+ };
+ };
+ }]);
+
+})(angular);
diff --git a/src/tour/tour.scss b/src/tour/tour.scss
index 7a875db..924ae98 100644
--- a/src/tour/tour.scss
+++ b/src/tour/tour.scss
@@ -1,15 +1,30 @@
.tour-tip {
- display: none;
+ opacity: 0;
+ visibility: hidden;
+ display: block;
+
+ -webkit-transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
+ -moz-transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
+ -ms-transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
+ -o-transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
+ transition: visibility 0s, opacity 0.6s cubic-bezier(0.345, 0, 0.25, 1);
+
position: absolute;
background: #1C252E;
color: #FFF;
- z-index: 101;
+ z-index: 121 !important;
top: 0;
left: 2.5%;
font-family: inherit;
font-weight: 400;
max-width: 400px;
border-radius: 10px;
+
+ &.show {
+ opacity: 1;
+ visibility: visible;
+ }
+
p {
color: #CBD0D4;
font-size: .9em;
diff --git a/src/tour/tour.spec.js b/src/tour/tour.spec.js
index da3f40b..c94f5a0 100644
--- a/src/tour/tour.spec.js
+++ b/src/tour/tour.spec.js
@@ -7,7 +7,14 @@ describe('Directive: tour', function () {
beforeEach(module('angular-tour.tour'));
function getTourStep() {
- return angular.element('body').find('div.tour-tip');
+ var tour = document.querySelector('div.tour-tip');
+ return angular.element(tour);
+ }
+
+ function htmlToElement(html) {
+ var template = document.createElement('template');
+ template.innerHTML = html;
+ return template.content.childNodes;
}
var $rootScope, $compile, $controller, $timeout;
@@ -118,7 +125,7 @@ describe('Directive: tour', function () {
});
describe('basics', function() {
- var elm, scope, tour, tip1, tip2, tourScope;
+ var elm, scope, tourTpl, tour, tip1, tip2, tourScope;
beforeEach(function() {
@@ -126,14 +133,15 @@ describe('Directive: tour', function () {
scope.stepIndex = 0;
- tour = angular.element('' +
- 'Important website feature' +
- 'Another website feature' +
- '');
+ tourTpl = htmlToElement('' +
+ 'Important website feature' +
+ 'Another website feature' +
+ '')[0];
+ tour = angular.element(tourTpl);
elm = $compile(tour)(scope);
- tip1 = tour.find('#tt1');
- tip2 = tour.find('#tt2');
+ tip1 = angular.element( document.getElementById('#tt1') );
+ tip2 = angular.element( document.getElementById('#tt2') );
scope.$apply();
$timeout.flush();
@@ -161,7 +169,7 @@ describe('Directive: tour', function () {
});
describe('tourtip', function() {
- var elm, scope, tour, tip1, tip2, tourScope;
+ var elm, scope, tourTpl, tour, tip1, tip2, tourScope;
beforeEach(function() {
@@ -169,14 +177,17 @@ describe('Directive: tour', function () {
scope.stepIndex = 0;
- tour = angular.element('' +
- 'Important website feature' +
- 'Another website feature' +
- '');
+ tourTpl = htmlToElement('')[0];
+ var feature1 = htmlToElement('Important website feature')[0];
+ var feature2 = htmlToElement('Another website feature')[0];
+
+ tour = angular.element(tourTpl);
+ tour.append(feature1);
+ tour.append(feature2);
elm = $compile(tour)(scope);
- tip1 = tour.find('#tt1');
- tip2 = tour.find('#tt2');
+ tip1 = tour.children().eq(0);
+ tip2 = tour.children().eq(1);
scope.$apply();
$timeout.flush();
@@ -194,12 +205,12 @@ describe('Directive: tour', function () {
expect(elm).toHaveOpenTourtips(1);
var stepText = tip1.attr('tourtip');
var tourStep = getTourStep();
- expect(tourStep.html()).toContain('feature 1');
+ expect(tourStep.html()).toContain(stepText);
expect(tip1.scope().ttOpen).toBe(true);
});
it('should open tip2 popup and close tip1 on next', function () {
- var elmNext = getTourStep().find('.tour-next-tip').eq(0);
+ var elmNext = getTourStep()[0].querySelector('.tour-next-tip');
elmNext.click();
@@ -218,7 +229,7 @@ describe('Directive: tour', function () {
});
it('should close tips when you click close', function () {
- var elmNext = getTourStep().find('.tour-close-tip').eq(0);
+ var elmNext = getTourStep()[0].querySelector('.tour-close-tip');
elmNext.click();
expect(tip1.scope().ttOpen).toBe(false);
@@ -229,7 +240,7 @@ describe('Directive: tour', function () {
scope.onProceedFunction = function() {};
scope.ttSourceScope = true;
spyOn(scope, 'onProceedFunction');
- var tour1Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour1Next = getTourStep()[0].querySelector('.tour-next-tip');
tour1Next.click();
$timeout.flush();
@@ -268,13 +279,9 @@ describe('Directive: tour', function () {
scope.otherStepIndex = -1;
// set up first tour
- tour = angular.element('');
- tip1 = angular.element('' +
- 'Important website feature' +
- '');
- tip2 = angular.element('' +
- 'Another website feature' +
- '');
+ tour = angular.element(htmlToElement('')[0]);
+ tip1 = angular.element(htmlToElement('Important website feature')[0]);
+ tip2 = angular.element(htmlToElement('Another website feature')[0]);
tour.append(tip1);
tour.append(tip2);
@@ -291,7 +298,7 @@ describe('Directive: tour', function () {
it('should call post-tour method', function() {
scope.tourEnd = function() {};
spyOn(scope, 'tourEnd');
- var tour1Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour1Next = getTourStep()[0].querySelector('.tour-next-tip');
tour1Next.click();
tour1Next.click();
expect(scope.tourEnd).toHaveBeenCalled();
@@ -307,7 +314,7 @@ describe('Directive: tour', function () {
it('should call tour-complete method', function() {
scope.tourComplete = function() {};
spyOn(scope, 'tourComplete');
- var tour1Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour1Next = getTourStep()[0].querySelector('.tour-next-tip');
tour1Next.click();
tour1Next.click();
expect(scope.tourComplete).toHaveBeenCalled();
@@ -323,7 +330,7 @@ describe('Directive: tour', function () {
it('should call post-step method', function() {
scope.tourStep = function() {};
spyOn(scope, 'tourStep');
- var tour1Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour1Next = getTourStep()[0].querySelector('.tour-next-tip');
tour1Next.click();
expect(scope.tourStep).toHaveBeenCalled();
});
@@ -334,12 +341,14 @@ describe('Directive: tour', function () {
scope.otherStepIndex = 0;
};
+ var tourWrapper = htmlToElement('')[0];
+ var feature1 = htmlToElement('Important website feature')[0];
+ var feature2 = htmlToElement('Another website feature')[0];
+
// set up second tour
- var otherTour = angular.element('');
- var otherTip1 = angular.element('' +
- 'Important website feature' + '');
- var otherTip2 = angular.element('' +
- 'Another website feature' + '');
+ var otherTour = angular.element(tourWrapper);
+ var otherTip1 = angular.element(feature1);
+ var otherTip2 = angular.element(feature2);
otherTour.append(otherTip1);
otherTour.append(otherTip2);
@@ -349,7 +358,7 @@ describe('Directive: tour', function () {
scope.$apply();
$timeout.flush();
- var tour1Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour1Next = getTourStep()[0].querySelector('.tour-next-tip');
// expect second tour to be closed
expect(otherTip1.scope().ttOpen).toBe(false);
@@ -367,7 +376,7 @@ describe('Directive: tour', function () {
otherElm = $compile(otherTour)(scope);
scope.$apply();
$timeout.flush();
- var tour2Next = getTourStep().find('.tour-next-tip').eq(0);
+ var tour2Next = getTourStep()[0].querySelector('.tour-next-tip');
// expect second tour to open after first tour finished
expect(otherTip1.scope().ttOpen).toBe(true);
@@ -441,28 +450,42 @@ describe('Directive: tour', function () {
});
describe('scroll service', function() {
- var target, scope, scrollTo;
+ var div, target, bodyEle, body, scope, scrollTo, arrived;
beforeEach(inject(function (_scrollTo_) {
scope = $rootScope.$new();
scrollTo = _scrollTo_;
-
- target = angular.element('');
- $('body').height(window.innerHeight*2).append(target);
- window.scrollTo(0, 0);
+
+ div = htmlToElement('Some element absolutely positioned
')[0];
+ target = angular.element(div);
+
+ bodyEle = document.querySelector('body');
+ bodyEle.style.height = window.innerHeight * 2 + 'px';
+ body = angular.element(bodyEle);
+
+ body.append(target);
}));
it('should scroll to position', function () {
- expect($(window).scrollTop()).toEqual(0);
+ expect( bodyEle.scrollTop ).toEqual(0);
+ scrollTo(target, 'body', -100, -100, 500);
+
+ runs(function() {
+ arrived = false;
+
+ setTimeout(function(){
+ arrived = true;
+ }, 500);
+ });
- scrollTo(target, 'body', -100, -100, 500, 200, 0);
waitsFor(function() {
- return $(window).scrollTop() === 100;
- }, 'Current position to be 100px');
+ return arrived;
+ }, 'Should have scrolled', 1000);
runs(function() {
- expect($(window).scrollTop()).toEqual(100);
+ expect( bodyEle.scrollTop ).toBeGreaterThan(0);
});
+
});
});
});