AngularJS自定义插件实现网站用户引导功能示例  更新时间:2016年11月07日 09:02:45   作者:破狼   这篇文章主要介绍了AngularJS自定义插件实现网站用户引导功能,结合实例形式分析了AngularJS自定义插件的实现步骤与相关功能技巧,需要的朋友可以参考下 本文实例讲述了AngularJS自定义插件实现网站用户引导功能。分享给大家供大家参考,具体如下: 最近由于项目进行了较大的改版,为了让用户能够适应这次新的改版,因此在系统中引入了“用户引导”功能,对于初次进入系统的用户一些简单的使用培训training。对于大多数网站来说,这是一个很常见的功能。所以在开发这个任务之前,博主尝试将其抽象化,独立于现有系统的业务逻辑,将其封装为一个通用的插件,使得代码更容易扩展和维护。 无图无真相,先上图: 关于这款trainning插件的使用很简单,它采用了类似Angular路由一样的配置,只需要简单的配置其每一步training信息。 title:step的标题信息; template/templateUrl: step的内容模板信息。这类可以配置html元素,或者是模板的url地址,同时templateUrl也支持Angular route一样的function语法; controller: step的控制器配置;在controller中可注入如下参数:当前step – currentStep、所有step的配置 – trainnings、当前step的配置 – currentTrainning、以及下一步的操作回调 – trainningInstance(其中nextStep:为下一步的回调,cancel为取消用户引导回调); controllerAs: controller的别名; resolve:在controller初始化前的数据配置,同Angular路由中的resolve; locals:本地变量,和resolve相似,可以传递到controller中。区别之处在于它不支持function调用,对于常量书写会比resolve更方便; placement: step容器上三角箭头的显示方位, position: step容器的具体显示位置,这是一个绝对坐标;可以传递{left: 100, top: 100}的绝对坐标,也可以是#stepPanelHost配置相对于此元素的placement位置。同时它也支持自定义function和注入Angular的其他组件语法。并且默认可注入:所有step配置 – trainnings,当前步骤 – step,当前step的配置 – currentTrainning,以及step容器节点 – stepPanel; backdrop:是否需要显示遮罩层,默认显示,除非显示声明为false配置,则不会显示遮罩层; stepClass:每一个step容器的样式信息; backdropClass: 每一个遮罩层的样式信息。 了解了这些配置后,并根据特定需求定制化整个用户引导的配置信息后,我们就可以使用trainningService的trainning方法来在特定实际启动用户引导,传入参数为每一步step的配置信息。并可以注册其done或者cancel事件: trainningService.trainning(trainningCourses.courses) .done(function() { vm.isDone = true; }); 下面是一个演示的配置信息: .constant(‘trainningCourses’, { courses: [{ title: ‘Step 1:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘left’, position: ‘#blogControl’ },{ title: ‘Step 3:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘top’, position: { top: 200, left: 100 } }, … { stepClass: ‘last-step’, backdropClass: ‘last-backdrop’, templateUrl: ‘trainning-content-done.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, position: [‘$window’, ‘stepPanel’, function($window, stepPanel) { // 自定义函数,使其屏幕居中 var win = angular.element($window); return { top: (win.height() – stepPanel.height()) / 2, left: (win.width() – stepPanel.width()) / 2 } }] }] }) 本文插件源码和演示效果唯一codepen上,效果图如下: 在trainning插件的源码设计中,包含如下几个要点: 提供service api。因为关于trainning这个插件,它是一个全局的插件,正好在Angular中所有的service也是单例的,所以将用户引导逻辑封装到Angular的service中是一个不错的设计。但对于trainning的每一步展示内容信息则是DOM操作,在Angular的处理中它不该存在于service中,最佳的方式是应该把他封装到Directive中。所以这里采用Directive的定义,并在service中compile,然后append到body中。 对于每一个这类独立的插件应该封装一个独立的scope,这样便于在后续的销毁,以及不会与现有的scope变量的冲突。 $q对延时触发的结果包装。对于像该trainning插件或者modal这类操作结果采用promise的封装,是一个不错的选择。它取代了回调参数的复杂性,并以流畅API的方式展现,更利于代码的可读性。同时也能与其他Angular service统一返回API。 对于controller、controllerAs、resolve、template、templateUrl这类类似路由的处理代码,完全可以移到到你的同类插件中去。它们可以增加插件的更多定制化扩展。关于这部分代码的解释,博主将会在后续文章中为大家推送。 利用$injector.invoke动态注入和调用Angular service,这样既能获取Angular其他service注入的扩展性,也能获取到函数的动态性。如上例中的屏幕居中的自定义扩展方式。 这类设计要点,同样可以运用到想modal、alert、overload这类全局插件中。有兴趣的读者,你可以在博主的codepen笔记中阅读这段代码http://codepen.io/greengerong/pen/pjwXQW#0。 上述代码摘录如下: HTML: All trainning setps done! You can start trainning again Blog : Public blog CSS: .last-step{ /* background-color: blue;*/ } .last-backdrop{ background-color: #FFFFFF; } .blog{ position: absolute; left: 300px; top: 100px; } .start-again{ position: absolute; left: 400px; top: 250px; } .next-step { .step-progressing { margin: 10px 0px; display: inline-block; li { margin-right: 5px; border: 1px solid #fff; background-color: #6E6E6E; width: 12px; height: 12px; border-radius: 50%; display: inline-block; &.active { background-color: #0000FF; } } } } JS: //Please set step content to fixed width when complex content or dynamic loading. angular.module(‘com.github.greengerong.backdrop’, []) .directive(‘uiBackdrop’, [‘$document’, function($document) { return { restrict: ‘EA’, replace: true, templateUrl: ‘modal-backdrop.html’, scope: { backdropClass: ‘=’, zIndex: ‘=’ } /* ,link: function(){ $document.bind(‘keydown’, function(evt){ evt.preventDefault(); evt.stopPropagation(); }); scope.$on(‘$destroy’, function(){ $document.unbind(‘keydown’); }); }*/ }; }]) .service(‘modalBackdropService’, [‘$rootScope’, ‘$compile’, ‘$document’, function($rootScope, $compile, $document) { var self = this; self.backdrop = function(backdropClass, zIndex) { var $backdrop = angular.element(”) .attr({ ‘backdrop-class’: ‘backdropClass’, ‘z-index’: ‘zIndex’ }); var backdropScope = $rootScope.$new(true); backdropScope.backdropClass = backdropClass; backdropScope.zIndex = zIndex; $document.find(‘body’).append($compile($backdrop)(backdropScope)); return function() { $backdrop.remove(); backdropScope.$destroy(); }; }; }]); angular.module(‘com.github.greengerong.trainning’, [‘com.github.greengerong.backdrop’, ‘ui.bootstrap’]) .directive(‘trainningStep’, [‘$timeout’, ‘$http’, ‘$templateCache’, ‘$compile’, ‘$position’, ‘$injector’, ‘$window’, ‘$q’, ‘$controller’, function($timeout, $http, $templateCache, $compile, $position, $injector, $window, $q, $controller) { return { restrict: ‘EA’, replace: true, templateUrl: ‘trainning-step.html’, scope: { step: ‘=’, trainnings: ‘=’, nextStep: ‘&’, cancel: ‘&’ }, link: function(stepPanelScope, elm) { var stepPanel = elm.find(‘.step-panel’); stepPanelScope.$watch(‘step’, function(step) { if (!step) { return; } stepPanelScope.currentTrainning = stepPanelScope.trainnings[stepPanelScope.step – 1]; var contentScope = stepPanelScope.$new(false); loadStepContent(contentScope, { ‘currentStep’: stepPanelScope.step, ‘trainnings’: stepPanelScope.trainnings, ‘currentTrainning’: stepPanelScope.currentTrainning, ‘trainningInstance’: { ‘nextStep’: stepPanelScope.nextStep, ‘cancel’: stepPanelScope.cancel } }).then(function(tplAndVars) { elm.find(‘.popover-content’).html($compile(tplAndVars[0])(contentScope)); }).then(function() { var pos = stepPanelScope.currentTrainning.position; adjustPosition(stepPanelScope, stepPanel, pos); }); }); angular.element($window).bind(‘resize’, function() { adjustPosition(stepPanelScope, stepPanel, stepPanelScope.currentTrainning.position); }); stepPanelScope.$on(‘$destroy’, function() { angular.element($window).unbind(‘resize’); }); function getPositionOnElement(stepScope, setpPos) { return $position.positionElements(angular.element(setpPos), stepPanel, stepScope.currentTrainning.placement, true); } function positionOnElement(stepScope, setpPos) { var targetPos = angular.isString(setpPos) ? getPositionOnElement(stepScope, setpPos) : setpPos; var positionStyle = stepScope.currentTrainning || {}; positionStyle.top = targetPos.top + ‘px’; positionStyle.left = targetPos.left + ‘px’; stepScope.positionStyle = positionStyle; } function adjustPosition(stepScope, stepPanel, pos) { if (!pos) { return; } var setpPos = angular.isFunction(pos) || angular.isArray(pos) ? $injector.invoke(pos, null, { trainnings: stepScope.trainnings, step: stepScope.setp, currentTrainning: stepScope.currentTrainning, stepPanel: stepPanel }) : pos; //get postion should wait for content setup $timeout(function() { positionOnElement(stepScope, setpPos); }); } function loadStepContent(contentScope, ctrlLocals) { var trainningOptions = contentScope.currentTrainning, getTemplatePromise = function(options) { return options.template ? $q.when(options.template) : $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, { cache: $templateCache }).then(function(result) { return result.data; }); }, getResolvePromises = function(resolves) { var promisesArr = []; angular.forEach(resolves, function(value) { if (angular.isFunction(value) || angular.isArray(value)) { promisesArr.push($q.when($injector.invoke(value))); } }); return promisesArr; }, controllerLoader = function(trainningOptions, trainningScope, ctrlLocals, tplAndVars) { var ctrlInstance; ctrlLocals = angular.extend({}, ctrlLocals || {}, trainningOptions.locals || {}); var resolveIter = 1; if (trainningOptions.controller) { ctrlLocals.$scope = trainningScope; angular.forEach(trainningOptions.resolve, function(value, key) { ctrlLocals[key] = tplAndVars[resolveIter++]; }); ctrlInstance = $controller(trainningOptions.controller, ctrlLocals); if (trainningOptions.controllerAs) { trainningScope[trainningOptions.controllerAs] = ctrlInstance; } } return trainningScope; }; var templateAndResolvePromise = $q.all([getTemplatePromise(trainningOptions)].concat(getResolvePromises(trainningOptions.resolve || {}))); return templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { controllerLoader(trainningOptions, contentScope, ctrlLocals, tplAndVars); return tplAndVars; }); } } }; }]) .service(‘trainningService’, [‘$compile’, ‘$rootScope’, ‘$document’, ‘$q’, function($compile, $rootScope, $document, $q) { var self = this; self.trainning = function(trainnings) { var trainningScope = $rootScope.$new(true), defer = $q.defer(), $stepElm = angular.element(”) .attr({ ‘step’: ‘step’, ‘trainnings’: ‘trainnings’, ‘next-step’: ‘nextStep($event, step);’, ‘cancel’: ‘cancel($event, step)’ }), destroyTrainningPanel = function(){ if (trainningScope) { $stepElm.remove(); trainningScope.$destroy(); } }; trainningScope.cancel = function($event, step){ defer.reject(‘cancel’); }; trainningScope.nextStep = function($event, step) { if (trainningScope.step === trainnings.length) { destroyTrainningPanel(); return defer.resolve(‘done’); } trainningScope.step++; }; trainningScope.trainnings = trainnings; trainningScope.step = 1; $document.find(‘body’).append($compile($stepElm)(trainningScope)); trainningScope.$on(‘$locationChangeStart’, destroyTrainningPanel); return { done: function(func) { defer.promise.then(func); return this; }, cancel: function(func) { defer.promise.then(null, func); return this; } }; }; }]); angular.module(‘com.github.greengerong’, [‘com.github.greengerong.trainning’]) .filter(‘range’, [function () { return function (len) { return _.range(1, len + 1); }; }]) .controller(‘StepPanelController’, [‘currentStep’, ‘trainnings’, ‘trainningInstance’, ‘trainnings’, function(currentStep, trainnings, trainningInstance, trainnings) { var vm = this; vm.currentStep = currentStep; vm.trainningInstance = trainningInstance; vm.trainnings = trainnings; vm.texts = [‘Write your own sort blog.’, ‘Click button to public your blog.’, ‘View your blog info on there.’, ‘Click this button, you can restart this trainning when .’, ‘All trainnings done!’]; return vm; }]) .constant(‘trainningCourses’, { courses: [{ title: ‘Step 1:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘left’, position: ‘#blogControl’ }, { title: ‘Step 2:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘right’, backdrop: false, position: ‘#submitBlog’ }, { title: ‘Step 3:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘top’, position: { top: 200, left: 100 } }, { title: ‘Step 4:’, templateUrl: ‘trainning-content.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, placement: ‘bottom’, position: ‘#startAgain’ }, { stepClass: ‘last-step’, backdropClass: ‘last-backdrop’, templateUrl: ‘trainning-content-done.html’, controller: ‘StepPanelController’, controllerAs: ‘stepPanel’, position: [‘$window’, ‘stepPanel’, function($window, stepPanel) { var win = angular.element($window); return { top: (win.height() – stepPanel.height()) / 2, left: (win.width() – stepPanel.width()) / 2 } }] }] }) .controller(‘DemoController’, [‘trainningService’, ‘trainningCourses’, ‘modalBackdropService’, function(trainningService, trainningCourses, modalBackdropService) { var vm = this; vm.trainning = function() { //call this service should wait your really document ready event. trainningService.trainning(trainningCourses.courses) .done(function() { vm.isDone = true; }); }; var backdropInstance = angular.noop; vm.backdrop = function() { modalBackdropService.backdrop(); }; vm.trainning(); return vm; }]); 希望本文所述对大家AngularJS程序设计有所帮助。 您可能感兴趣的文章: AngularJS中实现用户访问的身份认证和表单验证功能 3个可以改善用户体验的AngularJS指令介绍 AngularJS用户选择器指令实例分析 AngularJS实现用户登录状态判断的方法(Model添加拦截过滤器,路由增加限制) Angular.js与Bootstrap相结合实现表格分页代码 总结十个Angular.js由浅入深的面试问题 浅谈angular.js中实现双向绑定的方法$watch $digest $apply Angular.js如何从PHP读取后台数据 Angular.js回顾ng-app和ng-model使用技巧 Angular.js实现注册系统的实例详解 AngularJS 自定义插件 相关文章 AngularJS ng-change 指令的详解及简单实例 本文主要介绍AngularJS ng-change 指令,这里对ng-change指令资料做了详细介绍,并提供源码和运行结果,有需要的小伙伴参考下 2016-07-07 AngularJs验证重复密码的方法(两种) 本文给大家介绍angularjs验证重复密码的两种方法,每种方法都有各自的优缺点,大家可以根据需要选择一种方法,具体内容详情大家通过本文详细了解下吧 2016-11-11 AngularJS基础 ng-if 指令用法 本文主要介绍了AngularJS ng-if 指令,在这里整理了一些ng-if 指令的基础资料,并有实例代码,有需要的同学可以参考下 2016-08-08 AngularJS中重新加载当前路由页面的方法 下面小编就为大家分享一篇AngularJS中重新加载当前路由页面的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 2018-03-03 angularJS与bootstrap结合实现动态加载弹出提示内容 这篇文章主要介绍了angularJS与bootstrap结合实现动态加载弹出提示内容,通过bootstrp弹出提示。感兴趣的朋友可以参考下本篇文章 2015-10-10 Angular ng-repeat 对象和数组遍历实例 这篇文章主要介绍了Angular ng-repeat对象和数组遍历的相关资料,并附代码示例,需要的朋友可以参考下 2016-09-09 Angular.js基础学习之初始化 这篇文章主要介绍了Angular.js基础学习之初始化的相关资料,AngularJS 启动有两种方式,一种是绑定初始化,自动加载,另外一种是手动初始化,文中介绍的很详细,需要的朋友可以参考下。 2017-03-03 Angular浏览器插件Batarang介绍及使用 本篇文章主要介绍了Angular浏览器插件Batarang介绍及使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 2018-02-02 AngularJS使用指令增强标准表单元素功能 这篇文章主要介绍了AngularJS使用指令增强标准表单元素功能,包括数据绑定、建立模型属性、验证表单等,感兴趣的小伙伴们可以参考一下 2016-07-07 Angular.JS实现无限级的联动菜单(使用demo) 这篇文章主要介绍了Angular.JS中实现无限级联动菜单的使用示例,本文是在之前的一篇文章的基础上进行的几个demo分享,有需要的朋友可以参考借鉴,下面来一起看看吧。 2017-02-02 最新评论

本文实例讲述了AngularJS自定义插件实现网站用户引导功能。分享给大家供大家参考,具体如下:

最近由于项目进行了较大的改版,为了让用户能够适应这次新的改版,因此在系统中引入了“用户引导”功能,对于初次进入系统的用户一些简单的使用培训training。对于大多数网站来说,这是一个很常见的功能。所以在开发这个任务之前,博主尝试将其抽象化,独立于现有系统的业务逻辑,将其封装为一个通用的插件,使得代码更容易扩展和维护。

无图无真相,先上图:

关于这款trainning插件的使用很简单,它采用了类似Angular路由一样的配置,只需要简单的配置其每一步training信息。

title:step的标题信息;
template/templateUrl: step的内容模板信息。这类可以配置html元素,或者是模板的url地址,同时templateUrl也支持Angular route一样的function语法;
controller: step的控制器配置;在controller中可注入如下参数:当前step – currentStep、所有step的配置 – trainnings、当前step的配置 – currentTrainning、以及下一步的操作回调 – trainningInstance(其中nextStep:为下一步的回调,cancel为取消用户引导回调);
controllerAs: controller的别名;
resolve:在controller初始化前的数据配置,同Angular路由中的resolve;
locals:本地变量,和resolve相似,可以传递到controller中。区别之处在于它不支持function调用,对于常量书写会比resolve更方便;
placement: step容器上三角箭头的显示方位,
position: step容器的具体显示位置,这是一个绝对坐标;可以传递{left: 100, top: 100}的绝对坐标,也可以是#stepPanelHost配置相对于此元素的placement位置。同时它也支持自定义function和注入Angular的其他组件语法。并且默认可注入:所有step配置 – trainnings,当前步骤 – step,当前step的配置 – currentTrainning,以及step容器节点 – stepPanel;
backdrop:是否需要显示遮罩层,默认显示,除非显示声明为false配置,则不会显示遮罩层;
stepClass:每一个step容器的样式信息;
backdropClass: 每一个遮罩层的样式信息。

了解了这些配置后,并根据特定需求定制化整个用户引导的配置信息后,我们就可以使用trainningService的trainning方法来在特定实际启动用户引导,传入参数为每一步step的配置信息。并可以注册其done或者cancel事件:

trainningService.trainning(trainningCourses.courses)
.done(function() {
 vm.isDone = true;
});

下面是一个演示的配置信息:

.constant('trainningCourses', {
  courses: [{
   title: 'Step 1:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'left',
   position: '#blogControl'
  },{
   title: 'Step 3:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'top',
   position: {
    top: 200,
    left: 100
   }
  },
   ...
  {
   stepClass: 'last-step',
   backdropClass: 'last-backdrop',
   templateUrl: 'trainning-content-done.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   position: ['$window', 'stepPanel', function($window, stepPanel) {
    // 自定义函数,使其屏幕居中
    var win = angular.element($window);
    return {
     top: (win.height() - stepPanel.height()) / 2,
     left: (win.width() - stepPanel.width()) / 2
    }
   }]
  }]
})

本文插件源码和演示效果唯一codepen上,效果图如下:

在trainning插件的源码设计中,包含如下几个要点:

提供service api。因为关于trainning这个插件,它是一个全局的插件,正好在Angular中所有的service也是单例的,所以将用户引导逻辑封装到Angular的service中是一个不错的设计。但对于trainning的每一步展示内容信息则是DOM操作,在Angular的处理中它不该存在于service中,最佳的方式是应该把他封装到Directive中。所以这里采用Directive的定义,并在service中compile,然后append到body中。

对于每一个这类独立的插件应该封装一个独立的scope,这样便于在后续的销毁,以及不会与现有的scope变量的冲突。

$q对延时触发的结果包装。对于像该trainning插件或者modal这类操作结果采用promise的封装,是一个不错的选择。它取代了回调参数的复杂性,并以流畅API的方式展现,更利于代码的可读性。同时也能与其他Angular service统一返回API。

对于controller、controllerAs、resolve、template、templateUrl这类类似路由的处理代码,完全可以移到到你的同类插件中去。它们可以增加插件的更多定制化扩展。关于这部分代码的解释,博主将会在后续文章中为大家推送。

利用$injector.invoke动态注入和调用Angular service,这样既能获取Angular其他service注入的扩展性,也能获取到函数的动态性。如上例中的屏幕居中的自定义扩展方式。

这类设计要点,同样可以运用到想modal、alert、overload这类全局插件中。有兴趣的读者,你可以在博主的codepen笔记中阅读这段代码http://codepen.io/greengerong/pen/pjwXQW#0。

上述代码摘录如下:

HTML:

<div ng-app="com.github.greengerong" ng-controller="DemoController as demo">
 <div class="alert alert-success fade in" ng-if='demo.isDone'>
  <strong>All trainning setps done!</strong>
 </div>
 <button id="startAgain" class="btn btn-primary start-again" ng-click="demo.trainning()">You can start trainning again</button>
 <div class="blog">
  <form class="form-inline">
   <div class="form-group">
    <label class="sr-only" for="exampleInputAmount">Blog :</label>
    <div class="input-group">
     <input id="blogControl" type="text" class="form-control" />
    </div>
   </div>
   <button id="submitBlog" class="btn btn-primary" ng-click="demo.backdrop()">Public blog</button>
  </form>
 </div>
 <script type="text/ng-template" id="modal-backdrop.html">
  <div class="modal-backdrop fade in {{backdropClass}}" ng-style="{'z-index': zIndex || 1040}"></div>
 </script>
 <script type="text/ng-template" id="trainning-step.html">
  <div class="trainning-step">
   <div style="display:block; z-index:1080;left:-1000px;top:-1000px;" ng-style="positionStyle" class="step-panel {{currentTrainning.placement}} fade popover in {{currentTrainning.stepClass}}" ng-show="!isProgressing">
    <div class="arrow"></div>
    <div class="popover-inner">
     <h3 class="popover-title" ng-if='currentTrainning.title'>{{currentTrainning.title}}</h3>
     <div class="popover-content">
     </div>
    </div>
   </div>
   <ui-backdrop backdrop-class="currentTrainning.backdropClass" ng-if="currentTrainning.backdrop !== false"></ui-backdrop>
  </div>
 </script>
 <script type="text/ng-template" id="trainning-content.html">
  <div class="step-content">
   <div>{{ stepPanel.texts[stepPanel.currentStep - 1]}}</div>
   <div class="next-step">
    <ul class="step-progressing">
    <li data-ng-repeat="item in stepPanel.trainnings.length | range"
     data-ng-class="{active: stepPanel.currentStep == item}">
    </li>
   </ul>
    <button type="button" class="btn btn-link btn-next pull-right" ng-click="stepPanel.trainningInstance.nextStep({$event:$event, step:step});">Next</button>
   </div>
  </div>
 </script>
 <script type="text/ng-template" id="trainning-content-done.html">
  <div class="step-content">
    <div>
 {{ stepPanel.texts[stepPanel.currentStep - 1]}}
   </div>
   <div class="next-step">
    <ul class="step-progressing">
    <li data-ng-repeat="item in stepPanel.trainnings.length | range"
     data-ng-class="{active: stepPanel.currentStep == item}">
    </li>
   </ul>
    <button type="button" class="btn btn-link pull-right" ng-click="nextStep({$event:$event, step:step});">Got it</button>
   </div>
  </div>
 </script>
