diff --git a/Gruntfile.js b/Gruntfile.js index 3d1b1e5..7bea49f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,6 +1,6 @@ 'use strict'; -module.exports = function (grunt) { +module.exports = function(grunt){ // Load grunt tasks automatically require('load-grunt-tasks')(grunt); @@ -14,6 +14,16 @@ module.exports = function (grunt) { // Project configuration. grunt.initConfig({ + sass: { + options: { + sourceMap: true + }, + dist: { + files: { + './dist/angular-tour.css': './src/tour/**/*.scss' + } + } + }, yeoman: yeomanConfig, pkg: grunt.file.readJSON('package.json'), modules: [],//to be filled in by buildmodules task @@ -23,12 +33,12 @@ module.exports = function (grunt) { tplmodules: 'angular.module("<%= pkg.name %>.tpls", [<%= tplModules %>]);', all: 'angular.module("<%= pkg.name %>", ["<%= pkg.name %>.tpls", <%= srcModules %>]);', banner: '/**\n' + - ' * <%= pkg.description %>\n' + - ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + - ' * @link <%= pkg.homepage %>\n' + - ' * @author <%= pkg.author.name %>\n' + - ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + - ' */\n\n' + ' * <%= pkg.description %>\n' + + ' * @version v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %>\n' + + ' * @link <%= pkg.homepage %>\n' + + ' * @author <%= pkg.author.name %>\n' + + ' * @license MIT License, http://www.opensource.org/licenses/MIT\n' + + ' */\n\n' }, // Watches files for changes and runs tasks based on the changed files @@ -44,9 +54,9 @@ module.exports = function (grunt) { files: ['<%= yeoman.src %>/**/*.spec.js'], tasks: ['karma'] }, - compass: { + sass:{ files: ['<%= yeoman.src %>/**/*.{scss,sass}'], - tasks: ['compass:server', 'autoprefixer'] + tasks: ['sass', 'autoprefixer'] }, gruntfile: { files: ['Gruntfile.js'] @@ -131,26 +141,6 @@ module.exports = function (grunt) { } }, - // Compiles Sass to CSS and generates necessary files if requested - compass: { - options: { - sassDir: '<%= yeoman.src %>', - cssDir: '.tmp/styles', - relativeAssets: false, - assetCacheBuster: false - }, - server: { - options: { - debugInfo: true - } - }, - dist: { - options: { - noLineComments: true - } - } - }, - // Make sure code styles are up to par and there are no obvious mistakes jshint: { options: { @@ -187,7 +177,7 @@ module.exports = function (grunt) { // Replace all 'use strict' statements in the code with a single one at the top banner: '(function(window, document, undefined) {\n\'use strict\';\n<%= meta.modules %>\n', footer: '\n})(window, document);\n', - process: function(src, filepath) { + process: function(src, filepath){ return src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); } }, @@ -203,7 +193,7 @@ module.exports = function (grunt) { // Replace all 'use strict' statements in the code with a single one at the top banner: '(function(window, document, undefined) {\n\'use strict\';\n<%= meta.all %>\n<%= meta.tplmodules %>\n', footer: '\n})(window, document);\n', - process: function(src, filepath) { + process: function(src, filepath){ return src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); } }, @@ -296,7 +286,7 @@ module.exports = function (grunt) { module: null, // no bundle module for all the html2js templates base: 'src' }, - src: [ 'src/**/*.tpl.html' ], + src: ['src/**/*.tpl.html'], dest: '<%= yeoman.dist %>/<%= pkg.name %>-tpls.js' } }, @@ -321,23 +311,6 @@ module.exports = function (grunt) { src: ['src/**/*.tpl.html'] } }, - - // Run some tasks in parallel to speed up the build process - concurrent: { - server: [ - 'compass:server', - 'copy:styles' - ] - }, - - copy: { - styles: { - expand: true, - src: '<%= yeoman.src %>/**/*.css', - dest: '.tmp/' - } - }, - processhtml: { dist: { files: { @@ -372,91 +345,96 @@ module.exports = function (grunt) { //findModule: Adds a given module to config var foundModules = {}; - function findModule(name) { - if (foundModules[name]) { return; } + + function findModule(name){ + if(foundModules[name]){ + return; + } foundModules[name] = true; - function enquote(str) { + function enquote(str){ return '"' + str + '"'; } - function removeroot(str) { + function removeroot(str){ return str.slice(str.indexOf('/') + 1, str.length); } var module = { name: name, - moduleName: enquote(grunt.config('moduleprefix')+name), - srcFiles: grunt.file.expand(['src/'+name+'/*.js','!src/'+name+'/*.spec.js']), - tplFiles: grunt.file.expand('src/'+name+'/*.tpl.html'), - tplModules: grunt.file.expand('src/'+name+'/*.tpl.html').map(removeroot).map(enquote), + moduleName: enquote(grunt.config('moduleprefix') + name), + srcFiles: grunt.file.expand(['src/' + name + '/*.js', '!src/' + name + '/*.spec.js']), + tplFiles: grunt.file.expand('src/' + name + '/*.tpl.html'), + tplModules: grunt.file.expand('src/' + name + '/*.tpl.html').map(removeroot).map(enquote), dependencies: dependenciesForModule(name) }; module.dependencies.forEach(findModule); grunt.config('modules', grunt.config('modules').concat(module)); } - function dependenciesForModule(name) { + function dependenciesForModule(name){ var deps = []; - grunt.file.expand(['src/'+name+'/*.js','!src/'+name+'/*.spec.js']) - .map(grunt.file.read) - .forEach(function(contents) { - //Strategy: find where module is declared, - //and from there get everything inside the [] and split them by comma - var moduleDeclIndex = contents.indexOf('angular.module('); - var depArrayStart = contents.indexOf('[', moduleDeclIndex); - var depArrayEnd = contents.indexOf(']', depArrayStart); - var dependencies = contents.substring(depArrayStart + 1, depArrayEnd); - dependencies.split(',').forEach(function(dep) { - if (dep.indexOf(grunt.config('moduleprefix')) > -1) { - var depName = dep.trim().replace( grunt.config('moduleprefix'),'').replace(/['"]/g,''); - if (deps.indexOf(depName) < 0) { - deps.push(depName); - //Get dependencies for this new dependency - deps = deps.concat(dependenciesForModule(depName)); + grunt.file.expand(['src/' + name + '/*.js', '!src/' + name + '/*.spec.js']) + .map(grunt.file.read) + .forEach(function(contents){ + //Strategy: find where module is declared, + //and from there get everything inside the [] and split them by comma + var moduleDeclIndex = contents.indexOf('angular.module('); + var depArrayStart = contents.indexOf('[', moduleDeclIndex); + var depArrayEnd = contents.indexOf(']', depArrayStart); + var dependencies = contents.substring(depArrayStart + 1, depArrayEnd); + dependencies.split(',').forEach(function(dep){ + if(dep.indexOf(grunt.config('moduleprefix')) > -1){ + var depName = dep.trim().replace(grunt.config('moduleprefix'), '').replace(/['"]/g, ''); + if(deps.indexOf(depName) < 0){ + deps.push(depName); + //Get dependencies for this new dependency + deps = deps.concat(dependenciesForModule(depName)); + } } - } + }); }); - }); return deps; } - grunt.registerTask('buildmodules', function() { + grunt.registerTask('buildmodules', function(){ var _ = grunt.util._; //Build all modules grunt.file.expand({ filter: 'isDirectory', cwd: '.' - }, 'src/*').forEach(function(dir) { + }, 'src/*').forEach(function(dir){ findModule(dir.split('/')[1]); }); var modules = grunt.config('modules'); grunt.config('srcModules', _.pluck(modules, 'moduleName')); - grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls) { return tpls.length > 0;} )); + grunt.config('tplModules', _.pluck(modules, 'tplModules').filter(function(tpls){ + return tpls.length > 0; + })); }); - grunt.registerMultiTask('optionaltasks', 'Run task only if source files exists', function() { + grunt.registerMultiTask('optionaltasks', 'Run task only if source files exists', function(){ var options = this.options({ tasks: [] }); var filesExist = false; - this.files.forEach(function(f) { - var src = f.src.filter(function(filepath) { - if (!grunt.file.exists(filepath)) { + this.files.forEach(function(f){ + var src = f.src.filter(function(filepath){ + if(!grunt.file.exists(filepath)){ return false; - } else { + }else{ filesExist = true; return true; } }); }); - if(filesExist) { + if(filesExist){ console.log('true'); - options.tasks.forEach(function(task) { + options.tasks.forEach(function(task){ grunt.task.run(task); }); } @@ -470,7 +448,7 @@ module.exports = function (grunt) { grunt.registerTask('build', [ 'clean:dist', - 'compass:dist', + 'sass:dist', 'autoprefixer', 'buildmodules', 'optionaltasks:css', @@ -482,10 +460,10 @@ module.exports = function (grunt) { 'processhtml' ]); - grunt.registerTask('serve', function (target) { + grunt.registerTask('serve', function(target){ grunt.task.run([ 'clean:server', - 'concurrent:server', + //'concurrent:server', 'autoprefixer', 'connect:livereload', 'watch' diff --git a/bower.json b/bower.json index 57043e0..1aba0bd 100644 --- a/bower.json +++ b/bower.json @@ -31,8 +31,7 @@ "package.json" ], "dependencies": { - "angular": "~1.2.x", - "jquery": "~2.0.3" + "angular": "~1.4.x" }, "devDependencies": { "angular-mocks": "~1.x", diff --git a/demo/index.html b/demo/index.html index 523fde8..55ed278 100644 --- a/demo/index.html +++ b/demo/index.html @@ -18,9 +18,6 @@ - - - @@ -51,7 +48,7 @@ }); - + @@ -299,4 +296,4 @@ - \ No newline at end of file + diff --git a/dist/angular-tour-tpls.js b/dist/angular-tour-tpls.js index 263f590..ae0621f 100644 --- a/dist/angular-tour-tpls.js +++ b/dist/angular-tour-tpls.js @@ -1,11 +1,11 @@ -/** - * An AngularJS directive for showcasing features of your website - * @version v0.1.1 - 2014-03-19 - * @link https://github.com/DaftMonk/angular-tour - * @author Tyler Henkel - * @license MIT License, http://www.opensource.org/licenses/MIT - */ - +/** + * An AngularJS directive for showcasing features of your website + * @version v0.1.1 - 2015-09-14 + * @link https://github.com/DaftMonk/angular-tour + * @author Tyler Henkel + * @license MIT License, http://www.opensource.org/licenses/MIT + */ + (function (window, document, undefined) { 'use strict'; angular.module('angular-tour', [ @@ -16,7 +16,7 @@ angular.module('tour/tour.tpl.html', []).run([ '$templateCache', function ($templateCache) { - $templateCache.put('tour/tour.tpl.html', '
\n' + ' \n' + '
\n' + '

\n' + ' \n' + ' \xd7\n' + '
\n' + '
'); + $templateCache.put('tour/tour.tpl.html', '
\n' + ' \n' + '
\n' + '

\n' + ' \n' + ' ×\n' + '
\n' + '
'); } ]); angular.module('angular-tour.tour', []).constant('tourConfig', { @@ -30,9 +30,11 @@ 'orderedList', function ($scope, orderedList) { var self = this, steps = self.steps = orderedList(); + // we'll pass these in from the directive self.postTourCallback = angular.noop; self.postStepCallback = angular.noop; self.currentStep = 0; + // if currentStep changes, select the new step $scope.$watch(function () { return self.currentStep; }, function (val) { @@ -46,6 +48,7 @@ if (step) { step.ttOpen = true; } + // update currentStep if we manually selected this index if (self.currentStep !== nextIndex) { self.currentStep = nextIndex; } @@ -71,6 +74,7 @@ self.postTourCallback(); }; $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); }; @@ -90,6 +94,7 @@ throw 'The directive requires a `step` attribute to bind the current step to.'; } var model = $parse(attrs.step); + // Watch current step view model and update locally scope.$watch(attrs.step, function (newVal) { ctrl.currentStep = newVal; }); @@ -103,6 +108,7 @@ scope.$parent.$eval(attrs.postStep); } }; + // 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; @@ -145,6 +151,7 @@ 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) { @@ -159,52 +166,60 @@ if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.fadeIn(); - else { - tourtip.css({ display: 'block' }); - } + /*if(scope.ttAnimation) + tourtip.fadeIn(); + else { + tourtip.css({ display: 'block' }); + }*/ + tourtip.css({ display: 'block' }); + tourtip.removeClass('ng-hide'); + // Append it to the dom element.after(tourtip); + // Try to set target to the first child of our tour directive if (element.children().eq(0).length > 0) { targetElement = element.children().eq(0); } else { targetElement = element; } var updatePosition = function () { - position = targetElement.position(); - ttWidth = tourtip.width(); - ttHeight = tourtip.height(); - width = targetElement.width(); - height = targetElement.height(); + // Get the position of the directive element + position = targetElement[0]; + ttWidth = tourtip[0].offsetWidth; + ttHeight = tourtip[0].offsetHeight; + width = targetElement[0].offsetWidth; + height = targetElement[0].offsetHeight; + // Calculate the tourtip's top and left coordinates to center it switch (scope.ttPlacement) { case 'right': ttPosition = { - top: position.top, - left: position.left + width + scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft + width + scope.ttOffset }; break; case 'bottom': ttPosition = { - top: position.top + height + scope.ttOffset, - left: position.left + top: position.offsetTop + height + scope.ttOffset, + left: position.offsetLeft }; break; case 'left': ttPosition = { - top: position.top, - left: position.left - ttWidth - scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft - ttWidth - scope.ttOffset }; break; default: ttPosition = { - top: position.top - ttHeight - scope.ttOffset, - left: position.left + top: position.offsetTop - ttHeight - scope.ttOffset, + left: position.offsetLeft }; break; } ttPosition.top += 'px'; ttPosition.left += 'px'; + // Now set the calculated positioning. tourtip.css(ttPosition); + // Scroll to the tour tip scrollTo(tourtip, -200, -300, tourConfig.scrollSpeed); }; angular.element($window).bind('resize.' + scope.$id, function () { @@ -213,9 +228,10 @@ updatePosition(); } function hide() { - tourtip.detach(); + tourtip.addClass('ng-hide'); angular.element($window).unbind('resize.' + scope.$id); } + // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTourtip() { angular.element($window).unbind('resize.' + scope.$id); tourtip.remove(); @@ -304,19 +320,19 @@ return new OrderedList(); }; return orderedListFactory; - }).factory('scrollTo', function () { - return function (target, offsetY, offsetX, speed) { - if (target) { - offsetY = offsetY || -100; - offsetX = offsetX || -100; - speed = speed || 500; - $('html,body').stop().animate({ - scrollTop: target.offset().top + offsetY, - scrollLeft: target.offset().left + offsetX - }, speed); - } else { - $('html,body').stop().animate({ scrollTop: 0 }, speed); - } - }; - }); + }).factory('scrollTo', [ + '$window', + function ($window) { + return function (target, offsetY, offsetX, speed) { + if (target) { + offsetY = offsetY || -100; + offsetX = offsetX || -100; + speed = speed || 500; + $window.scrollTo(target[0].offsetLeft + offsetX, target[0].offsetTop + offsetY); + } else { + $window.scrollTo(0, 0); + } + }; + } + ]); }(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 8e701a5..b4093bf 100644 --- a/dist/angular-tour-tpls.min.js +++ b/dist/angular-tour-tpls.min.js @@ -1 +1 @@ -!function(){"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
')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,offset:28}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b();c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.currentStep=0,a.$watch(function(){return c.currentStep},function(a){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),a>=d.getCount()&&c.postTourCallback(),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()},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",function(a){return{controller:"TourController",restrict:"EA",scope:!0,link:function(b,c,d,e){if(!angular.isDefined(d.step))throw"The directive requires a `step` attribute to bind the current step to.";var f=a(d.step);b.$watch(d.step,function(a){e.currentStep=a}),e.postTourCallback=function(){angular.isDefined(d.postTour)&&b.$parent.$eval(d.postTour)},e.postStepCallback=function(){angular.isDefined(d.postStep)&&b.$parent.$eval(d.postStep)},b.setCurrentStep=function(a){f.assign(b.$parent,a),e.currentStep=a},b.getCurrentStep=function(){return e.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig",function(a,b,c,d,e,f){var g=(c.startSymbol(),c.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(c,h,i,j){function k(){var b,d,g,i,j,k,l;if(c.ttContent){c.ttAnimation?m.fadeIn():m.css({display:"block"}),h.after(m),l=h.children().eq(0).length>0?h.children().eq(0):h;var n=function(){switch(b=l.position(),d=m.width(),g=m.height(),k=l.width(),j=l.height(),c.ttPlacement){case"right":i={top:b.top,left:b.left+k+c.ttOffset};break;case"bottom":i={top:b.top+j+c.ttOffset,left:b.left};break;case"left":i={top:b.top,left:b.left-d-c.ttOffset};break;default:i={top:b.top-g-c.ttOffset,left:b.left}}i.top+="px",i.left+="px",m.css(i),e(m,-200,-300,f.scrollSpeed)};angular.element(a).bind("resize."+c.$id,function(){n()}),n()}}function l(){m.detach(),angular.element(a).unbind("resize."+c.$id)}i.$observe("tourtip",function(a){c.ttContent=a}),i.$observe("tourtipPlacement",function(a){c.ttPlacement=a||f.placement}),i.$observe("tourtipNextLabel",function(a){c.ttNextLabel=a||f.nextLabel}),i.$observe("tourtipOffset",function(a){c.ttOffset=parseInt(a,10)||f.offset}),c.ttOpen=!1,c.ttAnimation=f.animation,c.index=parseInt(i.tourtipStep,10);var m=b(g)(c);j.addStep(c),d(function(){c.$watch("ttOpen",function(a){a?k():l()})},500),c.$on("$destroy",function(){angular.element(a).unbind("resize."+c.$id),m.remove(),m=null})}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;d\n \n
\n

\n \n ×\n
\n')}]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,offset:28}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b();c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.currentStep=0,a.$watch(function(){return c.currentStep},function(a){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),a>=d.getCount()&&c.postTourCallback(),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()},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",function(a){return{controller:"TourController",restrict:"EA",scope:!0,link:function(b,c,d,e){if(!angular.isDefined(d.step))throw"The directive requires a `step` attribute to bind the current step to.";var f=a(d.step);b.$watch(d.step,function(a){e.currentStep=a}),e.postTourCallback=function(){angular.isDefined(d.postTour)&&b.$parent.$eval(d.postTour)},e.postStepCallback=function(){angular.isDefined(d.postStep)&&b.$parent.$eval(d.postStep)},b.setCurrentStep=function(a){f.assign(b.$parent,a),e.currentStep=a},b.getCurrentStep=function(){return e.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig",function(a,b,c,d,e,f){var g=(c.startSymbol(),c.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(c,h,i,j){function k(){var b,d,g,i,j,k,l;if(c.ttContent){m.css({display:"block"}),m.removeClass("ng-hide"),h.after(m),l=h.children().eq(0).length>0?h.children().eq(0):h;var n=function(){switch(b=l[0],d=m[0].offsetWidth,g=m[0].offsetHeight,k=l[0].offsetWidth,j=l[0].offsetHeight,c.ttPlacement){case"right":i={top:b.offsetTop,left:b.offsetLeft+k+c.ttOffset};break;case"bottom":i={top:b.offsetTop+j+c.ttOffset,left:b.offsetLeft};break;case"left":i={top:b.offsetTop,left:b.offsetLeft-d-c.ttOffset};break;default:i={top:b.offsetTop-g-c.ttOffset,left:b.offsetLeft}}i.top+="px",i.left+="px",m.css(i),e(m,-200,-300,f.scrollSpeed)};angular.element(a).bind("resize."+c.$id,function(){n()}),n()}}function l(){m.addClass("ng-hide"),angular.element(a).unbind("resize."+c.$id)}i.$observe("tourtip",function(a){c.ttContent=a}),i.$observe("tourtipPlacement",function(a){c.ttPlacement=a||f.placement}),i.$observe("tourtipNextLabel",function(a){c.ttNextLabel=a||f.nextLabel}),i.$observe("tourtipOffset",function(a){c.ttOffset=parseInt(a,10)||f.offset}),c.ttOpen=!1,c.ttAnimation=f.animation,c.index=parseInt(i.tourtipStep,10);var m=b(g)(c);j.addStep(c),d(function(){c.$watch("ttOpen",function(a){a?k():l()})},500),c.$on("$destroy",function(){angular.element(a).unbind("resize."+c.$id),m.remove(),m=null})}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;d= steps.getCount() || self.currentStep < 0 ? 0 : self.currentStep; self.select(startStep); }; @@ -80,6 +84,7 @@ throw 'The directive requires a `step` attribute to bind the current step to.'; } var model = $parse(attrs.step); + // Watch current step view model and update locally scope.$watch(attrs.step, function (newVal) { ctrl.currentStep = newVal; }); @@ -93,6 +98,7 @@ scope.$parent.$eval(attrs.postStep); } }; + // 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; @@ -135,6 +141,7 @@ 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) { @@ -149,52 +156,60 @@ if (!scope.ttContent) { return; } - if (scope.ttAnimation) - tourtip.fadeIn(); - else { - tourtip.css({ display: 'block' }); - } + /*if(scope.ttAnimation) + tourtip.fadeIn(); + else { + tourtip.css({ display: 'block' }); + }*/ + tourtip.css({ display: 'block' }); + tourtip.removeClass('ng-hide'); + // Append it to the dom element.after(tourtip); + // Try to set target to the first child of our tour directive if (element.children().eq(0).length > 0) { targetElement = element.children().eq(0); } else { targetElement = element; } var updatePosition = function () { - position = targetElement.position(); - ttWidth = tourtip.width(); - ttHeight = tourtip.height(); - width = targetElement.width(); - height = targetElement.height(); + // Get the position of the directive element + position = targetElement[0]; + ttWidth = tourtip[0].offsetWidth; + ttHeight = tourtip[0].offsetHeight; + width = targetElement[0].offsetWidth; + height = targetElement[0].offsetHeight; + // Calculate the tourtip's top and left coordinates to center it switch (scope.ttPlacement) { case 'right': ttPosition = { - top: position.top, - left: position.left + width + scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft + width + scope.ttOffset }; break; case 'bottom': ttPosition = { - top: position.top + height + scope.ttOffset, - left: position.left + top: position.offsetTop + height + scope.ttOffset, + left: position.offsetLeft }; break; case 'left': ttPosition = { - top: position.top, - left: position.left - ttWidth - scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft - ttWidth - scope.ttOffset }; break; default: ttPosition = { - top: position.top - ttHeight - scope.ttOffset, - left: position.left + top: position.offsetTop - ttHeight - scope.ttOffset, + left: position.offsetLeft }; break; } ttPosition.top += 'px'; ttPosition.left += 'px'; + // Now set the calculated positioning. tourtip.css(ttPosition); + // Scroll to the tour tip scrollTo(tourtip, -200, -300, tourConfig.scrollSpeed); }; angular.element($window).bind('resize.' + scope.$id, function () { @@ -203,9 +218,10 @@ updatePosition(); } function hide() { - tourtip.detach(); + tourtip.addClass('ng-hide'); angular.element($window).unbind('resize.' + scope.$id); } + // Make sure tooltip is destroyed and removed. scope.$on('$destroy', function onDestroyTourtip() { angular.element($window).unbind('resize.' + scope.$id); tourtip.remove(); @@ -294,19 +310,19 @@ return new OrderedList(); }; return orderedListFactory; - }).factory('scrollTo', function () { - return function (target, offsetY, offsetX, speed) { - if (target) { - offsetY = offsetY || -100; - offsetX = offsetX || -100; - speed = speed || 500; - $('html,body').stop().animate({ - scrollTop: target.offset().top + offsetY, - scrollLeft: target.offset().left + offsetX - }, speed); - } else { - $('html,body').stop().animate({ scrollTop: 0 }, speed); - } - }; - }); + }).factory('scrollTo', [ + '$window', + function ($window) { + return function (target, offsetY, offsetX, speed) { + if (target) { + offsetY = offsetY || -100; + offsetX = offsetX || -100; + speed = speed || 500; + $window.scrollTo(target[0].offsetLeft + offsetX, target[0].offsetTop + offsetY); + } else { + $window.scrollTo(0, 0); + } + }; + } + ]); }(window, document)); \ No newline at end of file diff --git a/dist/angular-tour.min.js b/dist/angular-tour.min.js index d331211..c42d84f 100644 --- a/dist/angular-tour.min.js +++ b/dist/angular-tour.min.js @@ -1 +1 @@ -!function(){"use strict";angular.module("angular-tour",["angular-tour.tour"]),angular.module("angular-tour.tour",[]).constant("tourConfig",{placement:"top",animation:!0,nextLabel:"Next",scrollSpeed:500,offset:28}).controller("TourController",["$scope","orderedList",function(a,b){var c=this,d=c.steps=b();c.postTourCallback=angular.noop,c.postStepCallback=angular.noop,c.currentStep=0,a.$watch(function(){return c.currentStep},function(a){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),a>=d.getCount()&&c.postTourCallback(),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()},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",function(a){return{controller:"TourController",restrict:"EA",scope:!0,link:function(b,c,d,e){if(!angular.isDefined(d.step))throw"The directive requires a `step` attribute to bind the current step to.";var f=a(d.step);b.$watch(d.step,function(a){e.currentStep=a}),e.postTourCallback=function(){angular.isDefined(d.postTour)&&b.$parent.$eval(d.postTour)},e.postStepCallback=function(){angular.isDefined(d.postStep)&&b.$parent.$eval(d.postStep)},b.setCurrentStep=function(a){f.assign(b.$parent,a),e.currentStep=a},b.getCurrentStep=function(){return e.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig",function(a,b,c,d,e,f){var g=(c.startSymbol(),c.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(c,h,i,j){function k(){var b,d,g,i,j,k,l;if(c.ttContent){c.ttAnimation?m.fadeIn():m.css({display:"block"}),h.after(m),l=h.children().eq(0).length>0?h.children().eq(0):h;var n=function(){switch(b=l.position(),d=m.width(),g=m.height(),k=l.width(),j=l.height(),c.ttPlacement){case"right":i={top:b.top,left:b.left+k+c.ttOffset};break;case"bottom":i={top:b.top+j+c.ttOffset,left:b.left};break;case"left":i={top:b.top,left:b.left-d-c.ttOffset};break;default:i={top:b.top-g-c.ttOffset,left:b.left}}i.top+="px",i.left+="px",m.css(i),e(m,-200,-300,f.scrollSpeed)};angular.element(a).bind("resize."+c.$id,function(){n()}),n()}}function l(){m.detach(),angular.element(a).unbind("resize."+c.$id)}i.$observe("tourtip",function(a){c.ttContent=a}),i.$observe("tourtipPlacement",function(a){c.ttPlacement=a||f.placement}),i.$observe("tourtipNextLabel",function(a){c.ttNextLabel=a||f.nextLabel}),i.$observe("tourtipOffset",function(a){c.ttOffset=parseInt(a,10)||f.offset}),c.ttOpen=!1,c.ttAnimation=f.animation,c.index=parseInt(i.tourtipStep,10);var m=b(g)(c);j.addStep(c),d(function(){c.$watch("ttOpen",function(a){a?k():l()})},500),c.$on("$destroy",function(){angular.element(a).unbind("resize."+c.$id),m.remove(),m=null})}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;d=d.getCount()&&c.postTourCallback(),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()},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",function(a){return{controller:"TourController",restrict:"EA",scope:!0,link:function(b,c,d,e){if(!angular.isDefined(d.step))throw"The directive requires a `step` attribute to bind the current step to.";var f=a(d.step);b.$watch(d.step,function(a){e.currentStep=a}),e.postTourCallback=function(){angular.isDefined(d.postTour)&&b.$parent.$eval(d.postTour)},e.postStepCallback=function(){angular.isDefined(d.postStep)&&b.$parent.$eval(d.postStep)},b.setCurrentStep=function(a){f.assign(b.$parent,a),e.currentStep=a},b.getCurrentStep=function(){return e.currentStep}}}}]).directive("tourtip",["$window","$compile","$interpolate","$timeout","scrollTo","tourConfig",function(a,b,c,d,e,f){var g=(c.startSymbol(),c.endSymbol(),"
");return{require:"^tour",restrict:"EA",scope:!0,link:function(c,h,i,j){function k(){var b,d,g,i,j,k,l;if(c.ttContent){m.css({display:"block"}),m.removeClass("ng-hide"),h.after(m),l=h.children().eq(0).length>0?h.children().eq(0):h;var n=function(){switch(b=l[0],d=m[0].offsetWidth,g=m[0].offsetHeight,k=l[0].offsetWidth,j=l[0].offsetHeight,c.ttPlacement){case"right":i={top:b.offsetTop,left:b.offsetLeft+k+c.ttOffset};break;case"bottom":i={top:b.offsetTop+j+c.ttOffset,left:b.offsetLeft};break;case"left":i={top:b.offsetTop,left:b.offsetLeft-d-c.ttOffset};break;default:i={top:b.offsetTop-g-c.ttOffset,left:b.offsetLeft}}i.top+="px",i.left+="px",m.css(i),e(m,-200,-300,f.scrollSpeed)};angular.element(a).bind("resize."+c.$id,function(){n()}),n()}}function l(){m.addClass("ng-hide"),angular.element(a).unbind("resize."+c.$id)}i.$observe("tourtip",function(a){c.ttContent=a}),i.$observe("tourtipPlacement",function(a){c.ttPlacement=a||f.placement}),i.$observe("tourtipNextLabel",function(a){c.ttNextLabel=a||f.nextLabel}),i.$observe("tourtipOffset",function(a){c.ttOffset=parseInt(a,10)||f.offset}),c.ttOpen=!1,c.ttAnimation=f.animation,c.index=parseInt(i.tourtipStep,10);var m=b(g)(c);j.addStep(c),d(function(){c.$watch("ttOpen",function(a){a?k():l()})},500),c.$on("$destroy",function(){angular.element(a).unbind("resize."+c.$id),m.remove(),m=null})}}}]).directive("tourPopup",function(){return{replace:!0,templateUrl:"tour/tour.tpl.html",scope:!0,restrict:"EA",link:function(a,b,c){}}}).factory("orderedList",function(){var a=function(){this.map={},this._array=[]};a.prototype.set=function(a,b){if(angular.isNumber(a))if(a in this.map)this.map[a]=b;else{if(a0?a-1:0;this._array.splice(c,0,a)}else this._array.push(a);this.map[a]=b,this._array.sort(function(a,b){return a-b})}},a.prototype.indexOf=function(a){for(var b in this.map)if(this.map.hasOwnProperty(b)&&this.map[b]===a)return Number(b)},a.prototype.push=function(a){var b=this._array[this._array.length-1]+1||0;this._array.push(b),this.map[b]=a,this._array.sort(function(a,b){return a-b})},a.prototype.remove=function(a){var b=this._array.indexOf(a);if(-1===b)throw new Error("key does not exist");this._array.splice(b,1),delete this.map[a]},a.prototype.get=function(a){return this.map[a]},a.prototype.getCount=function(){return this._array.length},a.prototype.forEach=function(a){for(var b,c,d=0;d=0.8.0" + "node": ">=0.10.0" }, "scripts": { "test": "grunt test" diff --git a/src/tour/tour.js b/src/tour/tour.js index d2a19c6..0e1ec39 100644 --- a/src/tour/tour.js +++ b/src/tour/tour.js @@ -191,11 +191,13 @@ angular.module('angular-tour.tour', []) return; } - if(scope.ttAnimation) + /*if(scope.ttAnimation) tourtip.fadeIn(); else { tourtip.css({ display: 'block' }); - } + }*/ + tourtip.css({ display: 'block' }); + tourtip.removeClass('ng-hide'); // Append it to the dom element.after( tourtip ); @@ -209,38 +211,38 @@ angular.module('angular-tour.tour', []) var updatePosition = function() { // Get the position of the directive element - position = targetElement.position(); + position = targetElement[0]; - ttWidth = tourtip.width(); - ttHeight = tourtip.height(); + ttWidth = tourtip[0].offsetWidth; + ttHeight = tourtip[0].offsetHeight; - width = targetElement.width(); - height = targetElement.height(); + width = targetElement[0].offsetWidth; + height = targetElement[0].offsetHeight; // Calculate the tourtip's top and left coordinates to center it switch ( scope.ttPlacement ) { case 'right': ttPosition = { - top: position.top, - left: position.left + width + scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft + width + scope.ttOffset }; break; case 'bottom': ttPosition = { - top: position.top + height + scope.ttOffset, - left: position.left + top: position.offsetTop + height + scope.ttOffset, + left: position.offsetLeft }; break; case 'left': ttPosition = { - top: position.top, - left: position.left - ttWidth - scope.ttOffset + top: position.offsetTop, + left: position.offsetLeft - ttWidth - scope.ttOffset }; break; default: ttPosition = { - top: position.top - ttHeight - scope.ttOffset, - left: position.left + top: position.offsetTop - ttHeight - scope.ttOffset, + left: position.offsetLeft }; break; } @@ -262,7 +264,7 @@ angular.module('angular-tour.tour', []) } function hide() { - tourtip.detach(); + tourtip.addClass('ng-hide'); angular.element($window).unbind('resize.' + scope.$id); } @@ -300,7 +302,7 @@ angular.module('angular-tour.tour', []) this.map = {}; this._array = []; }; - + OrderedList.prototype.set = function (key, value) { if (!angular.isNumber(key)) return; @@ -367,7 +369,7 @@ angular.module('angular-tour.tour', []) var orderedListFactory = function() { return new OrderedList(); }; - + return orderedListFactory; }) @@ -375,15 +377,69 @@ angular.module('angular-tour.tour', []) * ScrollTo * Smoothly scroll to a dom element */ - .factory('scrollTo', function() { + .factory('scrollTo', function($window, $timeout, tourConfig) { + + var smooth_scroll = function(targetX, targetY, duration) { + + targetX = Math.round(targetX); + targetY = Math.round(targetY); + duration = Math.round(duration); + + if (duration < 0) { + return Promise.reject("bad duration"); + } + if (duration === 0) { + window.scrollTo(targetX, targetY); + return Promise.resolve(); + } + + var start_time = Date.now(), + end_time = start_time + duration, + startLeft = window.scrollX, + startTop = window.scrollY, + distanceX = targetX - startLeft, + distanceY = targetY - startTop; + + // based on http://en.wikipedia.org/wiki/Smoothstep + var smooth_step = function(start, end, point) { + if (point <= start) { + return 0; + } + if (point >= end) { + return 1; + } + var x = (point - start) / (end - start); // interpolation + return x * x * (3 - 2 * x); + } + + return new Promise(function(resolve, reject) { + var scroll_frame = function() { + var now = Date.now(), + point = smooth_step(start_time, end_time, now), + frameLeft = Math.round(startLeft + (distanceX * point)), + frameTop = Math.round(startTop + (distanceY * point)); + window.scrollTo(frameLeft, frameTop) + // check if we're done! + if (now >= end_time) { + resolve(); + return; + } + // schedule next frame for execution + $timeout(scroll_frame, 0); + } + // boostrap the animation process + $timeout(scroll_frame, 0); + }); + } + return function(target, offsetY, offsetX, speed) { if(target) { offsetY = offsetY || -100; offsetX = offsetX || -100; speed = speed || 500; - $('html,body').stop().animate({scrollTop: target.offset().top + offsetY, scrollLeft: target.offset().left + offsetX}, speed); + smooth_scroll(target[0].offsetLeft + offsetX, target[0].offsetTop + offsetY, speed); } else { - $('html,body').stop().animate({scrollTop: 0}, speed); + smooth_scroll(0, 0, speed); } }; - }); \ No newline at end of file + }); diff --git a/src/tour/tour.scss b/src/tour/tour.scss index 6fb1aad..807161f 100644 --- a/src/tour/tour.scss +++ b/src/tour/tour.scss @@ -10,6 +10,10 @@ font-weight: 400; max-width: 400px; border-radius: 10px; + -webkit-animation: fadeIn 0.3s both ease-in; + -moz-animation: fadeIn 0.3s both ease-in; + animation: fadeIn 0.3s both ease-in; + p { color: #CBD0D4; font-size: .9em; @@ -95,4 +99,32 @@ color: #eee!important; } } + + @-webkit-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + @-moz-keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } }