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