</div>

CSS:

.last-step{
 /* background-color: blue;*/
}
.last-backdrop{
 background-color: #FFFFFF;
}
.blog{
 position: absolute;
 left: 300px;
 top: 100px;
}
.start-again{
 position: absolute;
 left: 400px;
 top: 250px;
}
.next-step {
 .step-progressing {
  margin: 10px 0px;
  display: inline-block;
  li {
  margin-right: 5px;
  border: 1px solid #fff;
  background-color: #6E6E6E;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
  &.active {
   background-color: #0000FF;
  }
  }
 }
}

JS:

//Please set step content to fixed width when complex content or dynamic loading.
angular.module('com.github.greengerong.backdrop', [])
 .directive('uiBackdrop', ['$document', function($document) {
  return {
   restrict: 'EA',
   replace: true,
   templateUrl: 'modal-backdrop.html',
   scope: {
    backdropClass: '=',
    zIndex: '='
   }
   /* ,link: function(){
    $document.bind('keydown', function(evt){
     evt.preventDefault();
     evt.stopPropagation();
    });
    scope.$on('$destroy', function(){
     $document.unbind('keydown');
    });
    }*/
  };
 }])
 .service('modalBackdropService', ['$rootScope', '$compile', '$document', function($rootScope, $compile, $document) {
  var self = this;
  self.backdrop = function(backdropClass, zIndex) {
   var $backdrop = angular.element('<ui-backdrop></ui-backdrop>')
    .attr({
     'backdrop-class': 'backdropClass',
     'z-index': 'zIndex'
    });
   var backdropScope = $rootScope.$new(true);
   backdropScope.backdropClass = backdropClass;
   backdropScope.zIndex = zIndex;
   $document.find('body').append($compile($backdrop)(backdropScope));
   return function() {
    $backdrop.remove();
    backdropScope.$destroy();
   };
  };
 }]);
