From 47e7fce0fb6ac99ab3ae4673906eb1f4c0c056be Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 9 Feb 2016 11:20:24 -0500 Subject: [PATCH 01/12] fix indenting and change a few jquery references --- demo/index.html | 2 +- src/tour/tour.js | 891 +++++++++++++++++++++++------------------------ 2 files changed, 441 insertions(+), 452 deletions(-) diff --git a/demo/index.html b/demo/index.html index 181bf86..1c3ff6f 100644 --- a/demo/index.html +++ b/demo/index.html @@ -19,7 +19,7 @@ - + diff --git a/src/tour/tour.js b/src/tour/tour.js index 85581e3..9e970c0 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -2,11 +2,11 @@ angular.module('angular-tour.tour', []) -/** - * tourConfig - * Default configuration, can be customized by injecting tourConfig into your app and modifying it - */ -.constant('tourConfig', { + /** + * 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 @@ -15,13 +15,15 @@ angular.module('angular-tour.tour', []) 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) { + }) + + /** + * 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; @@ -33,16 +35,11 @@ angular.module('angular-tour.tour', []) 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); - } - } + $scope.$watch(function () { + return self.currentStep; + }, function (val) { + firstCurrentStepChange ? firstCurrentStepChange = false : self.select(val); + } ); self.select = function(nextIndex) { @@ -50,537 +47,529 @@ angular.module('angular-tour.tour', []) 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); - } + (angular.isNumber(step.index) && !isNaN(step.index)) ? steps.set(step.index, step) : steps.push(step); }; self.unselectAllSteps = function() { - steps.forEach(function(step) { - step.ttOpen = false; - }); + steps.forEach(function(step) { + step.ttOpen = false; + }); }; self.cancelTour = function() { - self.unselectAllSteps(); - self.postTourCallback(false); + 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); + // 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(); + self.cancelTour(); }; -}) - -/** - * Tour - * directive that allows you to control the tour - */ -.directive('tour', function($parse, $timeout, tourConfig) { + }]) + + /** + * 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.'); - } - var model = $parse(attrs.step); - var backDrop = false; + 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.'); + } + 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; - }); + // 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) - - 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; - }; - } - }; -}) + 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'); -/** - * Tourtip - * tourtip manages the state of the tour-popup directive - */ -.directive('tourtip', function($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) { + 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() { + var backdrop = document.getElementsByClassName('tour-backdrop'); + angular.element(backdrop).remove(); + angular.element('
').insertBefore('.tour-tip'); + }, 1000) + + 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; + }; + } + }; + }]) + + /** + * 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 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; - }); + 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); - }); + //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('tourtipNextLabel', function(val) { + scope.ttNextLabel = val || tourConfig.nextLabel; + }); - attrs.$observe('tourtipContainerElement', function(val) { - scope.ttContainerElement = val || tourConfig.containerElement; - }); + attrs.$observe('tourtipContainerElement', function(val) { + scope.ttContainerElement = val || tourConfig.containerElement; + }); - attrs.$observe('tourtipMargin', function(val) { - scope.ttMargin = parseInt(val, 10) || tourConfig.margin; - }); + 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('tourtipOffsetVertical', function(val) { + scope.offsetVertical = parseInt(val, 10) || 0; + }); - attrs.$observe('tourtipOffsetHorizontal', function(val) { - scope.offsetHorizontal = 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('onShow', function(val) { + scope.onStepShow = val || null; + }); - //defaults: null - attrs.$observe('onProceed', function(val) { - scope.onStepProceed = 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('tourtipElement', function(val) { + scope.ttElement = val || null; + }); - //defaults: null - attrs.$observe('tourtipTitle', function (val) { - scope.ttTitle = 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'; - }); + //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 targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; - - var targetScope = scope; - if (targetElement !== element && !scope.ttSourceScope) - targetScope = targetElement.scope(); - - return targetScope; - } + //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) { + val ? show() : 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; + } - 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; + 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; + } - function show() { - if (!scope.ttContent) { - return; - } + function show() { + if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.fadeIn(); - else { - tourtip.css({ - display: 'block' - }); - } + scope.ttAnimation ? tourtip.fadeIn() : tourtip.css({ display: 'block' }); - var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; + 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; + if (targetElement == null || targetElement.length === 0) + throw 'Target element could not be found. Selector: ' + scope.ttElement; - angular.element(scope.ttContainerElement).append(tourtip); + angular.element(scope.ttContainerElement).append(tourtip); - var updatePosition = function() { + var updatePosition = function() { - var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement); - var ttPosition = calculatePosition(targetElement, offsetElement); + var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement); + var ttPosition = calculatePosition(targetElement, offsetElement); - // Now set the calculated positioning. - tourtip.css(ttPosition); + // 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); - }; + // Scroll to the tour tip + var ttPositionTop = parseInt(ttPosition.top), + ttPositionLeft = parseInt(ttPosition.left); + scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft); + }; - if (tourConfig.backDrop) - focusActiveElement(targetElement); + if (tourConfig.backDrop) { focusActiveElement(targetElement); } - angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50)); + angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50)); - updatePosition(); + updatePosition(); - if (scope.onStepShow) { - var targetScope = getTargetScope(); + 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); - } - } + //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 hide() { + tourtip.detach(); + angular.element($window).unbind('resize.' + scope.$id); + } - function focusActiveElement(el) { - angular.element('.tour-element-active').removeClass('tour-element-active'); + 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'); } + } - 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; + }); - // 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(); - 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); - } - }; - } + 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() { + /** + * 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) {} + 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() { + /** + * OrderedList + * Used for keeping steps in order + */ + .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; + 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 { - 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; - }); + 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); - } + 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; - }); + 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]; + 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]; + return this.map[key]; }; + OrderedList.prototype.getCount = function() { - return this._array.length; + return this._array.length; }; + OrderedList.prototype.forEach = function(f) { - var key, value; - for (var i = 0; i < this._array.length; i++) { - key = this._array[i]; - value = this.map[key]; - f(value, key); - } + var key, value; + for (var i = 0; i < this._array.length; i++) { + key = this._array[i]; + value = this.map[key]; + f(value, key); + } }; + OrderedList.prototype.first = function() { - var key, value; - key = this._array[0]; - value = this.map[key]; - return value; + var key, value; + key = this._array[0]; + value = this.map[key]; + return value; }; var orderedListFactory = function() { - return new OrderedList(); + return new OrderedList(); }; return orderedListFactory; -}) + }) -/** - * ScrollTo - * Smoothly scroll to a dom element - */ -.factory('scrollTo', function() { + /** + * 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); - } + 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)); + }) + + .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); + } + 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; }; - }; -}); + }); From b31df5a948183dd917a445da8faca8c7ddf76d75 Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 9 Feb 2016 11:52:17 -0500 Subject: [PATCH 02/12] fix all jslint errors --- demo/index.html | 2 +- src/tour/tour.js | 153 +++++++++++++++++++++++++---------------------- 2 files changed, 84 insertions(+), 71 deletions(-) diff --git a/demo/index.html b/demo/index.html index 1c3ff6f..42936ed 100644 --- a/demo/index.html +++ b/demo/index.html @@ -51,7 +51,7 @@ }); - + diff --git a/src/tour/tour.js b/src/tour/tour.js index 9e970c0..2a0080e 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -21,7 +21,7 @@ angular.module('angular-tour.tour', []) * TourController * the logic for the tour, which manages all the steps */ - .controller('TourController', ['$scope', 'orderedList', + .controller('TourController', ['$scope', 'orderedList', function($scope, orderedList) { var self = this, @@ -38,33 +38,39 @@ angular.module('angular-tour.tour', []) $scope.$watch(function () { return self.currentStep; }, function (val) { - firstCurrentStepChange ? firstCurrentStepChange = false : self.select(val); + if (firstCurrentStepChange) + firstCurrentStepChange = false; + else + self.select(val); } ); self.select = function(nextIndex) { - if (!angular.isNumber(nextIndex)) return; + if (!angular.isNumber(nextIndex)) return; - self.unselectAllSteps(); - var step = steps.get(nextIndex); - if (step) { step.ttOpen = true; } + 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; } + // update currentStep if we manually selected this index + 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); } - - self.postStepCallback(); + if (nextIndex >= steps.getCount()) { self.postTourCallback(true); } + + self.postStepCallback(); }; - self.addStep = function(step) { - (angular.isNumber(step.index) && !isNaN(step.index)) ? steps.set(step.index, step) : 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) { + steps.forEach(function (step) { step.ttOpen = false; }); }; @@ -89,7 +95,7 @@ angular.module('angular-tour.tour', []) * Tour * directive that allows you to control the tour */ - .directive('tour', ['$parse', '$timeout', 'tourConfig', + .directive('tour', ['$parse', '$timeout', 'tourConfig', function($parse, $timeout, tourConfig) { return { @@ -138,7 +144,7 @@ angular.module('angular-tour.tour', []) var backdrop = document.getElementsByClassName('tour-backdrop'); angular.element(backdrop).remove(); angular.element('
').insertBefore('.tour-tip'); - }, 1000) + }, 1000); backDrop = true; } @@ -161,7 +167,7 @@ angular.module('angular-tour.tour', []) * Tourtip * tourtip manages the state of the tour-popup directive */ - .directive('tourtip', ['$window', '$compile', '$interpolate', '$timeout', 'scrollTo', 'tourConfig', 'debounce', '$q', + .directive('tourtip', ['$window', '$compile', '$interpolate', '$timeout', 'scrollTo', 'tourConfig', 'debounce', '$q', function($window, $compile, $interpolate, $timeout, scrollTo, tourConfig, debounce, $q) { var startSym = $interpolate.startSymbol(), @@ -176,7 +182,7 @@ angular.module('angular-tour.tour', []) link: function(scope, element, attrs, tourCtrl) { attrs.$observe('tourtip', function(val) { - scope.ttContent = val; + scope.ttContent = val; }); //defaults: tourConfig.placement @@ -249,7 +255,10 @@ angular.module('angular-tour.tour', []) // wrap this in a time out because the tourtip won't compile right away $timeout(function() { scope.$watch('ttOpen', function(val) { - val ? show() : hide(); + if (val) + show(); + else + hide(); }); }, 500); @@ -296,50 +305,51 @@ angular.module('angular-tour.tour', []) 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; + 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'; @@ -351,11 +361,14 @@ angular.module('angular-tour.tour', []) function show() { if (!scope.ttContent) { return; } - scope.ttAnimation ? tourtip.fadeIn() : tourtip.css({ display: 'block' }); + if (scope.ttAnimation) + tourtip.fadeIn(); + else + tourtip.css({ display: 'block' }); var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; - if (targetElement == null || targetElement.length === 0) + if (targetElement === null || targetElement.length === 0) throw 'Target element could not be found. Selector: ' + scope.ttElement; angular.element(scope.ttContainerElement).append(tourtip); @@ -369,8 +382,8 @@ angular.module('angular-tour.tour', []) tourtip.css(ttPosition); // Scroll to the tour tip - var ttPositionTop = parseInt(ttPosition.top), - ttPositionLeft = parseInt(ttPosition.left); + var ttPositionTop = parseInt(ttPosition.top, 10), + ttPositionLeft = parseInt(ttPosition.left, 10); scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft); }; @@ -444,8 +457,8 @@ angular.module('angular-tour.tour', []) */ .factory('orderedList', function() { var OrderedList = function() { - this.map = {}; - this._array = []; + this.map = {}; + this._array = []; }; OrderedList.prototype.set = function(key, value) { From 47708c375aea07502952fd86336a695f4812df64 Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 9 Feb 2016 14:38:47 -0500 Subject: [PATCH 03/12] working on scroll to function --- demo/index.html | 2 +- src/tour/tour.js | 92 +++++++++++++++++++++++++++++++++++++----------- 2 files changed, 72 insertions(+), 22 deletions(-) diff --git a/demo/index.html b/demo/index.html index 42936ed..bbe9fe0 100644 --- a/demo/index.html +++ b/demo/index.html @@ -19,7 +19,7 @@ - + diff --git a/src/tour/tour.js b/src/tour/tour.js index 2a0080e..f10925a 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -281,6 +281,8 @@ angular.module('angular-tour.tour', []) 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(); @@ -296,13 +298,13 @@ angular.module('angular-tour.tour', []) } // restrict right position if the tourtip doesn't fit in the container var containerWidth = container[0].getBoundingClientRect().width; - if (tourtip.width() + position.width > containerWidth) { + if (tourtipWidth + position.width > containerWidth) { restrictRight = containerWidth - position.left + scope.ttMargin; } } - var ttWidth = tourtip.width(); - var ttHeight = tourtip.height(); + var ttWidth = tourtipWidth; + var ttHeight = tourtipHeight; // Calculate the tourtip's top and left coordinates to center it var _left; @@ -362,7 +364,7 @@ angular.module('angular-tour.tour', []) if (!scope.ttContent) { return; } if (scope.ttAnimation) - tourtip.fadeIn(); + tourtip.css({ display: 'block' }); else tourtip.css({ display: 'block' }); @@ -371,20 +373,19 @@ angular.module('angular-tour.tour', []) if (targetElement === null || targetElement.length === 0) throw 'Target element could not be found. Selector: ' + scope.ttElement; - angular.element(scope.ttContainerElement).append(tourtip); + var containerEle = document.querySelectorAll(scope.ttContainerElement); + angular.element(containerEle).append(tourtip); var updatePosition = function() { - var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement); + 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 - var ttPositionTop = parseInt(ttPosition.top, 10), - ttPositionLeft = parseInt(ttPosition.left, 10); - scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft); + scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed); }; if (tourConfig.backDrop) { focusActiveElement(targetElement); } @@ -543,24 +544,73 @@ angular.module('angular-tour.tour', []) * Smoothly scroll to a dom element */ .factory('scrollTo', function() { - return function(target, containerElement, offsetY, offsetX, speed, ttPositionTop, ttPositionLeft) { + + var animationInProgress = false; + + function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) { + // if (animationInProgress) { return; } + + var currentLocation, + startTop = container.scrollTop, + startLeft = container.scrollLeft, + timeLapsed = 0, + duration = speed || 500, + distanceY = endTop - startTop + (offsetX || 0), + distanceX = endLeft - startLeft + (offsetY || 0), + timeProgress, + positionTop, + scrollHeight, + internalHeight; + + + var getEasingPattern = function(time) { + // easeInOutCubic: acceleration until halfway, then deceleration + return time < 0.5 ? (4 * time * time * time) : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; + }; + + var stopAnimation = function () { + currentLocation = container.scrollTop; + scrollHeight = container.scrollHeight; + internalHeight = container.clientHeight + currentLocation; + + if ( positionTop === endTop || currentLocation === endTop || internalHeight >= scrollHeight) { + clearInterval(runAnimation); + animationInProgress = false; + } + }; + + var animateScroll = function () { + timeLapsed += 16; + timeProgress = ( timeLapsed / duration ); + timeProgress = ( timeProgress > 1 ) ? 1 : timeProgress; + var multiplier = getEasingPattern(timeProgress); + positionTop = startTop + ( distanceY * multiplier ); + + // Move slightly following the easing pattern + container.scrollTop = positionTop; + container.scrollLeft = startLeft + ( distanceX * multiplier ); + + // Check if we have reached our destination + stopAnimation(); + }; + + animationInProgress = true; + var runAnimation = setInterval(animateScroll, 16); + } + + return function(target, containerSelector, offsetY, offsetX, speed) { + var container = document.querySelectorAll(containerSelector); 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); } + _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed); }; }) - .factory('debounce', function($timeout, $q) { + .factory('debounce', ['$timeout', '$q', + function($timeout, $q) { + return function(func, wait, immediate) { var timeout; var deferred = $q.defer(); @@ -585,4 +635,4 @@ angular.module('angular-tour.tour', []) return deferred.promise; }; }; - }); + }]); From b53887fc8794e0c8c187f04b7bdd5235c60ab40b Mon Sep 17 00:00:00 2001 From: David Meza Date: Mon, 15 Feb 2016 16:09:21 -0500 Subject: [PATCH 04/12] finally get the scrollTo service working properly --- src/tour/tour.js | 69 ++++++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/src/tour/tour.js b/src/tour/tour.js index f10925a..d0494f6 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -543,59 +543,70 @@ angular.module('angular-tour.tour', []) * ScrollTo * Smoothly scroll to a dom element */ - .factory('scrollTo', function() { + .factory('scrollTo', ['$interval', function($interval) { var animationInProgress = false; - function _autoScroll(container, endTop, endLeft, offsetY, offsetX, speed) { - // if (animationInProgress) { return; } + 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; } - var currentLocation, - startTop = container.scrollTop, + var startTop = container.scrollTop, startLeft = container.scrollLeft, timeLapsed = 0, duration = speed || 500, - distanceY = endTop - startTop + (offsetX || 0), - distanceX = endLeft - startLeft + (offsetY || 0), - timeProgress, - positionTop, - scrollHeight, - internalHeight; + 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 distanceY = finalY - startTop, // If we're going up, this will be a negative number + distanceX = finalX - startLeft, + currentPositionY, + currentPositionX, + timeProgress; - var getEasingPattern = function(time) { - // easeInOutCubic: acceleration until halfway, then deceleration - return time < 0.5 ? (4 * time * time * time) : (time - 1) * (2 * time - 2) * (2 * time - 2) + 1; - }; - var stopAnimation = function () { - currentLocation = container.scrollTop; - scrollHeight = container.scrollHeight; - internalHeight = container.clientHeight + currentLocation; - - if ( positionTop === endTop || currentLocation === endTop || internalHeight >= scrollHeight) { - clearInterval(runAnimation); + // If we have reached our destination clear the interval + if (currentPositionY === finalY && currentPositionX === finalX) { + $interval.cancel(runAnimation); animationInProgress = false; } }; var animateScroll = function () { timeLapsed += 16; + // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1 timeProgress = ( timeLapsed / duration ); + // 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); - positionTop = startTop + ( distanceY * multiplier ); - + // 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 = positionTop; - container.scrollLeft = startLeft + ( distanceX * multiplier ); - + container.scrollTop = currentPositionY; + container.scrollLeft = currentPositionX; // Check if we have reached our destination stopAnimation(); }; animationInProgress = true; - var runAnimation = setInterval(animateScroll, 16); + // Kicks off the function + var runAnimation = $interval(animateScroll, 16); } return function(target, containerSelector, offsetY, offsetX, speed) { @@ -606,7 +617,7 @@ angular.module('angular-tour.tour', []) } _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed); }; - }) + }]) .factory('debounce', ['$timeout', '$q', function($timeout, $q) { From ff3c609fc0d08de880cad93503fc86ec6a5771f1 Mon Sep 17 00:00:00 2001 From: David Meza Date: Mon, 15 Feb 2016 17:17:29 -0500 Subject: [PATCH 05/12] working on removing jquery dep from backdrop --- demo/index.html | 3 - dist/angular-tour-tpls.js | 163 +++++++++++++++++++--------- dist/angular-tour-tpls.min.js | 2 +- dist/angular-tour.css | 193 +++++++++++++++------------------- dist/angular-tour.css.map | 7 ++ dist/angular-tour.js | 163 +++++++++++++++++++--------- dist/angular-tour.min.js | 2 +- src/tour/tour.js | 36 ++++--- src/tour/tour.scss | 12 ++- 9 files changed, 348 insertions(+), 233 deletions(-) create mode 100644 dist/angular-tour.css.map diff --git a/demo/index.html b/demo/index.html index bbe9fe0..7f58d03 100644 --- a/demo/index.html +++ b/demo/index.html @@ -18,9 +18,6 @@ - - - diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index 97ccc41..54e312f 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-02-15 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -42,11 +42,10 @@ $scope.$watch(function () { return self.currentStep; }, function (val) { - if (firstCurrentStepChange) { + if (firstCurrentStepChange) firstCurrentStepChange = false; - } else { + else self.select(val); - } }); self.select = function (nextIndex) { if (!angular.isNumber(nextIndex)) @@ -60,19 +59,19 @@ if (self.currentStep !== nextIndex) { self.currentStep = nextIndex; } - if (self.currentStep > -1) + 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)) { + if (angular.isNumber(step.index) && !isNaN(step.index)) steps.set(step.index, step); - } else { + else steps.push(step); - } }; self.unselectAllSteps = function () { steps.forEach(function (step) { @@ -112,9 +111,11 @@ ctrl.currentStep = newVal; }); ctrl.postTourCallback = function (completed) { - angular.element('.tour-backdrop').remove(); + var backdropEle = document.getElementsByClassName('tour-backdrop'); + var active = document.getElementsByClassName('tour-element-active'); + angular.element(backdropEle).remove(); backDrop = false; - angular.element('.tour-element-active').removeClass('tour-element-active'); + angular.element(active).removeClass('tour-element-active'); if (completed && angular.isDefined(attrs.tourComplete)) { scope.$parent.$eval(attrs.tourComplete); } @@ -131,7 +132,8 @@ if (tourConfig.backDrop) { angular.element(tourConfig.containerElement).append(angular.element('
')); $timeout(function () { - $('.tour-backdrop').remove(); + var backdrop = document.getElementsByClassName('tour-backdrop'); + angular.element(backdrop).remove(); angular.element('
').insertBefore('.tour-tip'); }, 1000); backDrop = true; @@ -225,11 +227,10 @@ // wrap this in a time out because the tourtip won't compile right away $timeout(function () { scope.$watch('ttOpen', function (val) { - if (val) { + if (val) show(); - } else { + else hide(); - } }); }, 500); //determining target scope. It's used only when using virtual steps and there @@ -240,8 +241,9 @@ function getTargetScope() { var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; var targetScope = scope; - if (targetElement !== element && !scope.ttSourceScope) + if (targetElement !== element && !scope.ttSourceScope) { targetScope = targetElement.scope(); + } return targetScope; } function calculatePosition(element, container) { @@ -249,6 +251,8 @@ // 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 @@ -262,44 +266,45 @@ } // restrict right position if the tourtip doesn't fit in the container var containerWidth = container[0].getBoundingClientRect().width; - if (tourtip.width() + position.width > containerWidth) { + if (tourtipWidth + position.width > containerWidth) { restrictRight = containerWidth - position.left + scope.ttMargin; } } - var ttWidth = tourtip.width(); - var ttHeight = tourtip.height(); + var ttWidth = tourtipWidth; + var ttHeight = tourtipHeight; // Calculate the tourtip's top and left coordinates to center it + var _left; switch (scope.ttPlacement) { case 'right': - var _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal; + _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; + _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; + _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; + _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; + _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal; ttPosition = { top: top + scope.offsetVertical, left: _left > 0 ? _left : minimumLeft, @@ -307,7 +312,7 @@ }; break; default: - var _left = position.left - containerLeft + scope.offsetHorizontal; + _left = position.left - containerLeft + scope.offsetHorizontal; ttPosition = { top: top - ttHeight - scope.ttMargin + scope.offsetVertical, left: _left > 0 ? _left : minimumLeft @@ -322,26 +327,26 @@ if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.fadeIn(); - else { - tourtip.css({ display: 'block' }); - } + tourtip.css({ + opacity: 1, + visibility: 'visible' + }); var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; - if (targetElement == null || targetElement.length === 0) + if (targetElement === null || targetElement.length === 0) throw 'Target element could not be found. Selector: ' + scope.ttElement; - angular.element(scope.ttContainerElement).append(tourtip); + var containerEle = document.querySelectorAll(scope.ttContainerElement); + angular.element(containerEle).append(tourtip); var updatePosition = function () { - var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement); + 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 - var ttPositionTop = parseInt(ttPosition.top), ttPositionLeft = parseInt(ttPosition.left); - scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft); + scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed); }; - if (tourConfig.backDrop) + if (tourConfig.backDrop) { focusActiveElement(targetElement); + } angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50)); updatePosition(); if (scope.onStepShow) { @@ -357,9 +362,11 @@ angular.element($window).unbind('resize.' + scope.$id); } function focusActiveElement(el) { - angular.element('.tour-element-active').removeClass('tour-element-active'); - if (!scope.centered) + 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() { @@ -461,21 +468,77 @@ 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; + }).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; - $('html,' + containerElement).stop().animate({ - scrollTop: ttPositionTop + offsetY, - scrollLeft: ttPositionLeft + offsetX - }, speed); - } else { - $('html,' + containerElement).stop().animate({ scrollTop: 0 }, speed); + 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; + var stopAnimation = function () { + // If we have reached our destination clear the interval + if (currentPositionY === finalY && currentPositionX === finalX) { + $interval.cancel(runAnimation); + animationInProgress = false; + } + }; + var animateScroll = function () { + 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); } - }; - }).factory('debounce', [ + return function (target, containerSelector, offsetY, offsetX, speed) { + var container = document.querySelectorAll(containerSelector); + if (target) { + 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) { diff --git a/dist/angular-tour-tpls.min.js b/dist/angular-tour-tpls.min.js index aa1728c..89079cd 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 \n
\n

\n

\n \n ×\n
\n
\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

\n \n ×\n
\n\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(angular.element(d.containerElement).append(angular.element('
')),c(function(){var a=b.getElementsByClassName("tour-backdrop");angular.element(a).remove(),angular.element('
').insertBefore(".tour-tip")},1e3),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){u.css({opacity:1,visibility:"visible"});var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/dist/angular-tour.css b/dist/angular-tour.css index 3725810..d2a5b1f 100644 --- a/dist/angular-tour.css +++ b/dist/angular-tour.css @@ -1,117 +1,92 @@ .tour-tip { - display: none; + opacity: 0; + visibility: hidden; + -webkit-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -moz-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -ms-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -o-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + transition: opacity 0.6s, visibility 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; -} - -.tour-tip p { - color: #CBD0D4; - font-size: .9em; - line-height: 1.5em; - margin: 0 0 1.125em; -} - -.tour-tip button, -.tour-tip .button { - background-color: #189DAF; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset; - transition: background-color 300ms ease-out; - cursor: pointer; - font-family: inherit; - font-weight: 700; - line-height: 1; - position: relative; - text-decoration: none; - text-align: center; - display: inline-block; - font-size: .8em; - color: #FFF; - border-style: solid; - border-width: 0; - margin: 0 0 1.25em; - padding: .5em 1.25em .6em; - border-radius: 8px; -} - -.tour-tip button small, -.tour-tip .button small { - padding-top: .625em; - padding-bottom: .5625em; - -webkit-appearance: none; -} - -.tour-tip button:hover, -.tour-tip .button:hover { - background-color: #0A87A1; -} - -.tour-tip .tour-arrow { - display: block; - position: absolute; - left: 22px; - width: 0; - height: 0; - border: inset 14px; -} - -.tour-tip .tour-arrow.tt-top { - border-style: solid; - bottom: -27px; - border-color: #1C252E transparent transparent !important; -} - -.tour-tip .tour-arrow.tt-bottom { - border-style: solid; - top: -28px; - border-color: transparent transparent #1C252E !important; -} - -.tour-tip .tour-arrow.tt-right { - border-style: solid; - left: -27px; - top: 28px; - border-color: transparent #1C252E transparent transparent !important; -} - -.tour-tip .tour-arrow.tt-left { - border-style: solid; - left: 100%; - top: 28px; - border-color: transparent transparent transparent #1c252e !important; -} - -.tour-tip .tour-content-wrapper { - padding: 1.125em 1.25em 1.5em; -} - -.tour-tip .tour-content-wrapper .button { - margin-bottom: 0 !important; -} - -.tour-tip .tour-close-tip { - position: absolute; - right: 12px; - top: 10px; - color: #fff !important; - text-decoration: none; - font-size: 30px; - font-weight: 400; - line-height: 0.5 !important; - cursor: pointer; -} - -.tour-tip .tour-close-tip:hover, -.tour-tip .tour-close-tip:focus { - color: #eee !important; -} + border-radius: 10px; } + .tour-tip p { + color: #CBD0D4; + font-size: .9em; + line-height: 1.5em; + margin: 0 0 1.125em; } + .tour-tip button, .tour-tip .button { + background-color: #189DAF; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset; + transition: background-color 300ms ease-out; + cursor: pointer; + font-family: inherit; + font-weight: 700; + line-height: 1; + position: relative; + text-decoration: none; + text-align: center; + display: inline-block; + font-size: .8em; + color: #FFF; + border-style: solid; + border-width: 0; + margin: 0 0 1.25em; + padding: .5em 1.25em .6em; + border-radius: 8px; } + .tour-tip button small, .tour-tip .button small { + padding-top: .625em; + padding-bottom: .5625em; + -webkit-appearance: none; } + .tour-tip button:hover, .tour-tip .button:hover { + background-color: #0A87A1; } + .tour-tip .tour-arrow { + display: block; + position: absolute; + left: 22px; + width: 0; + height: 0; + border: inset 14px; } + .tour-tip .tour-arrow.tt-top { + border-style: solid; + bottom: -27px; + border-color: #1C252E transparent transparent !important; } + .tour-tip .tour-arrow.tt-bottom { + border-style: solid; + top: -28px; + border-color: transparent transparent #1C252E !important; } + .tour-tip .tour-arrow.tt-right { + border-style: solid; + left: -27px; + top: 28px; + border-color: transparent #1C252E transparent transparent !important; } + .tour-tip .tour-arrow.tt-left { + border-style: solid; + left: 100%; + top: 28px; + border-color: transparent transparent transparent #1c252e !important; } + .tour-tip .tour-content-wrapper { + padding: 1.125em 1.25em 1.5em; } + .tour-tip .tour-content-wrapper .button { + margin-bottom: 0 !important; } + .tour-tip .tour-close-tip { + position: absolute; + right: 12px; + top: 10px; + color: #fff !important; + text-decoration: none; + font-size: 30px; + font-weight: 400; + line-height: 0.5 !important; + cursor: pointer; } + .tour-tip .tour-close-tip:hover, .tour-tip .tour-close-tip:focus { + color: #eee !important; } .tour-backdrop { position: fixed; @@ -120,11 +95,11 @@ right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); - z-index: 100; -} + z-index: 100; } .tour-element-active { z-index: 120 !important; position: relative; - background: #fff; -} \ No newline at end of file + background: #fff; } + +/*# sourceMappingURL=angular-tour.css.map */ diff --git a/dist/angular-tour.css.map b/dist/angular-tour.css.map new file mode 100644 index 0000000..466856a --- /dev/null +++ b/dist/angular-tour.css.map @@ -0,0 +1,7 @@ +{ +"version": 3, +"mappings": "AAAA,SAAU;EACR,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,MAAM;EAElB,kBAAkB,EAAE,6DAA6D;EAC9E,eAAe,EAAE,6DAA6D;EAC7E,cAAc,EAAE,6DAA6D;EAC5E,aAAa,EAAE,6DAA6D;EACzE,UAAU,EAAE,6DAA6D;EAEjF,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,cAAc;EACvB,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,IAAI;EACV,WAAW,EAAE,OAAO;EACpB,WAAW,EAAE,GAAG;EAChB,SAAS,EAAE,KAAK;EAChB,aAAa,EAAE,IAAI;EACnB,WAAE;IACA,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,WAAW;EAErB,mCAAgB;IACd,gBAAgB,EAAE,OAAO;IACzB,UAAU,EAAE,sCAAsC;IAClD,UAAU,EAAE,+BAA+B;IAC3C,MAAM,EAAE,OAAO;IACf,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,YAAY;IACrB,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,GAAG;IAClB,+CAAM;MACJ,WAAW,EAAE,MAAM;MACnB,cAAc,EAAE,OAAO;MACvB,kBAAkB,EAAE,IAAI;IAE1B,+CAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,qBAAY;IAuBV,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,UAAU;IA3BlB,4BAAS;MACP,YAAY,EAAE,KAAK;MACnB,MAAM,EAAE,KAAK;MACb,YAAY,EAAE,0CAAyC;IAEzD,+BAAY;MACV,YAAY,EAAE,KAAK;MACnB,GAAG,EAAE,KAAK;MACV,YAAY,EAAE,0CAAyC;IAEzD,8BAAW;MACT,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,KAAK;MACX,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;IAErE,6BAAU;MACR,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,IAAI;MACV,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;EASvE,+BAAsB;IACpB,OAAO,EAAE,oBAAoB;IAE7B,uCAAQ;MACN,aAAa,EAAE,YAAW;EAG9B,yBAAgB;IACd,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,eAAc;IACrB,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,cAAY;IACzB,MAAM,EAAE,OAAO;IACf,gEAAiB;MACf,KAAK,EAAE,eAAc;;AAK3B,cAAe;EACb,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,CAAC;EAAE,IAAI,EAAE,CAAC;EAAE,KAAK,EAAE,CAAC;EAAE,MAAM,EAAE,CAAC;EACpC,UAAU,EAAE,kBAAc;EAC1B,OAAO,EAAE,GAAG;;AAGd,oBAAqB;EACnB,OAAO,EAAE,cAAc;EACvB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,IAAI", +"sources": ["../src/tour/tour.scss"], +"names": [], +"file": "angular-tour.css" +} \ No newline at end of file diff --git a/dist/angular-tour.js b/dist/angular-tour.js index 0ff1e06..19781b9 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-02-15 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -32,11 +32,10 @@ $scope.$watch(function () { return self.currentStep; }, function (val) { - if (firstCurrentStepChange) { + if (firstCurrentStepChange) firstCurrentStepChange = false; - } else { + else self.select(val); - } }); self.select = function (nextIndex) { if (!angular.isNumber(nextIndex)) @@ -50,19 +49,19 @@ if (self.currentStep !== nextIndex) { self.currentStep = nextIndex; } - if (self.currentStep > -1) + 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)) { + if (angular.isNumber(step.index) && !isNaN(step.index)) steps.set(step.index, step); - } else { + else steps.push(step); - } }; self.unselectAllSteps = function () { steps.forEach(function (step) { @@ -102,9 +101,11 @@ ctrl.currentStep = newVal; }); ctrl.postTourCallback = function (completed) { - angular.element('.tour-backdrop').remove(); + var backdropEle = document.getElementsByClassName('tour-backdrop'); + var active = document.getElementsByClassName('tour-element-active'); + angular.element(backdropEle).remove(); backDrop = false; - angular.element('.tour-element-active').removeClass('tour-element-active'); + angular.element(active).removeClass('tour-element-active'); if (completed && angular.isDefined(attrs.tourComplete)) { scope.$parent.$eval(attrs.tourComplete); } @@ -121,7 +122,8 @@ if (tourConfig.backDrop) { angular.element(tourConfig.containerElement).append(angular.element('
')); $timeout(function () { - $('.tour-backdrop').remove(); + var backdrop = document.getElementsByClassName('tour-backdrop'); + angular.element(backdrop).remove(); angular.element('
').insertBefore('.tour-tip'); }, 1000); backDrop = true; @@ -215,11 +217,10 @@ // wrap this in a time out because the tourtip won't compile right away $timeout(function () { scope.$watch('ttOpen', function (val) { - if (val) { + if (val) show(); - } else { + else hide(); - } }); }, 500); //determining target scope. It's used only when using virtual steps and there @@ -230,8 +231,9 @@ function getTargetScope() { var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; var targetScope = scope; - if (targetElement !== element && !scope.ttSourceScope) + if (targetElement !== element && !scope.ttSourceScope) { targetScope = targetElement.scope(); + } return targetScope; } function calculatePosition(element, container) { @@ -239,6 +241,8 @@ // 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 @@ -252,44 +256,45 @@ } // restrict right position if the tourtip doesn't fit in the container var containerWidth = container[0].getBoundingClientRect().width; - if (tourtip.width() + position.width > containerWidth) { + if (tourtipWidth + position.width > containerWidth) { restrictRight = containerWidth - position.left + scope.ttMargin; } } - var ttWidth = tourtip.width(); - var ttHeight = tourtip.height(); + var ttWidth = tourtipWidth; + var ttHeight = tourtipHeight; // Calculate the tourtip's top and left coordinates to center it + var _left; switch (scope.ttPlacement) { case 'right': - var _left = position.left - containerLeft + position.width + scope.ttMargin + scope.offsetHorizontal; + _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; + _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; + _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; + _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; + _left = position.left - containerLeft - ttWidth - scope.ttMargin + scope.offsetHorizontal; ttPosition = { top: top + scope.offsetVertical, left: _left > 0 ? _left : minimumLeft, @@ -297,7 +302,7 @@ }; break; default: - var _left = position.left - containerLeft + scope.offsetHorizontal; + _left = position.left - containerLeft + scope.offsetHorizontal; ttPosition = { top: top - ttHeight - scope.ttMargin + scope.offsetVertical, left: _left > 0 ? _left : minimumLeft @@ -312,26 +317,26 @@ if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.fadeIn(); - else { - tourtip.css({ display: 'block' }); - } + tourtip.css({ + opacity: 1, + visibility: 'visible' + }); var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; - if (targetElement == null || targetElement.length === 0) + if (targetElement === null || targetElement.length === 0) throw 'Target element could not be found. Selector: ' + scope.ttElement; - angular.element(scope.ttContainerElement).append(tourtip); + var containerEle = document.querySelectorAll(scope.ttContainerElement); + angular.element(containerEle).append(tourtip); var updatePosition = function () { - var offsetElement = scope.ttContainerElement === 'body' ? undefined : angular.element(scope.ttContainerElement); + 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 - var ttPositionTop = parseInt(ttPosition.top), ttPositionLeft = parseInt(ttPosition.left); - scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed, ttPositionTop, ttPositionLeft); + scrollTo(tourtip, scope.ttContainerElement, -150, -300, tourConfig.scrollSpeed); }; - if (tourConfig.backDrop) + if (tourConfig.backDrop) { focusActiveElement(targetElement); + } angular.element($window).bind('resize.' + scope.$id, debounce(updatePosition, 50)); updatePosition(); if (scope.onStepShow) { @@ -347,9 +352,11 @@ angular.element($window).unbind('resize.' + scope.$id); } function focusActiveElement(el) { - angular.element('.tour-element-active').removeClass('tour-element-active'); - if (!scope.centered) + 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() { @@ -451,21 +458,77 @@ 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; + }).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; - $('html,' + containerElement).stop().animate({ - scrollTop: ttPositionTop + offsetY, - scrollLeft: ttPositionLeft + offsetX - }, speed); - } else { - $('html,' + containerElement).stop().animate({ scrollTop: 0 }, speed); + 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; + var stopAnimation = function () { + // If we have reached our destination clear the interval + if (currentPositionY === finalY && currentPositionX === finalX) { + $interval.cancel(runAnimation); + animationInProgress = false; + } + }; + var animateScroll = function () { + 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); } - }; - }).factory('debounce', [ + return function (target, containerSelector, offsetY, offsetX, speed) { + var container = document.querySelectorAll(containerSelector); + if (target) { + 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) { diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js index 2bd71d5..13621e3 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>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(angular.element(d.containerElement).append(angular.element('
')),c(function(){var a=b.getElementsByClassName("tour-backdrop");angular.element(a).remove(),angular.element('
').insertBefore(".tour-tip")},1e3),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){u.css({opacity:1,visibility:"visible"});var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/src/tour/tour.js b/src/tour/tour.js index d0494f6..5fd4d1f 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -12,7 +12,7 @@ angular.module('angular-tour.tour', []) 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 + backDrop: true, // 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 }) @@ -138,12 +138,18 @@ angular.module('angular-tour.tour', []) ctrl.showStepCallback = function() { if (tourConfig.backDrop) { - angular.element(tourConfig.containerElement).append(angular.element('
')); + var div = document.createElement('div'); + div.className = 'tour-backdrop'; + var container = document.querySelector(tourConfig.containerElement); + angular.element(container).append(angular.element(div)); $timeout(function() { var backdrop = document.getElementsByClassName('tour-backdrop'); + var tooltip = document.getElementsByClassName('tour-tip'); + var div = document.createElement('div'); + div.className = 'tour-backdrop'; angular.element(backdrop).remove(); - angular.element('
').insertBefore('.tour-tip'); + tooltip.parentNode.insertBefore(div, tooltip); }, 1000); backDrop = true; @@ -363,10 +369,7 @@ angular.module('angular-tour.tour', []) function show() { if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.css({ display: 'block' }); - else - tourtip.css({ display: 'block' }); + tourtip.css({ opacity: 1, visibility: 'visible' }); var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; @@ -549,26 +552,25 @@ angular.module('angular-tour.tour', []) 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; } - var startTop = container.scrollTop, - startLeft = container.scrollLeft, - timeLapsed = 0, - duration = speed || 500, - offsetY = offsetY || 0, - offsetX = offsetX || 0; - + 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 distanceY = finalY - startTop, // If we're going up, this will be a negative number + 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, @@ -586,7 +588,7 @@ angular.module('angular-tour.tour', []) var animateScroll = function () { timeLapsed += 16; // get percentage of progress to the specified speed (e.g. 16/500). Should always be between 0 and 1 - timeProgress = ( timeLapsed / duration ); + 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 diff --git a/src/tour/tour.scss b/src/tour/tour.scss index 7a875db..6557d05 100644 --- a/src/tour/tour.scss +++ b/src/tour/tour.scss @@ -1,9 +1,17 @@ .tour-tip { - display: none; + opacity: 0; + visibility: hidden; + + -webkit-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -moz-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -ms-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -o-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + transition: opacity 0.6s, visibility 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; From ba10098b148886413d8c42891ce00e1ac89d4af6 Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 16 Feb 2016 10:14:42 -0500 Subject: [PATCH 06/12] fade in effect should work but doesn't. Maybe it's because of angular compilation --- dist/angular-tour.css | 14 +++++++++----- dist/angular-tour.css.map | 2 +- src/tour/tour.js | 17 +++++++++-------- src/tour/tour.scss | 17 ++++++++++++----- 4 files changed, 31 insertions(+), 19 deletions(-) diff --git a/dist/angular-tour.css b/dist/angular-tour.css index d2a5b1f..e152173 100644 --- a/dist/angular-tour.css +++ b/dist/angular-tour.css @@ -1,11 +1,12 @@ .tour-tip { opacity: 0; visibility: hidden; - -webkit-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -moz-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -ms-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -o-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + 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; @@ -16,6 +17,9 @@ font-weight: 400; max-width: 400px; border-radius: 10px; } + .tour-tip.show { + opacity: 1; + visibility: visible; } .tour-tip p { color: #CBD0D4; font-size: .9em; diff --git a/dist/angular-tour.css.map b/dist/angular-tour.css.map index 466856a..dc2fb30 100644 --- a/dist/angular-tour.css.map +++ b/dist/angular-tour.css.map @@ -1,6 +1,6 @@ { "version": 3, -"mappings": "AAAA,SAAU;EACR,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,MAAM;EAElB,kBAAkB,EAAE,6DAA6D;EAC9E,eAAe,EAAE,6DAA6D;EAC7E,cAAc,EAAE,6DAA6D;EAC5E,aAAa,EAAE,6DAA6D;EACzE,UAAU,EAAE,6DAA6D;EAEjF,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,cAAc;EACvB,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,IAAI;EACV,WAAW,EAAE,OAAO;EACpB,WAAW,EAAE,GAAG;EAChB,SAAS,EAAE,KAAK;EAChB,aAAa,EAAE,IAAI;EACnB,WAAE;IACA,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,WAAW;EAErB,mCAAgB;IACd,gBAAgB,EAAE,OAAO;IACzB,UAAU,EAAE,sCAAsC;IAClD,UAAU,EAAE,+BAA+B;IAC3C,MAAM,EAAE,OAAO;IACf,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,YAAY;IACrB,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,GAAG;IAClB,+CAAM;MACJ,WAAW,EAAE,MAAM;MACnB,cAAc,EAAE,OAAO;MACvB,kBAAkB,EAAE,IAAI;IAE1B,+CAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,qBAAY;IAuBV,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,UAAU;IA3BlB,4BAAS;MACP,YAAY,EAAE,KAAK;MACnB,MAAM,EAAE,KAAK;MACb,YAAY,EAAE,0CAAyC;IAEzD,+BAAY;MACV,YAAY,EAAE,KAAK;MACnB,GAAG,EAAE,KAAK;MACV,YAAY,EAAE,0CAAyC;IAEzD,8BAAW;MACT,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,KAAK;MACX,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;IAErE,6BAAU;MACR,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,IAAI;MACV,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;EASvE,+BAAsB;IACpB,OAAO,EAAE,oBAAoB;IAE7B,uCAAQ;MACN,aAAa,EAAE,YAAW;EAG9B,yBAAgB;IACd,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,eAAc;IACrB,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,cAAY;IACzB,MAAM,EAAE,OAAO;IACf,gEAAiB;MACf,KAAK,EAAE,eAAc;;AAK3B,cAAe;EACb,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,CAAC;EAAE,IAAI,EAAE,CAAC;EAAE,KAAK,EAAE,CAAC;EAAE,MAAM,EAAE,CAAC;EACpC,UAAU,EAAE,kBAAc;EAC1B,OAAO,EAAE,GAAG;;AAGd,oBAAqB;EACnB,OAAO,EAAE,cAAc;EACvB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,IAAI", +"mappings": "AAAA,SAAU;EACR,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,KAAK;EAEd,kBAAkB,EAAE,2DAA2D;EAC5E,eAAe,EAAE,2DAA2D;EAC3E,cAAc,EAAE,2DAA2D;EAC1E,aAAa,EAAE,2DAA2D;EACvE,UAAU,EAAE,2DAA2D;EAE/E,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,cAAc;EACvB,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,IAAI;EACV,WAAW,EAAE,OAAO;EACpB,WAAW,EAAE,GAAG;EAChB,SAAS,EAAE,KAAK;EAChB,aAAa,EAAE,IAAI;EAEnB,cAAO;IACL,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,OAAO;EAGrB,WAAE;IACA,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,WAAW;EAErB,mCAAgB;IACd,gBAAgB,EAAE,OAAO;IACzB,UAAU,EAAE,sCAAsC;IAClD,UAAU,EAAE,+BAA+B;IAC3C,MAAM,EAAE,OAAO;IACf,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,YAAY;IACrB,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,GAAG;IAClB,+CAAM;MACJ,WAAW,EAAE,MAAM;MACnB,cAAc,EAAE,OAAO;MACvB,kBAAkB,EAAE,IAAI;IAE1B,+CAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,qBAAY;IAuBV,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,UAAU;IA3BlB,4BAAS;MACP,YAAY,EAAE,KAAK;MACnB,MAAM,EAAE,KAAK;MACb,YAAY,EAAE,0CAAyC;IAEzD,+BAAY;MACV,YAAY,EAAE,KAAK;MACnB,GAAG,EAAE,KAAK;MACV,YAAY,EAAE,0CAAyC;IAEzD,8BAAW;MACT,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,KAAK;MACX,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;IAErE,6BAAU;MACR,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,IAAI;MACV,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;EASvE,+BAAsB;IACpB,OAAO,EAAE,oBAAoB;IAE7B,uCAAQ;MACN,aAAa,EAAE,YAAW;EAG9B,yBAAgB;IACd,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,eAAc;IACrB,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,cAAY;IACzB,MAAM,EAAE,OAAO;IACf,gEAAiB;MACf,KAAK,EAAE,eAAc;;AAK3B,cAAe;EACb,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,CAAC;EAAE,IAAI,EAAE,CAAC;EAAE,KAAK,EAAE,CAAC;EAAE,MAAM,EAAE,CAAC;EACpC,UAAU,EAAE,kBAAc;EAC1B,OAAO,EAAE,GAAG;;AAGd,oBAAqB;EACnB,OAAO,EAAE,cAAc;EACvB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,IAAI", "sources": ["../src/tour/tour.scss"], "names": [], "file": "angular-tour.css" diff --git a/src/tour/tour.js b/src/tour/tour.js index 5fd4d1f..2792015 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -12,7 +12,7 @@ angular.module('angular-tour.tour', []) 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: true, // if there is a backdrop (gray overlay) when tour starts + 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 }) @@ -138,19 +138,19 @@ angular.module('angular-tour.tour', []) ctrl.showStepCallback = function() { if (tourConfig.backDrop) { - var div = document.createElement('div'); - div.className = 'tour-backdrop'; - var container = document.querySelector(tourConfig.containerElement); - angular.element(container).append(angular.element(div)); + // var div = document.createElement('div'); + // div.className = 'tour-backdrop'; + // var container = document.querySelector(tourConfig.containerElement); + // angular.element(container).append(angular.element(div)); $timeout(function() { var backdrop = document.getElementsByClassName('tour-backdrop'); - var tooltip = document.getElementsByClassName('tour-tip'); + var tooltip = document.getElementsByClassName('tour-tip')[0]; var div = document.createElement('div'); div.className = 'tour-backdrop'; angular.element(backdrop).remove(); tooltip.parentNode.insertBefore(div, tooltip); - }, 1000); + }, 501); backDrop = true; } @@ -369,7 +369,7 @@ angular.module('angular-tour.tour', []) function show() { if (!scope.ttContent) { return; } - tourtip.css({ opacity: 1, visibility: 'visible' }); + tourtip.addClass('show'); var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; @@ -409,6 +409,7 @@ angular.module('angular-tour.tour', []) function hide() { tourtip.detach(); + tourtip.removeClass('show'); angular.element($window).unbind('resize.' + scope.$id); } diff --git a/src/tour/tour.scss b/src/tour/tour.scss index 6557d05..924ae98 100644 --- a/src/tour/tour.scss +++ b/src/tour/tour.scss @@ -1,12 +1,13 @@ .tour-tip { opacity: 0; visibility: hidden; + display: block; - -webkit-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -moz-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -ms-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - -o-transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); - transition: opacity 0.6s, visibility 0.6s cubic-bezier(0.345, 0, 0.25, 1); + -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; @@ -18,6 +19,12 @@ font-weight: 400; max-width: 400px; border-radius: 10px; + + &.show { + opacity: 1; + visibility: visible; + } + p { color: #CBD0D4; font-size: .9em; From 4af72a8b49efa694f6e90bc299b5ce002d3df27c Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 16 Feb 2016 10:24:06 -0500 Subject: [PATCH 07/12] found the issue --- src/tour/tour.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/tour/tour.js b/src/tour/tour.js index 2792015..6212637 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -369,8 +369,6 @@ angular.module('angular-tour.tour', []) function show() { if (!scope.ttContent) { return; } - tourtip.addClass('show'); - var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; if (targetElement === null || targetElement.length === 0) @@ -397,6 +395,9 @@ angular.module('angular-tour.tour', []) 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(); @@ -408,8 +409,8 @@ angular.module('angular-tour.tour', []) } function hide() { - tourtip.detach(); tourtip.removeClass('show'); + tourtip.detach(); angular.element($window).unbind('resize.' + scope.$id); } From 49f42b2f543835d216c3802bea93f954e0c42ce7 Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 16 Feb 2016 11:16:18 -0500 Subject: [PATCH 08/12] run grunt build task --- bower.json | 9 +- dist/angular-tour-tpls.js | 21 ++-- dist/angular-tour-tpls.min.js | 2 +- dist/angular-tour.css | 195 +++++++++++++++++++--------------- dist/angular-tour.css.map | 7 -- dist/angular-tour.js | 21 ++-- dist/angular-tour.min.js | 2 +- karma.conf.js | 1 - src/tour/tour.spec.js | 21 ++-- 9 files changed, 157 insertions(+), 122 deletions(-) delete mode 100644 dist/angular-tour.css.map diff --git a/bower.json b/bower.json index be7da60..949cc2c 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "angular-tour", - "version": "0.2.5", + "version": "0.3.0", "description": "An AngularJS directive for showcasing features of your website", "keywords": [ "angularjs", @@ -20,7 +20,7 @@ "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,8 +34,7 @@ "package.json" ], "dependencies": { - "angular": "~1.4.6", - "jquery": "~2.0.3" + "angular": "^1.5.0" }, "devDependencies": { "angular-mocks": "~1.4.6", @@ -43,6 +42,6 @@ }, "license": "MIT", "resolutions": { - "angular": "~1.4.6" + "angular": "^1.5" } } diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index 54e312f..6c946f7 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 - 2016-02-15 + * @version v0.2.5 - 2016-02-16 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -130,12 +130,18 @@ }; ctrl.showStepCallback = function () { if (tourConfig.backDrop) { - angular.element(tourConfig.containerElement).append(angular.element('
')); + // var div = document.createElement('div'); + // div.className = 'tour-backdrop'; + // var container = document.querySelector(tourConfig.containerElement); + // angular.element(container).append(angular.element(div)); $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(); - angular.element('
').insertBefore('.tour-tip'); - }, 1000); + tooltip.parentNode.insertBefore(div, tooltip); + }, 501); backDrop = true; } }; @@ -327,10 +333,6 @@ if (!scope.ttContent) { return; } - tourtip.css({ - opacity: 1, - visibility: 'visible' - }); 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; @@ -349,6 +351,8 @@ } 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 @@ -358,6 +362,7 @@ } } function hide() { + tourtip.removeClass('show'); tourtip.detach(); angular.element($window).unbind('resize.' + scope.$id); } diff --git a/dist/angular-tour-tpls.min.js b/dist/angular-tour-tpls.min.js index 89079cd..79443ac 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 \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(angular.element(d.containerElement).append(angular.element('
')),c(function(){var a=b.getElementsByClassName("tour-backdrop");angular.element(a).remove(),angular.element('
').insertBefore(".tour-tip")},1e3),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){u.css({opacity:1,visibility:"visible"});var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'
\n \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),u.addClass("show"),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/dist/angular-tour.css b/dist/angular-tour.css index e152173..b723980 100644 --- a/dist/angular-tour.css +++ b/dist/angular-tour.css @@ -2,10 +2,6 @@ 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; @@ -16,81 +12,114 @@ font-family: inherit; font-weight: 400; max-width: 400px; - border-radius: 10px; } - .tour-tip.show { - opacity: 1; - visibility: visible; } - .tour-tip p { - color: #CBD0D4; - font-size: .9em; - line-height: 1.5em; - margin: 0 0 1.125em; } - .tour-tip button, .tour-tip .button { - background-color: #189DAF; - box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset; - transition: background-color 300ms ease-out; - cursor: pointer; - font-family: inherit; - font-weight: 700; - line-height: 1; - position: relative; - text-decoration: none; - text-align: center; - display: inline-block; - font-size: .8em; - color: #FFF; - border-style: solid; - border-width: 0; - margin: 0 0 1.25em; - padding: .5em 1.25em .6em; - border-radius: 8px; } - .tour-tip button small, .tour-tip .button small { - padding-top: .625em; - padding-bottom: .5625em; - -webkit-appearance: none; } - .tour-tip button:hover, .tour-tip .button:hover { - background-color: #0A87A1; } - .tour-tip .tour-arrow { - display: block; - position: absolute; - left: 22px; - width: 0; - height: 0; - border: inset 14px; } - .tour-tip .tour-arrow.tt-top { - border-style: solid; - bottom: -27px; - border-color: #1C252E transparent transparent !important; } - .tour-tip .tour-arrow.tt-bottom { - border-style: solid; - top: -28px; - border-color: transparent transparent #1C252E !important; } - .tour-tip .tour-arrow.tt-right { - border-style: solid; - left: -27px; - top: 28px; - border-color: transparent #1C252E transparent transparent !important; } - .tour-tip .tour-arrow.tt-left { - border-style: solid; - left: 100%; - top: 28px; - border-color: transparent transparent transparent #1c252e !important; } - .tour-tip .tour-content-wrapper { - padding: 1.125em 1.25em 1.5em; } - .tour-tip .tour-content-wrapper .button { - margin-bottom: 0 !important; } - .tour-tip .tour-close-tip { - position: absolute; - right: 12px; - top: 10px; - color: #fff !important; - text-decoration: none; - font-size: 30px; - font-weight: 400; - line-height: 0.5 !important; - cursor: pointer; } - .tour-tip .tour-close-tip:hover, .tour-tip .tour-close-tip:focus { - color: #eee !important; } + border-radius: 10px; +} + +.tour-tip.show { + opacity: 1; + visibility: visible; +} + +.tour-tip p { + color: #CBD0D4; + font-size: .9em; + line-height: 1.5em; + margin: 0 0 1.125em; +} + +.tour-tip button, +.tour-tip .button { + background-color: #189DAF; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5) inset; + transition: background-color 300ms ease-out; + cursor: pointer; + font-family: inherit; + font-weight: 700; + line-height: 1; + position: relative; + text-decoration: none; + text-align: center; + display: inline-block; + font-size: .8em; + color: #FFF; + border-style: solid; + border-width: 0; + margin: 0 0 1.25em; + padding: .5em 1.25em .6em; + border-radius: 8px; +} + +.tour-tip button small, +.tour-tip .button small { + padding-top: .625em; + padding-bottom: .5625em; + -webkit-appearance: none; +} + +.tour-tip button:hover, +.tour-tip .button:hover { + background-color: #0A87A1; +} + +.tour-tip .tour-arrow { + display: block; + position: absolute; + left: 22px; + width: 0; + height: 0; + border: inset 14px; +} + +.tour-tip .tour-arrow.tt-top { + border-style: solid; + bottom: -27px; + border-color: #1C252E transparent transparent !important; +} + +.tour-tip .tour-arrow.tt-bottom { + border-style: solid; + top: -28px; + border-color: transparent transparent #1C252E !important; +} + +.tour-tip .tour-arrow.tt-right { + border-style: solid; + left: -27px; + top: 28px; + border-color: transparent #1C252E transparent transparent !important; +} + +.tour-tip .tour-arrow.tt-left { + border-style: solid; + left: 100%; + top: 28px; + border-color: transparent transparent transparent #1c252e !important; +} + +.tour-tip .tour-content-wrapper { + padding: 1.125em 1.25em 1.5em; +} + +.tour-tip .tour-content-wrapper .button { + margin-bottom: 0 !important; +} + +.tour-tip .tour-close-tip { + position: absolute; + right: 12px; + top: 10px; + color: #fff !important; + text-decoration: none; + font-size: 30px; + font-weight: 400; + line-height: 0.5 !important; + cursor: pointer; +} + +.tour-tip .tour-close-tip:hover, +.tour-tip .tour-close-tip:focus { + color: #eee !important; +} .tour-backdrop { position: fixed; @@ -99,11 +128,11 @@ right: 0; bottom: 0; background: rgba(0, 0, 0, 0.6); - z-index: 100; } + z-index: 100; +} .tour-element-active { z-index: 120 !important; position: relative; - background: #fff; } - -/*# sourceMappingURL=angular-tour.css.map */ + background: #fff; +} \ No newline at end of file diff --git a/dist/angular-tour.css.map b/dist/angular-tour.css.map deleted file mode 100644 index dc2fb30..0000000 --- a/dist/angular-tour.css.map +++ /dev/null @@ -1,7 +0,0 @@ -{ -"version": 3, -"mappings": "AAAA,SAAU;EACR,OAAO,EAAE,CAAC;EACV,UAAU,EAAE,MAAM;EAClB,OAAO,EAAE,KAAK;EAEd,kBAAkB,EAAE,2DAA2D;EAC5E,eAAe,EAAE,2DAA2D;EAC3E,cAAc,EAAE,2DAA2D;EAC1E,aAAa,EAAE,2DAA2D;EACvE,UAAU,EAAE,2DAA2D;EAE/E,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,OAAO;EACnB,KAAK,EAAE,IAAI;EACX,OAAO,EAAE,cAAc;EACvB,GAAG,EAAE,CAAC;EACN,IAAI,EAAE,IAAI;EACV,WAAW,EAAE,OAAO;EACpB,WAAW,EAAE,GAAG;EAChB,SAAS,EAAE,KAAK;EAChB,aAAa,EAAE,IAAI;EAEnB,cAAO;IACL,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,OAAO;EAGrB,WAAE;IACA,KAAK,EAAE,OAAO;IACd,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,KAAK;IAClB,MAAM,EAAE,WAAW;EAErB,mCAAgB;IACd,gBAAgB,EAAE,OAAO;IACzB,UAAU,EAAE,sCAAsC;IAClD,UAAU,EAAE,+BAA+B;IAC3C,MAAM,EAAE,OAAO;IACf,WAAW,EAAE,OAAO;IACpB,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,CAAC;IACd,QAAQ,EAAE,QAAQ;IAClB,eAAe,EAAE,IAAI;IACrB,UAAU,EAAE,MAAM;IAClB,OAAO,EAAE,YAAY;IACrB,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,IAAI;IACX,YAAY,EAAE,KAAK;IACnB,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,UAAU;IAClB,OAAO,EAAE,gBAAgB;IACzB,aAAa,EAAE,GAAG;IAClB,+CAAM;MACJ,WAAW,EAAE,MAAM;MACnB,cAAc,EAAE,OAAO;MACvB,kBAAkB,EAAE,IAAI;IAE1B,+CAAQ;MACN,gBAAgB,EAAE,OAAO;EAG7B,qBAAY;IAuBV,OAAO,EAAE,KAAK;IACd,QAAQ,EAAE,QAAQ;IAClB,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,CAAC;IACR,MAAM,EAAE,CAAC;IACT,MAAM,EAAE,UAAU;IA3BlB,4BAAS;MACP,YAAY,EAAE,KAAK;MACnB,MAAM,EAAE,KAAK;MACb,YAAY,EAAE,0CAAyC;IAEzD,+BAAY;MACV,YAAY,EAAE,KAAK;MACnB,GAAG,EAAE,KAAK;MACV,YAAY,EAAE,0CAAyC;IAEzD,8BAAW;MACT,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,KAAK;MACX,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;IAErE,6BAAU;MACR,YAAY,EAAE,KAAK;MACnB,IAAI,EAAE,IAAI;MACV,GAAG,EAAE,IAAI;MACT,YAAY,EAAE,sDAAqD;EASvE,+BAAsB;IACpB,OAAO,EAAE,oBAAoB;IAE7B,uCAAQ;MACN,aAAa,EAAE,YAAW;EAG9B,yBAAgB;IACd,QAAQ,EAAE,QAAQ;IAClB,KAAK,EAAE,IAAI;IACX,GAAG,EAAE,IAAI;IACT,KAAK,EAAE,eAAc;IACrB,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;IACf,WAAW,EAAE,GAAG;IAChB,WAAW,EAAE,cAAY;IACzB,MAAM,EAAE,OAAO;IACf,gEAAiB;MACf,KAAK,EAAE,eAAc;;AAK3B,cAAe;EACb,QAAQ,EAAE,KAAK;EACf,GAAG,EAAE,CAAC;EAAE,IAAI,EAAE,CAAC;EAAE,KAAK,EAAE,CAAC;EAAE,MAAM,EAAE,CAAC;EACpC,UAAU,EAAE,kBAAc;EAC1B,OAAO,EAAE,GAAG;;AAGd,oBAAqB;EACnB,OAAO,EAAE,cAAc;EACvB,QAAQ,EAAE,QAAQ;EAClB,UAAU,EAAE,IAAI", -"sources": ["../src/tour/tour.scss"], -"names": [], -"file": "angular-tour.css" -} \ No newline at end of file diff --git a/dist/angular-tour.js b/dist/angular-tour.js index 19781b9..79d4a0f 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 - 2016-02-15 + * @version v0.2.5 - 2016-02-16 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -120,12 +120,18 @@ }; ctrl.showStepCallback = function () { if (tourConfig.backDrop) { - angular.element(tourConfig.containerElement).append(angular.element('
')); + // var div = document.createElement('div'); + // div.className = 'tour-backdrop'; + // var container = document.querySelector(tourConfig.containerElement); + // angular.element(container).append(angular.element(div)); $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(); - angular.element('
').insertBefore('.tour-tip'); - }, 1000); + tooltip.parentNode.insertBefore(div, tooltip); + }, 501); backDrop = true; } }; @@ -317,10 +323,6 @@ if (!scope.ttContent) { return; } - tourtip.css({ - opacity: 1, - visibility: 'visible' - }); 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; @@ -339,6 +341,8 @@ } 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 @@ -348,6 +352,7 @@ } } function hide() { + tourtip.removeClass('show'); tourtip.detach(); angular.element($window).unbind('resize.' + scope.$id); } diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js index 13621e3..0a104ca 100644 --- a/dist/angular-tour.min.js +++ b/dist/angular-tour.min.js @@ -1 +1 @@ -!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(angular.element(d.containerElement).append(angular.element('
')),c(function(){var a=b.getElementsByClassName("tour-backdrop");angular.element(a).remove(),angular.element('
').insertBefore(".tour-tip")},1e3),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){u.css({opacity:1,visibility:"visible"});var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),u.addClass("show"),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file 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/src/tour/tour.spec.js b/src/tour/tour.spec.js index da3f40b..9386689 100644 --- a/src/tour/tour.spec.js +++ b/src/tour/tour.spec.js @@ -441,27 +441,32 @@ describe('Directive: tour', function () { }); describe('scroll service', function() { - var target, scope, scrollTo; + var div, target, body, scope, scrollTo; beforeEach(inject(function (_scrollTo_) { scope = $rootScope.$new(); scrollTo = _scrollTo_; - - target = angular.element('
'); - $('body').height(window.innerHeight*2).append(target); + div = document.createElement('div'); + div.style.position = 'absolute'; + div.style.top = '500px'; + target = angular.element(div); + body = angular.element(document).find('body') + body[0].style.height = (window.innerHeight*3).toString() + 'px'; + body.append(target); window.scrollTo(0, 0); })); it('should scroll to position', function () { - expect($(window).scrollTop()).toEqual(0); + expect( body[0].scrollTop ).toEqual(0); + debugger; - scrollTo(target, 'body', -100, -100, 500, 200, 0); + scrollTo(target, 'body', -100, -100, 500); waitsFor(function() { - return $(window).scrollTop() === 100; + return body.scrollTop === 100; }, 'Current position to be 100px'); runs(function() { - expect($(window).scrollTop()).toEqual(100); + expect( body.scrollTop ).toEqual(100); }); }); }); From e2b92e208c8f048091b83979b172c5315107f10e Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 16 Feb 2016 11:42:06 -0500 Subject: [PATCH 09/12] missed a couple references --- dist/angular-tour-tpls.js | 10 ++++++++-- dist/angular-tour-tpls.min.js | 2 +- dist/angular-tour.js | 10 ++++++++-- dist/angular-tour.min.js | 2 +- src/tour/tour.js | 8 ++++++-- 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index 6c946f7..f457009 100644 --- a/dist/angular-tour-tpls.js +++ b/dist/angular-tour-tpls.js @@ -140,6 +140,10 @@ 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; @@ -245,7 +249,8 @@ //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 target = document.querySelectorAll(scope.ttElement); + var targetElement = scope.ttElement ? angular.element(target) : element; var targetScope = scope; if (targetElement !== element && !scope.ttSourceScope) { targetScope = targetElement.scope(); @@ -333,7 +338,8 @@ if (!scope.ttContent) { return; } - var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; + 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); diff --git a/dist/angular-tour-tpls.min.js b/dist/angular-tour-tpls.min.js index 79443ac..5889641 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 \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),u.addClass("show"),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'
\n \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/dist/angular-tour.js b/dist/angular-tour.js index 79d4a0f..fe5feb5 100644 --- a/dist/angular-tour.js +++ b/dist/angular-tour.js @@ -130,6 +130,10 @@ 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; @@ -235,7 +239,8 @@ //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 target = document.querySelectorAll(scope.ttElement); + var targetElement = scope.ttElement ? angular.element(target) : element; var targetScope = scope; if (targetElement !== element && !scope.ttSourceScope) { targetScope = targetElement.scope(); @@ -323,7 +328,8 @@ if (!scope.ttContent) { return; } - var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; + 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); diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js index 0a104ca..c1d219b 100644 --- a/dist/angular-tour.min.js +++ b/dist/angular-tour.min.js @@ -1 +1 @@ -!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=f.ttElement?angular.element(f.ttElement):m,b=f;return a===m||f.ttSourceScope||(b=a.scope()),b}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=f.ttElement?angular.element(f.ttElement):m;if(null===a||0===a.length)throw"Target element could not be found. Selector: "+f.ttElement;var e=b.querySelectorAll(f.ttContainerElement);angular.element(e).append(u);var k=function(){var b="body"===f.ttContainerElement?c:angular.element(e),d=q(a,b);u.css(d),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(a),angular.element(d).bind("resize."+f.$id,j(k,50)),k(),u.addClass("show"),f.onStepShow){var l=p();g(function(){l.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/src/tour/tour.js b/src/tour/tour.js index 6212637..2bfbe05 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -149,6 +149,8 @@ angular.module('angular-tour.tour', []) 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); @@ -275,7 +277,8 @@ angular.module('angular-tour.tour', []) //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 target = document.querySelectorAll(scope.ttElement); + var targetElement = scope.ttElement ? angular.element(target) : element; var targetScope = scope; if (targetElement !== element && !scope.ttSourceScope) { targetScope = targetElement.scope(); } @@ -369,7 +372,8 @@ angular.module('angular-tour.tour', []) function show() { if (!scope.ttContent) { return; } - var targetElement = scope.ttElement ? angular.element(scope.ttElement) : element; + 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; From 3f73a018843b9d79e11030daccb31cfda0f6216c Mon Sep 17 00:00:00 2001 From: David Meza Date: Tue, 8 Mar 2016 16:56:15 -0500 Subject: [PATCH 10/12] pass first 29 tests --- bower.json | 19 +++--- dist/angular-tour-tpls.js | 21 +++--- dist/angular-tour-tpls.min.js | 2 +- dist/angular-tour.js | 21 +++--- dist/angular-tour.min.js | 2 +- package.json | 10 +-- src/tour/tour.js | 20 ++---- src/tour/tour.spec.js | 116 ++++++++++++++++++++-------------- 8 files changed, 105 insertions(+), 106 deletions(-) diff --git a/bower.json b/bower.json index 949cc2c..777b523 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { - "name": "angular-tour", + "name": "angular-tour-no-jquery", "version": "0.3.0", "description": "An AngularJS directive for showcasing features of your website", "keywords": [ @@ -8,16 +8,16 @@ "angular", "module" ], - "homepage": "https://github.com/DaftMonk/angular-tour", - "bugs": "https://github.com/DaftMonk/angular-tour/issues", + "homepage": "https://github.com/david-meza/angular-tour", + "bugs": "https://github.com/david-meza/angular-tour/issues", "author": { "name": "Tyler Henkel", "email": "", - "url": "https://github.com/DaftMonk" + "url": "https://github.com/david-meza" }, "repository": { "type": "git", - "url": "git://github.com/DaftMonk/angular-tour.git" + "url": "git@github.com:david-meza/angular-tour.git" }, "main": [ "./dist/angular-tour-tpls.js", @@ -37,11 +37,8 @@ "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.5" - } + "license": "MIT" } diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index f457009..7221cad 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 - 2016-02-16 + * @version v0.2.5 - 2016-03-08 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -130,10 +130,6 @@ }; ctrl.showStepCallback = function () { if (tourConfig.backDrop) { - // var div = document.createElement('div'); - // div.className = 'tour-backdrop'; - // var container = document.querySelector(tourConfig.containerElement); - // angular.element(container).append(angular.element(div)); $timeout(function () { var backdrop = document.getElementsByClassName('tour-backdrop'); var tooltip = document.getElementsByClassName('tour-tip')[0]; @@ -509,14 +505,15 @@ 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; - var stopAnimation = function () { + function stopAnimation() { // If we have reached our destination clear the interval if (currentPositionY === finalY && currentPositionX === finalX) { $interval.cancel(runAnimation); animationInProgress = false; } - }; - var animateScroll = function () { + } + 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; @@ -535,17 +532,15 @@ 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); - if (target) { - offsetY = offsetY || -100; - offsetX = offsetX || -100; - } + offsetY = offsetY || -100; + offsetX = offsetX || -100; _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed); }; } diff --git a/dist/angular-tour-tpls.min.js b/dist/angular-tour-tpls.min.js index 5889641..e83cc8c 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 \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'
\n \n
\n

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/dist/angular-tour.js b/dist/angular-tour.js index fe5feb5..c5899ad 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 - 2016-02-16 + * @version v0.2.5 - 2016-03-08 * @link https://github.com/DaftMonk/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT @@ -120,10 +120,6 @@ }; ctrl.showStepCallback = function () { if (tourConfig.backDrop) { - // var div = document.createElement('div'); - // div.className = 'tour-backdrop'; - // var container = document.querySelector(tourConfig.containerElement); - // angular.element(container).append(angular.element(div)); $timeout(function () { var backdrop = document.getElementsByClassName('tour-backdrop'); var tooltip = document.getElementsByClassName('tour-tip')[0]; @@ -499,14 +495,15 @@ 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; - var stopAnimation = function () { + function stopAnimation() { // If we have reached our destination clear the interval if (currentPositionY === finalY && currentPositionX === finalX) { $interval.cancel(runAnimation); animationInProgress = false; } - }; - var animateScroll = function () { + } + 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; @@ -525,17 +522,15 @@ 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); - if (target) { - offsetY = offsetY || -100; - offsetX = offsetX || -100; - } + offsetY = offsetY || -100; + offsetX = offsetX || -100; _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed); }; } diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js index c1d219b..04c0a52 100644 --- a/dist/angular-tour.min.js +++ b/dist/angular-tour.min.js @@ -1 +1 @@ -!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){if(!e){i=i||500,g=g||0,h=h||0;var j=d+g;0>j?j=0:j>b.scrollHeight&&(j=b.scrollHeight);var k=f+h;0>k?k=0:k>b.scrollWidth&&(k=b.scrollWidth);var l,m,n,o=b.scrollTop,p=b.scrollLeft,q=0,r=j-o,s=k-p,t=function(){l===j&&m===k&&(a.cancel(v),e=!1)},u=function(){q+=16,n=q/i,n=n>1?1:n;var a=c(n),d=r*a,e=s*a;l=o+d,m=p+e,b.scrollTop=l,b.scrollLeft=m,t()};e=!0;var v=a(u,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);a&&(e=e||-100,f=f||-100),d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file diff --git a/package.json b/package.json index bf38be7..2a82bd1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "angular-tour", - "version": "0.2.5", + "version": "0.3.0", "description": "An AngularJS directive for showcasing features of your website", "keywords": [ "angularjs", @@ -8,16 +8,16 @@ "angular", "module" ], - "homepage": "https://github.com/DaftMonk/angular-tour", - "bugs": "https://github.com/DaftMonk/angular-tour/issues", + "homepage": "https://github.com/david-meza/angular-tour", + "bugs": "https://github.com/david-meza/angular-tour/issues", "author": { "name": "Tyler Henkel", "email": "", - "url": "https://github.com/DaftMonk" + "url": "https://github.com/david-meza" }, "repository": { "type": "git", - "url": "git://github.com/DaftMonk/angular-tour.git" + "url": "git@github.com:david-meza/angular-tour.git" }, "dependencies": {}, "devDependencies": { diff --git a/src/tour/tour.js b/src/tour/tour.js index 2bfbe05..1da1f1e 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -138,10 +138,6 @@ angular.module('angular-tour.tour', []) ctrl.showStepCallback = function() { if (tourConfig.backDrop) { - // var div = document.createElement('div'); - // div.className = 'tour-backdrop'; - // var container = document.querySelector(tourConfig.containerElement); - // angular.element(container).append(angular.element(div)); $timeout(function() { var backdrop = document.getElementsByClassName('tour-backdrop'); @@ -582,16 +578,16 @@ angular.module('angular-tour.tour', []) currentPositionX, timeProgress; - - var stopAnimation = function () { + function stopAnimation() { // If we have reached our destination clear the interval if (currentPositionY === finalY && currentPositionX === finalX) { $interval.cancel(runAnimation); animationInProgress = false; } - }; + } - var animateScroll = function () { + 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 ); @@ -610,7 +606,7 @@ angular.module('angular-tour.tour', []) container.scrollLeft = currentPositionX; // Check if we have reached our destination stopAnimation(); - }; + } animationInProgress = true; // Kicks off the function @@ -619,10 +615,8 @@ angular.module('angular-tour.tour', []) return function(target, containerSelector, offsetY, offsetX, speed) { var container = document.querySelectorAll(containerSelector); - if (target) { - offsetY = offsetY || -100; - offsetX = offsetX || -100; - } + offsetY = offsetY || -100; + offsetX = offsetX || -100; _autoScroll(container[0], target[0].offsetTop, target[0].offsetLeft, offsetY, offsetX, speed); }; }]) diff --git a/src/tour/tour.spec.js b/src/tour/tour.spec.js index 9386689..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,33 +450,42 @@ describe('Directive: tour', function () { }); describe('scroll service', function() { - var div, target, body, scope, scrollTo; + var div, target, bodyEle, body, scope, scrollTo, arrived; beforeEach(inject(function (_scrollTo_) { scope = $rootScope.$new(); scrollTo = _scrollTo_; - div = document.createElement('div'); - div.style.position = 'absolute'; - div.style.top = '500px'; + + div = htmlToElement('
Some element absolutely positioned
')[0]; target = angular.element(div); - body = angular.element(document).find('body') - body[0].style.height = (window.innerHeight*3).toString() + 'px'; + + bodyEle = document.querySelector('body'); + bodyEle.style.height = window.innerHeight * 2 + 'px'; + body = angular.element(bodyEle); + body.append(target); - window.scrollTo(0, 0); })); it('should scroll to position', function () { - expect( body[0].scrollTop ).toEqual(0); - debugger; - + expect( bodyEle.scrollTop ).toEqual(0); scrollTo(target, 'body', -100, -100, 500); + + runs(function() { + arrived = false; + + setTimeout(function(){ + arrived = true; + }, 500); + }); + waitsFor(function() { - return body.scrollTop === 100; - }, 'Current position to be 100px'); + return arrived; + }, 'Should have scrolled', 1000); runs(function() { - expect( body.scrollTop ).toEqual(100); + expect( bodyEle.scrollTop ).toBeGreaterThan(0); }); + }); }); }); From 4b2fac0eb9e7c8ee337239b44573cc63ac1e730d Mon Sep 17 00:00:00 2001 From: David Date: Sat, 12 Mar 2016 19:11:26 -0500 Subject: [PATCH 11/12] wrap in closure --- bower.json | 2 +- dist/angular-tour-tpls.js | 1048 ++++++++++++++--------------- dist/angular-tour-tpls.min.js | 2 +- dist/angular-tour.js | 1048 ++++++++++++++--------------- dist/angular-tour.min.js | 2 +- package.json | 2 +- src/tour/tour.js | 1162 +++++++++++++++++---------------- 7 files changed, 1637 insertions(+), 1629 deletions(-) diff --git a/bower.json b/bower.json index 777b523..2a65ad2 100644 --- a/bower.json +++ b/bower.json @@ -11,7 +11,7 @@ "homepage": "https://github.com/david-meza/angular-tour", "bugs": "https://github.com/david-meza/angular-tour/issues", "author": { - "name": "Tyler Henkel", + "name": "David Meza", "email": "", "url": "https://github.com/david-meza" }, diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index 7221cad..132370e 100644 --- a/dist/angular-tour-tpls.js +++ b/dist/angular-tour-tpls.js @@ -1,7 +1,7 @@ /** * An AngularJS directive for showcasing features of your website - * @version v0.2.5 - 2016-03-08 - * @link https://github.com/DaftMonk/angular-tour + * @version v0.3.0 - 2016-03-12 + * @link https://github.com/david-meza/angular-tour * @author Tyler Henkel * @license MIT License, http://www.opensource.org/licenses/MIT */ @@ -19,559 +19,561 @@ $templateCache.put('tour/tour.tpl.html', '
\n' + ' \n' + '
\n' + '

\n' + '

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

\n

\n \n ×\n
\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,margin:28,backDrop:!1,useSourceScope:!1,containerElement:"body"}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b(),e=!0;c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.showStepCallback=angular.noop,c.currentStep=-1,a.$watch(function(){return c.currentStep},function(a){e?e=!1:c.select(a)}),c.select=function(a){if(angular.isNumber(a)){c.unselectAllSteps();var b=d.get(a);b&&(b.ttOpen=!0),c.currentStep!==a&&(c.currentStep=a),c.currentStep>-1&&c.showStepCallback(),a>=d.getCount()&&c.postTourCallback(!0),c.postStepCallback()}},c.addStep=function(a){angular.isNumber(a.index)&&!isNaN(a.index)?d.set(a.index,a):d.push(a)},c.unselectAllSteps=function(){d.forEach(function(a){a.ttOpen=!1})},c.cancelTour=function(){c.unselectAllSteps(),c.postTourCallback(!1)},a.openTour=function(){var a=c.currentStep>=d.getCount()||c.currentStep<0?0:c.currentStep;c.select(a)},a.closeTour=function(){c.cancelTour()}}]).directive("tour",["$parse","$timeout","tourConfig",function(a,c,d){return{controller:"TourController",restrict:"EA",scope:!0,link:function(e,f,g,h){if(!angular.isDefined(g.step))throw"The directive requires a `step` attribute to bind the current step to.";var i=a(g.step),j=!1;e.$watch(g.step,function(a){h.currentStep=a}),h.postTourCallback=function(a){var c=b.getElementsByClassName("tour-backdrop"),d=b.getElementsByClassName("tour-element-active");angular.element(c).remove(),j=!1,angular.element(d).removeClass("tour-element-active"),a&&angular.isDefined(g.tourComplete)&&e.$parent.$eval(g.tourComplete),angular.isDefined(g.postTour)&&e.$parent.$eval(g.postTour)},h.postStepCallback=function(){angular.isDefined(g.postStep)&&e.$parent.$eval(g.postStep)},h.showStepCallback=function(){d.backDrop&&(c(function(){var a=b.getElementsByClassName("tour-backdrop"),c=b.getElementsByClassName("tour-tip")[0],d=b.createElement("div");d.className="tour-backdrop",angular.element(a).remove(),angular.isDefined(c)&&c.parentNode.insertBefore(d,c)},501),j=!0)},e.setCurrentStep=function(a){i.assign(e.$parent,a),h.currentStep=a},e.getCurrentStep=function(){return h.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig","debounce","$q",function(d,e,f,g,h,i,j,k){var l=(f.startSymbol(),f.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(f,m,n,o){function p(){var a=b.querySelectorAll(f.ttElement),c=f.ttElement?angular.element(a):m,d=f;return c===m||f.ttSourceScope||(d=c.scope()),d}function q(b,c){var d,e,g=0,h=u[0].offsetWidth,i=u[0].offsetHeight,j=b[0].getBoundingClientRect(),k=j.top+a.pageYOffset,l=0;if(c&&c[0]){k=k-c[0].getBoundingClientRect().top+c[0].scrollTop,"fixed"===c.css("position")&&(l=c[0].getBoundingClientRect().left);var m=c[0].getBoundingClientRect().width;h+j.width>m&&(d=m-j.left+f.ttMargin)}var n,o=h,p=i;switch(f.ttPlacement){case"right":n=j.left-l+j.width+f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g};break;case"bottom":n=j.left-l+f.offsetHorizontal,e={top:k+j.height+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.5*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"center-top":n=j.left-l+.5*(j.width-o)+f.offsetHorizontal,e={top:k+.1*(j.height-p)+f.ttMargin+f.offsetVertical,left:n>0?n:g};break;case"left":n=j.left-l-o-f.ttMargin+f.offsetHorizontal,e={top:k+f.offsetVertical,left:n>0?n:g,right:d};break;default:n=j.left-l+f.offsetHorizontal,e={top:k-p-f.ttMargin+f.offsetVertical,left:n>0?n:g}}return e.top+="px",e.left+="px",e}function r(){if(f.ttContent){var a=b.querySelectorAll(f.ttElement),e=f.ttElement?angular.element(a):m;if(null===e||0===e.length)throw"Target element could not be found. Selector: "+f.ttElement;var k=b.querySelectorAll(f.ttContainerElement);angular.element(k).append(u);var l=function(){var a="body"===f.ttContainerElement?c:angular.element(k),b=q(e,a);u.css(b),h(u,f.ttContainerElement,-150,-300,i.scrollSpeed)};if(i.backDrop&&t(e),angular.element(d).bind("resize."+f.$id,j(l,50)),l(),u.addClass("show"),f.onStepShow){var n=p();g(function(){n.$eval(f.onStepShow)},300)}}}function s(){u.removeClass("show"),u.detach(),angular.element(d).unbind("resize."+f.$id)}function t(a){var c=b.getElementsByClassName("tour-element-active");angular.element(c).removeClass("tour-element-active"),f.centered||a.addClass("tour-element-active")}n.$observe("tourtip",function(a){f.ttContent=a}),n.$observe("tourtipPlacement",function(a){f.ttPlacement=(a||i.placement).toLowerCase().trim(),f.centered=0===f.ttPlacement.indexOf("center")}),n.$observe("tourtipNextLabel",function(a){f.ttNextLabel=a||i.nextLabel}),n.$observe("tourtipContainerElement",function(a){f.ttContainerElement=a||i.containerElement}),n.$observe("tourtipMargin",function(a){f.ttMargin=parseInt(a,10)||i.margin}),n.$observe("tourtipOffsetVertical",function(a){f.offsetVertical=parseInt(a,10)||0}),n.$observe("tourtipOffsetHorizontal",function(a){f.offsetHorizontal=parseInt(a,10)||0}),n.$observe("onShow",function(a){f.onStepShow=a||null}),n.$observe("onProceed",function(a){f.onStepProceed=a||null}),n.$observe("tourtipElement",function(a){f.ttElement=a||null}),n.$observe("tourtipTitle",function(a){f.ttTitle=a||null}),n.$observe("useSourceScope",function(a){f.ttSourceScope=a?"true"===a:i.useSourceScope}),f.ttNextLabel=i.nextLabel,f.ttContainerElement=i.containerElement,f.ttPlacement=i.placement.toLowerCase().trim(),f.centered=!1,f.ttMargin=i.margin,f.offsetHorizontal=0,f.offsetVertical=0,f.ttSourceScope=i.useSourceScope,f.ttOpen=!1,f.ttAnimation=i.animation,f.index=parseInt(n.tourtipStep,10);var u=e(l)(f);o.addStep(f),g(function(){f.$watch("ttOpen",function(a){a?r():s()})},500),f.$on("$destroy",function(){angular.element(d).unbind("resize."+f.$id),u.remove(),u=null}),f.proceed=function(){if(f.onStepProceed){var a=p(),b=a.$eval(f.onStepProceed);k.resolve(b).then(function(){f.setCurrentStep(f.getCurrentStep()+1)})}else f.setCurrentStep(f.getCurrentStep()+1)}}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;da?4*a*a*a:(a-1)*(2*a-2)*(2*a-2)+1}function d(b,d,f,g,h,i){function j(){n===l&&o===m&&(a.cancel(v),e=!1)}function k(){console.log("called"),s+=16,p=s/i,p=p>1?1:p;var a=c(p),d=t*a,e=u*a;n=q+d,o=r+e,b.scrollTop=n,b.scrollLeft=o,j()}if(!e){i=i||500,g=g||0,h=h||0;var l=d+g;0>l?l=0:l>b.scrollHeight&&(l=b.scrollHeight);var m=f+h;0>m?m=0:m>b.scrollWidth&&(m=b.scrollWidth);var n,o,p,q=b.scrollTop,r=b.scrollLeft,s=0,t=l-q,u=m-r;e=!0;var v=a(k,16)}}var e=!1;return function(a,c,e,f,g){var h=b.querySelectorAll(c);e=e||-100,f=f||-100,d(h[0],a[0].offsetTop,a[0].offsetLeft,e,f,g)}}]).factory("debounce",["$timeout","$q",function(a,b){return function(c,d,e){var f,g=b.defer();return function(){var h=this,i=arguments,j=function(){f=null,e||(g.resolve(c.apply(h,i)),g=b.defer())},k=e&&!f;return f&&a.cancel(f),f=a(j,d),k&&(g.resolve(c.apply(h,i)),g=b.defer()),g.promise}}}])}(window,document); \ No newline at end of file +!function(a,b,c){"use strict";angular.module("angular-tour",["angular-tour.tpls","angular-tour.tour"]),angular.module("angular-tour.tpls",["tour/tour.tpl.html"]),angular.module("tour/tour.tpl.html",[]).run(["$templateCache",function(a){a.put("tour/tour.tpl.html",'
\n \n
\n

\n

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