In order to allow users to adapt to the new Web site, introduced in the system "user-led" function, for most sites, this is a very common function. So before you develop this task, try to abstract it, separate it from the business logic of your existing system, and encapsulate it as a general-purpose plug-in, making it easier to extend and maintain your code.
Looks like a look at the effect chart
The use of this trainning plug-in is simple, it takes the same configuration as angular routing, simply configure each step training information.
Title:step the title of the information;
Template/templateurl:step the content template information. This class can configure HTML elements, or the URL address of the template, while Templateurl also supports the same function syntax as angular route;
Controller:step controller configuration; The following parameters can be injected into the controller: current step? Currentstep, all step configuration? Trainnings, the current step configuration? Currenttrainning, and the next action callback? Trainninginstance (where NeXTSTEP: For the next callback, Cancel to cancel the user boot callback);
The alias of the Controlleras:controller;
Resolve: Data configuration before controller initialization, with resolve in angular routing;
Locals: local variables, similar to resolve, can be passed into the controller. The difference is that it does not support function calls and is more convenient for constant writing than for resolve;
Placement:step the display azimuth of the triangular arrows on the container,
The exact display position of the Position:step container, which is an absolute coordinate, the absolute coordinates of {left:100, top:100} can be passed, or the placement location of the #steppanelhost configuration relative to this element. It also supports custom function and other component syntax for injecting angular. And the default can be injected: all step configurations? Trainnings, what's the current step? Step, the current step configuration? Currenttrainning, and the Step container node? Steppanel;
Backdrop: Whether the mask layer needs to be displayed, by default, the mask layer is not displayed unless the display is declared as false;
Stepclass: The style information of each step container;
Backdropclass: Style information for each matte layer.
After understanding these configurations and customizing the entire user-booted configuration information based on specific requirements, we can use the Trainningservice trainning method to launch the user boot in a specific practical way, passing in the parameters for each step of the configuration information. and may register its done or cancel event:
Trainningservice.trainning (trainningcourses.courses)
. Done (function () {
Vm.isdone = true;
});
Here is a demo of the configuration information:
. 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) {
Custom function to center its screen
var win = angular.element ($window);
return {
Top: (Win.height ()-steppanel.height ())/2,
Left: (Win.width ()-steppanel.width ())/2
}
}]
}]
})
This article plug-in source and demo effect only Codepen, the effect is as follows:
<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> &NBSP;&NBSP;{{&NBSP;STEPPANEL.TEXTS[STEPPANEL.CURRENTSTEP&NBSP;-&NBSP;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> &nBsp; <button type= "button" class= "btn Btn-link pull-right " ng-click=" NextStep ({$event: $event, step:step}); " >Got it</button> </div > </div> </script> </ Div>
. 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; } }
}
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 '); }); }*/
}; &NBSP;&NBSP;&NBSP;&NBSP}]) .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 ({ &nBSP; ' 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 ();
};
};
&NBSP;&NBSP;&NBSP;&NBSP}]); 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 }
&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}). Then (function (Tplandvars) {
elm.find ('. Popover-content '). HTML ($compile (tplandvars[0)) (Contentscope); &NBSP;&NBSP}). 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, &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;STEP:&NBSP;STEPSCOPE.SETP, currenttrainning: stepscope.currenttrainning, steppanel: steppanel
&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}) : 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 &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}). Then (function (result) { return
result.data;
}); &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}, getresolvepromises = function (resolves) {
var promisesArr = []; angular.foreach (resolves, function (value) { if ( Angular.isfunction (value) | | angular.isarray (value)) { &nbsP; promisesarr.push ($q. When ($injector. Invoke (value)); }
});
return promisesArr; &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}, controllerloader = function ( Trainningoptions, trainningscope,&nBsp;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) { &nBsp; controllerloader (trainningOptions,
Contentscope, ctrllocals, tplandvars);
return tplAndVars;
}); } } }
; &NBSP;&NBSP;&NBSP;&NBSP}]) .service (' Trainningservice ', [' $compile ', ' $ Rootscope ', ' $document ', ' $q ', function ($compile, $rootScope, $document, $q) { var self = this; &nBsp; 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) ' &NBSP;&NBSP}), 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) { &nBsp; destroytrainningpanel ();
return defer.resolve (' done '); }
trainningScope.step++;
}; trainningScope.trainnings =
Trainnings;
trainningScope.step = 1; $document. Find (' body '). Append ($compile ($STEPELM)
(Trainningscope)); $rootScope. $on (' $locationChangeStart ', destroytrainningpanel); return { done: function (func) {
defer.promise.then (func); &NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}, cancel: function (func ) {
defer.promise.then (Null, func); }
}; };
&NBSP;&NBSP;&NBSP;&NBSP}]); Angular.module (' Com.github.greengerong ', [' com.github.greengerong.trainning '). Filter (' Range ', [ function () { return function (len) {
return _.range (1, len + 1);
}; &NBSP;&NBSP;&NBSP;&NBSP}]) .controller (' Steppanelcontroller ', [' currentStep '), ' trainnings ', ' trainninginstance ', ' trainnings ', function (Currentstep, trainnings, trainninginstance, trainnings) {&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;VAR&NBSP;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; &NBSP;&NBSP;&NBSP;&NBSP}]) .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, &NBsp; left: (Win.width () - steppanel.width ()) / 2 }
&NBSP;&NBSP;&NBSP;&NBSP;&NBSP}] }]   .controller (' Democontroller ', [' trainningservice ', ' trainningCourses '), ' Modalbackdropservice ', function (Trainningservice, trainningcourses, modalbackdropservice) { var vm = this; vm.trainning = function () { //call this Service should&nbSp;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; &NBSP;&NBSP;&NBSP;&NBSP}]);
Run Effect chart
The source design of the Trainning plug-in includes the following points:
provide service APIs. Because about trainning this plug-in, it is a global plug-in, just in angular all the service is also a single example, so the user guidance logic encapsulated into the angular service is a good design. However, for each step of trainning, the content information is DOM operation, in angular processing it should not exist in the service, the best way is to encapsulate him in the directive. So here the definition of directive, and in the service compile, and then append into the body.
for each such standalone plug-in should encapsulate a separate scope that facilitates subsequent destruction and does not conflict with existing scope variables.
$q Wrap The result of a delay trigger. It is a good choice to use promise encapsulation for such operations as the Trainning plug-in or modal. It replaces the complexity of callback parameters and is presented in a fluent API, which is more readable to the code. It also can be integrated with other angular service to return the API.
for similar routes such as Controller, Controlleras, resolve, template, Templateurl, can be moved to your same plug-in. They can add more customization extensions to plug-ins. On this part of the code explanation, the blogger will be in the follow-up article for everyone to push.
uses $injector.invoke to dynamically inject and invoke angular service, so as to gain the extensibility of angular other service injection, as well as to obtain the dynamic nature of the function. As shown in the previous example, the custom extension method is centered on the screen. The design essentials such as
can also be applied to global plug-ins such as modal, alert, and overload.