angular.module('com.github.greengerong.trainning', ['com.github.greengerong.backdrop', 'ui.bootstrap'])
 .directive('trainningStep', ['$timeout', '$http', '$templateCache', '$compile', '$position', '$injector', '$window', '$q', '$controller', function($timeout, $http, $templateCache, $compile, $position, $injector, $window, $q, $controller) {
  return {
   restrict: 'EA',
   replace: true,
   templateUrl: 'trainning-step.html',
   scope: {
    step: '=',
    trainnings: '=',
    nextStep: '&',
    cancel: '&'
   },
   link: function(stepPanelScope, elm) {
    var stepPanel = elm.find('.step-panel');
    stepPanelScope.$watch('step', function(step) {
     if (!step) {
      return;
     }
     stepPanelScope.currentTrainning = stepPanelScope.trainnings[stepPanelScope.step - 1];
     var contentScope = stepPanelScope.$new(false);
     loadStepContent(contentScope, {
      'currentStep': stepPanelScope.step,
      'trainnings': stepPanelScope.trainnings,
      'currentTrainning': stepPanelScope.currentTrainning,
      'trainningInstance': {
       'nextStep': stepPanelScope.nextStep,
       'cancel': stepPanelScope.cancel
      }
     }).then(function(tplAndVars) {
      elm.find('.popover-content').html($compile(tplAndVars[0])(contentScope));
     }).then(function() {
      var pos = stepPanelScope.currentTrainning.position;
      adjustPosition(stepPanelScope, stepPanel, pos);
     });
    });
    angular.element($window).bind('resize', function() {
     adjustPosition(stepPanelScope, stepPanel, stepPanelScope.currentTrainning.position);
    });
    stepPanelScope.$on('$destroy', function() {
     angular.element($window).unbind('resize');
    });
    function getPositionOnElement(stepScope, setpPos) {
     return $position.positionElements(angular.element(setpPos), stepPanel, stepScope.currentTrainning.placement, true);
    }
    function positionOnElement(stepScope, setpPos) {
     var targetPos = angular.isString(setpPos) ? getPositionOnElement(stepScope, setpPos) : setpPos;
     var positionStyle = stepScope.currentTrainning || {};
     positionStyle.top = targetPos.top + 'px';
     positionStyle.left = targetPos.left + 'px';
     stepScope.positionStyle = positionStyle;
    }
    function adjustPosition(stepScope, stepPanel, pos) {
     if (!pos) {
      return;
     }
     var setpPos = angular.isFunction(pos) || angular.isArray(pos) ? $injector.invoke(pos, null, {
      trainnings: stepScope.trainnings,
      step: stepScope.setp,
      currentTrainning: stepScope.currentTrainning,
      stepPanel: stepPanel
     }) : pos;
     //get postion should wait for content setup
     $timeout(function() {
      positionOnElement(stepScope, setpPos);
     });
    }
    function loadStepContent(contentScope, ctrlLocals) {
     var trainningOptions = contentScope.currentTrainning,
      getTemplatePromise = function(options) {
       return options.template ? $q.when(options.template) :
        $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl, {
         cache: $templateCache
        }).then(function(result) {
         return result.data;
        });
      },
      getResolvePromises = function(resolves) {
       var promisesArr = [];
       angular.forEach(resolves, function(value) {
        if (angular.isFunction(value) || angular.isArray(value)) {
         promisesArr.push($q.when($injector.invoke(value)));
        }
       });
       return promisesArr;
      },
      controllerLoader = function(trainningOptions, trainningScope, ctrlLocals, tplAndVars) {
       var ctrlInstance;
       ctrlLocals = angular.extend({}, ctrlLocals || {}, trainningOptions.locals || {});
       var resolveIter = 1;
       if (trainningOptions.controller) {
        ctrlLocals.$scope = trainningScope;
        angular.forEach(trainningOptions.resolve, function(value, key) {
         ctrlLocals[key] = tplAndVars[resolveIter++];
        });
        ctrlInstance = $controller(trainningOptions.controller, ctrlLocals);
        if (trainningOptions.controllerAs) {
         trainningScope[trainningOptions.controllerAs] = ctrlInstance;
        }
       }
       return trainningScope;
      };
     var templateAndResolvePromise = $q.all([getTemplatePromise(trainningOptions)].concat(getResolvePromises(trainningOptions.resolve || {})));
     return templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
      controllerLoader(trainningOptions, contentScope, ctrlLocals, tplAndVars);
      return tplAndVars;
     });
    }
   }
  };
 }])
 .service('trainningService', ['$compile', '$rootScope', '$document', '$q', function($compile, $rootScope, $document, $q) {
  var self = this;
  self.trainning = function(trainnings) {
   var trainningScope = $rootScope.$new(true),
    defer = $q.defer(),
    $stepElm = angular.element('<trainning-step></trainning-step>')
    .attr({
     'step': 'step',
     'trainnings': 'trainnings',
     'next-step': 'nextStep($event, step);',
     'cancel': 'cancel($event, step)'
    }),
    destroyTrainningPanel = function(){
     if (trainningScope) {
      $stepElm.remove();
      trainningScope.$destroy();
     }
    };
   trainningScope.cancel = function($event, step){
    defer.reject('cancel');
   };
   trainningScope.nextStep = function($event, step) {
    if (trainningScope.step === trainnings.length) {
     destroyTrainningPanel();
     return defer.resolve('done');
    }
    trainningScope.step++;
   };
   trainningScope.trainnings = trainnings;
   trainningScope.step = 1;
   $document.find('body').append($compile($stepElm)(trainningScope));
   trainningScope.$on('$locationChangeStart', destroyTrainningPanel);
   return {
    done: function(func) {
     defer.promise.then(func);
     return this;
    },
    cancel: function(func) {
     defer.promise.then(null, func);
     return this;
    }
   };
  };
 }]);
