微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

AngularJS自定义插件实现网站用户引导功能示例

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

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

无图无真相,先上图:

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

标题信息;

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事件:

rush:js;"> trainningService.trainning(trainningCourses.courses) .done(function() { vm.isDone = true; });

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

rush:js;"> .constant('trainningCourses',{ courses: [{ title: 'Step 1:',templateUrl: 'trainning-content.html',controller: 'StepPanelController',controllerAs: 'stepPanel',placement: 'left',position: '#blogControl' },{ title: 'Step 3:',placement: 'top',position: { top: 200,left: 100 } },... { stepClass: 'last-step',backdropClass: 'last-backdrop',templateUrl: 'trainning-content-done.html',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插件的源码设计中,包含如下几个要点:

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

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

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

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

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

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

上述代码摘录如下:

HTML:

rush:xhtml;">

All trainning setps done!

CSS:

rush: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:

rush: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','$position','$injector','$window','$q','$controller',function($timeout,$http,$templateCache,$position,$injector,$window,$q,$controller) { return { restrict: 'EA',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,stepPanelScope.currentTrainning.position); }); stepPanelScope.$on('$destroy',function() { angular.element($window).unbind('resize'); }); function getPositionOnElement(stepScope,setpPos) { return $position.positionElements(angular.element(setpPos),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,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,tplAndVars); return tplAndVars; }); } } }; }]) .service('trainningService',['$compile','$rootScope',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',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',{ title: 'Step 2:',placement: 'right',backdrop: false,position: '#submitBlog' },{ title: 'Step 4:',placement: 'bottom',position: '#startAgain' },{ stepClass: 'last-step',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程序设计有所帮助。

原文地址:https://www.jb51.cc/js/44608.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