angular.module('com.github.greengerong', ['com.github.greengerong.trainning'])
.filter('range', [function () {
   return function (len) {
    return _.range(1, len + 1);
 };
 }])
 .controller('StepPanelController', ['currentStep', 'trainnings', 'trainningInstance', 'trainnings', function(currentStep, trainnings, trainningInstance, trainnings) {
  var vm = this;
  vm.currentStep = currentStep;
  vm.trainningInstance = trainningInstance;
  vm.trainnings = trainnings;
  vm.texts = ['Write your own sort blog.', 'Click button to public your blog.', 'View your blog info on there.', 'Click this button, you can restart this trainning when .', 'All trainnings done!'];
  return vm;
 }])
 .constant('trainningCourses', {
  courses: [{
   title: 'Step 1:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'left',
   position: '#blogControl'
  }, {
   title: 'Step 2:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'right',
   backdrop: false,
   position: '#submitBlog'
  }, {
   title: 'Step 3:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'top',
   position: {
    top: 200,
    left: 100
   }
  }, {
   title: 'Step 4:',
   templateUrl: 'trainning-content.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   placement: 'bottom',
   position: '#startAgain'
  }, {
   stepClass: 'last-step',
   backdropClass: 'last-backdrop',
   templateUrl: 'trainning-content-done.html',
   controller: 'StepPanelController',
   controllerAs: 'stepPanel',
   position: ['$window', 'stepPanel', function($window, stepPanel) {
    var win = angular.element($window);
    return {
     top: (win.height() - stepPanel.height()) / 2,
     left: (win.width() - stepPanel.width()) / 2
    }
   }]
  }]
 })
 .controller('DemoController', ['trainningService', 'trainningCourses', 'modalBackdropService', function(trainningService, trainningCourses, modalBackdropService) {
  var vm = this;
  vm.trainning = function() {
   //call this service should wait your really document ready event.
   trainningService.trainning(trainningCourses.courses)
    .done(function() {
     vm.isDone = true;
    });
  };
  var backdropInstance = angular.noop;
  vm.backdrop = function() {
   modalBackdropService.backdrop();
  };
  vm.trainning();
  return vm;
 }]);

希望本文所述对大家AngularJS程序设计有所帮助。

您可能感兴趣的文章:

  • AngularJS中实现用户访问的身份认证和表单验证功能
  • 3个可以改善用户体验的AngularJS指令介绍
  • AngularJS用户选择器指令实例分析
  • AngularJS实现用户登录状态判断的方法(Model添加拦截过滤器,路由增加限制)
  • Angular.js与Bootstrap相结合实现表格分页代码
  • 总结十个Angular.js由浅入深的面试问题
  • 浅谈angular.js中实现双向绑定的方法$watch $digest $apply
  • Angular.js如何从PHP读取后台数据
  • Angular.js回顾ng-app和ng-model使用技巧
  • Angular.js实现注册系统的实例详解
张贴在3