angular-visor
Advanced tools
Comparing version 0.0.9 to 0.1.0
{ | ||
"name": "visor", | ||
"description": "Angular authentication and authorization library", | ||
"version": "0.0.9", | ||
"version": "0.1.0", | ||
"homepage": "https://github.com/illniyar/visor", | ||
@@ -6,0 +6,0 @@ "main": "./release/visor.js", |
@@ -0,1 +1,5 @@ | ||
<a name"0.1.0"></a> | ||
## 0.1.0 (2015-07-08) | ||
<a name"0.0.6"></a> | ||
@@ -2,0 +6,0 @@ ### 0.0.6 (2015-06-17) |
@@ -19,3 +19,3 @@ module.exports = function (grunt) { | ||
dist: ['<%= builddir %>', '<%=sitedir %>'], | ||
"gh-pages": ['.grunt'] | ||
'gh-pages': ['.grunt'] | ||
}, | ||
@@ -25,3 +25,3 @@ concat: { | ||
banner: '<%=meta.banner\n\n%>' + | ||
'if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){\n' + | ||
'if (typeof module !== \'undefined\' && typeof exports !== \'undefined\' && module.exports === exports){\n' + | ||
' module.exports = \'visor\';\n' + | ||
@@ -33,3 +33,3 @@ '}\n\n' + | ||
build: { | ||
src: "src/*.js", | ||
src: 'src/*.js', | ||
dest: '<%= builddir %>/<%= module_name %>.js' | ||
@@ -74,4 +74,4 @@ } | ||
expand: true, | ||
src: ["visor.js", "visor.min.js"], | ||
cwd: "<%=builddir%>/", | ||
src: ['visor.js', 'visor.min.js'], | ||
cwd: '<%=builddir%>/', | ||
dest: '<%=releasedir%>/' | ||
@@ -89,3 +89,3 @@ }] | ||
options: { | ||
preset:'angular', | ||
preset: 'angular', | ||
file: 'CHANGELOG.md', | ||
@@ -98,5 +98,5 @@ app_name: 'Visor' | ||
file: 'package.json', | ||
additionalFiles:'bower.json', | ||
additionalFiles: 'bower.json', | ||
tagName: 'v<%= version %>', | ||
commitMessage: 'release <%= version %>' , | ||
commitMessage: 'release <%= version %>', | ||
tagMessage: 'Version <%= version %>', | ||
@@ -109,3 +109,3 @@ beforeRelease: ['changelog'] | ||
files: { | ||
src: ['package.json','bower.json','CHANGELOG.md'] | ||
src: ['package.json', 'bower.json', 'CHANGELOG.md'] | ||
} | ||
@@ -117,3 +117,3 @@ } | ||
options: { | ||
message: "Publish version <%= pkg.version %>" | ||
message: 'Publish version <%= pkg.version %>' | ||
} | ||
@@ -149,9 +149,9 @@ } | ||
grunt.registerTask('npm-publish', 'publish to npm.', function() { | ||
grunt.registerTask('npm-publish', 'publish to npm.', function () { | ||
var npm = require('npm'); | ||
var done = this.async(); | ||
grunt.log.writeln('Publishing to NPM'); | ||
npm.load(function(){ | ||
npm.commands.publish(['.'],function(e){ | ||
if (e){ | ||
npm.load(function () { | ||
npm.commands.publish(['.'], function (e) { | ||
if (e) { | ||
grunt.log.errorln(e); | ||
@@ -171,5 +171,5 @@ done(false); | ||
grunt.registerTask('gh-pages', 'Build, create site and push to gh-pages', ['gh-pages', 'clean:gh-pages']); | ||
grunt.registerTask('push-to-git','Add, commit, create tag and push to git',['gitadd:release','gitcommit:master','gittag:release','gitpush:origin']); | ||
grunt.registerTask('publish','Builds and publishes to all relevent repositories', | ||
['bumpup:patch','site','changelog','push-to-git']) | ||
grunt.registerTask('push-to-git', 'Add, commit, create tag and push to git', ['gitadd:release', 'gitcommit:master', 'gittag:release', 'gitpush:origin']); | ||
grunt.registerTask('publish', 'Builds and publishes to all relevent repositories', | ||
['bumpup:minor', 'site', 'changelog', 'push-to-git', 'npm-publish']) | ||
} |
{ | ||
"name": "angular-visor", | ||
"author": "Illniyar", | ||
"version": "0.0.9", | ||
"version": "0.1.0", | ||
"main": "release/visor.js", | ||
@@ -44,2 +44,2 @@ "description": "Angular authentication and authorization library", | ||
} | ||
} | ||
} |
#Visor | ||
## Authentication and authorization library for angular.js [](https://travis-ci.org/Illniyar/visor) | ||
## Authentication and authorization library for angular.js [](https://travis-ci.org/Illniyar/visor)[](https://gemnasium.com/Illniyar/visor) | ||
--- | ||
@@ -5,0 +5,0 @@ |
1795
release/visor.js
/**visor | ||
* Angular authentication and authorization library | ||
* @version v0.0.9 | ||
* @version v0.1.0 | ||
* @link https://github.com/illniyar/visor.git | ||
* @license MIT License, http://www.opensource.org/licenses/MIT | ||
*/ | ||
if (typeof module !== "undefined" && typeof exports !== "undefined" && module.exports === exports){ | ||
if (typeof module !== 'undefined' && typeof exports !== 'undefined' && module.exports === exports){ | ||
module.exports = 'visor'; | ||
@@ -12,38 +12,38 @@ } | ||
(function (window, angular, undefined) { | ||
(function(){ | ||
"use strict"; | ||
(function () { | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` module contains the {@link delayLocationChange.delayLocationChange `delayLocationChange`} service. | ||
* | ||
*/ | ||
angular.module("delayLocationChange",[]) | ||
/** | ||
* @ngdoc overview | ||
* @name delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` module contains the {@link delayLocationChange.delayLocationChange `delayLocationChange`} service. | ||
* | ||
*/ | ||
angular.module("delayLocationChange", []) | ||
.service("delayLocationChange",["$rootScope","$q","$timeout","$location","$injector", | ||
function($rootScope,$q,$timeout,$location,$injector){ | ||
.service("delayLocationChange", ["$rootScope", "$q", "$timeout", "$location", "$injector", | ||
function ($rootScope, $q, $timeout, $location, $injector) { | ||
/** | ||
* @ngdoc service | ||
* @name delayLocationChange.delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` allows multiple services to stop the first location change (I.E. the rendering of the first page) until a promise is complete. | ||
* | ||
* | ||
* @param {promise|function()} waitFor - if a promise, will delay until promise is resolved | ||
* , if a function, will delay until the result of running the function, which must return a promise, will be resolved. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.module("myModule",["delayLocationChange"]) | ||
* .run(function(delayLocationChange){ | ||
/** | ||
* @ngdoc service | ||
* @name delayLocationChange.delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` allows multiple services to stop the first location change (I.E. the rendering of the first page) until a promise is complete. | ||
* | ||
* | ||
* @param {promise|function()} waitFor - if a promise, will delay until promise is resolved | ||
* , if a function, will delay until the result of running the function, which must return a promise, will be resolved. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.module("myModule",["delayLocationChange"]) | ||
* .run(function(delayLocationChange){ | ||
* delayLocationChange(function($http){ | ||
@@ -56,108 +56,204 @@ * return $http.get("/something/that/is/needed") | ||
* }; | ||
* </pre> | ||
*/ | ||
var service = function(arg){ | ||
if (arg.then) { | ||
//handles a promise | ||
addPromise(arg); | ||
} else { | ||
//assume it's a function | ||
if (changeStarted) { | ||
addPromise($injector.invoke(fn)); | ||
} else { | ||
//need to wait until angular started the locationChange, otherwise | ||
//something might start running before it's should | ||
waitingFunctions.push(arg); | ||
} | ||
} | ||
}; | ||
* </pre> | ||
*/ | ||
var service = function (arg) { | ||
if (arg.then) { | ||
//handles a promise | ||
addPromise(arg); | ||
} else { | ||
//assume it's a function | ||
if (changeStarted) { | ||
addPromise($injector.invoke(fn)); | ||
} else { | ||
//need to wait until angular started the locationChange, otherwise | ||
//something might start running before it's should | ||
waitingFunctions.push(arg); | ||
} | ||
} | ||
}; | ||
// we make sure that all promises finish by counting the number of promises | ||
//we recieved | ||
var unfinishedPromises = 0; | ||
var waitingFunctions = []; | ||
var changeStarted = false,_toUrl,_fromUrl,nextUrl; | ||
// we make sure that all promises finish by counting the number of promises | ||
//we recieved | ||
var unfinishedPromises = 0; | ||
var waitingFunctions = []; | ||
var changeStarted = false, _toUrl, _fromUrl, nextUrl; | ||
//checkPromises both determines if all promises were resolved and initiates | ||
//the delayed location change if no more promises remain | ||
function checkPromises(){ | ||
unfinishedPromises--; | ||
if (changeStarted && unfinishedPromises <= 0){ | ||
reloadChange(); | ||
} | ||
} | ||
//checkPromises both determines if all promises were resolved and initiates | ||
//the delayed location change if no more promises remain | ||
function checkPromises() { | ||
unfinishedPromises--; | ||
if (changeStarted && unfinishedPromises <= 0) { | ||
reloadChange(); | ||
} | ||
} | ||
function reloadChange(){ | ||
if ($location.absUrl() === _toUrl) { | ||
//we are running on the assumption (that might prove false at some point) | ||
//that nothing happens between canceling $locationChangeStart and emitting | ||
//$locationChangeSuccess | ||
$rootScope.$broadcast("$locationChangeSuccess",_toUrl,_fromUrl); | ||
} else { | ||
$location.url(nextUrl); | ||
} | ||
} | ||
function reloadChange() { | ||
if ($location.absUrl() === _toUrl) { | ||
//we are running on the assumption (that might prove false at some point) | ||
//that nothing happens between canceling $locationChangeStart and emitting | ||
//$locationChangeSuccess | ||
$rootScope.$broadcast("$locationChangeSuccess", _toUrl, _fromUrl); | ||
} else { | ||
$location.url(nextUrl); | ||
} | ||
} | ||
function addPromise(promise){ | ||
unfinishedPromises++; | ||
//to access using array notation because finally is a reserved word | ||
promise['finally'](checkPromises); | ||
} | ||
var unlisten = $rootScope.$on("$locationChangeStart",function(e,toUrl,fromUrl){ | ||
changeStarted = true; | ||
nextUrl = $location.url(); | ||
unlisten(); | ||
//We are relying on the fact that since the url never actually changed, | ||
//the fact that angular will return to the previous ulr when doing preventDefault, will not | ||
// have any effect | ||
e.preventDefault(); | ||
waitingFunctions.forEach(function(fn){addPromise($injector.invoke(fn))}); | ||
function addPromise(promise) { | ||
unfinishedPromises++; | ||
//to access using array notation because finally is a reserved word | ||
promise['finally'](checkPromises); | ||
} | ||
if(unfinishedPromises === 0 && !_toUrl){ //firstCall and no promises | ||
//we need to let at least one run through to verify | ||
//no promises will be added | ||
unfinishedPromises++; | ||
$timeout(checkPromises,1); | ||
} | ||
_toUrl = toUrl; | ||
_fromUrl = fromUrl; | ||
}); | ||
var unlisten = $rootScope.$on("$locationChangeStart", function (e, toUrl, fromUrl) { | ||
changeStarted = true; | ||
nextUrl = $location.url(); | ||
unlisten(); | ||
//We are relying on the fact that since the url never actually changed, | ||
//the fact that angular will return to the previous ulr when doing preventDefault, will not | ||
// have any effect | ||
e.preventDefault(); | ||
waitingFunctions.forEach(function (fn) { | ||
addPromise($injector.invoke(fn)) | ||
}); | ||
return service; | ||
}]); | ||
if (unfinishedPromises === 0 && !_toUrl) { //firstCall and no promises | ||
//we need to let at least one run through to verify | ||
//no promises will be added | ||
unfinishedPromises++; | ||
$timeout(checkPromises, 1); | ||
} | ||
_toUrl = toUrl; | ||
_fromUrl = fromUrl; | ||
}); | ||
return service; | ||
}]); | ||
})(); | ||
(function () { | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name visor.allowed | ||
* @description | ||
* | ||
* # Visor.Allowed | ||
* | ||
* `Visor.Allowed` contains directives that change elements based on weather a route is allowed or not. | ||
* | ||
*/ | ||
angular.module("visor.allowed", ["visor.permissions"]) | ||
/** | ||
* @ngdoc directive | ||
* @name visor.allowed.showIfAllowed | ||
* | ||
* @description | ||
* | ||
* the `showIfAllowed` directive shows or hides the given HTML element based on whether the expression | ||
* provided to `showIfAllowed` resolve to a route (url or state name) that can be accessed. | ||
* `showIfAllowed` directive acts similar to `ngHide` directive - it adds an 'ng-hide' class to the element. | ||
* | ||
* @animations | ||
* addClass: `.ng-hide` - happens when the `showIfAllowed` evaluates to a route that is restricted or when the | ||
* route becomes restricted | ||
* removeClass: `.ng-hide` - happens when the `showIfAllowed` evaluates to a route that is not restricted or | ||
* when the route is no longer restricted | ||
* | ||
* @element ANY | ||
* @param {expression} showIfAllowed If the {@link guide/expression expression} resolves to a route that | ||
* is currently available then the element is shown. | ||
*/ | ||
.directive("showIfAllowed", ["visorPermissions", "$animate", function (visorPermissions, $animate) { | ||
return { | ||
restrict: 'A', | ||
link: function (scope, element, attr) { | ||
var unListen = visorPermissions.notifyOnCacheClear(function () { | ||
syncElement(attr.showIfAllowed); | ||
}) | ||
(function(){ | ||
"use strict"; | ||
function syncElement(value) { | ||
// Copied from ngHideDirective (v1.3.13) | ||
var allowed = visorPermissions.checkPermissionsForRoute(value); | ||
$animate[allowed ? 'removeClass' : 'addClass'](element, "ng-hide", { | ||
tempClasses: "ng-hide-animate" | ||
}); | ||
} | ||
/** | ||
* @ngdoc overview | ||
* @name visor | ||
* @description | ||
* | ||
* # Visor | ||
* | ||
* `Visor` is an authentication and authorization module. | ||
* | ||
* <div doc-module-components="visor"></div> | ||
* | ||
* See {@link visor.visor `visor`} for usage. | ||
*/ | ||
angular.module("visor",["visor.permissions","visor.ui-router","visor.ngRoute","delayLocationChange"]) | ||
attr.$observe('showIfAllowed', syncElement); | ||
scope.$on('$destroy', unListen); | ||
} | ||
}; | ||
}]) | ||
/** | ||
* @ngdoc directive | ||
* @name visor.allowed.classIfRestricted | ||
* | ||
* @description | ||
* | ||
* the `classIfRestricted` directive adds a class to the given HTML element based on whether the expression | ||
* provided to `classIfRestricted` resolve to a route (url or state name) that is restricted. | ||
* | ||
* @animations | ||
* addClass: `.visor-restricted` - happens when the `classIfRestricted` evaluates to a route that is restricted or when the | ||
* route becomes restricted | ||
* removeClass: `.visor-restricted` - happens when the `classIfRestricted` evaluates to a route that is not restricted or | ||
* when the route is no longer restricted | ||
* | ||
* @element ANY | ||
* @param {expression} showIfAllowed If the {@link guide/expression expression} resolves to a route that | ||
* is currently available then the element is shown. | ||
* | ||
* @param {string} restrictedClass the class to add to the element. Defaults to 'visor-restricted' | ||
*/ | ||
.directive("classIfRestricted", ["visorPermissions", "$animate", function (visorPermissions, $animate) { | ||
return { | ||
restrict: 'A', | ||
link: function (scope, element, attr) { | ||
//internal mechanism - cache clear is the only way in which a permission value can change | ||
var unListen = visorPermissions.notifyOnCacheClear(function () { | ||
syncElement(attr.classIfRestricted); | ||
}) | ||
/** | ||
* @ngdoc service | ||
* @name visor.authenticatedOnly | ||
* @description | ||
* | ||
* # authenticatedOnly | ||
* | ||
* `authenticatedOnly` is a restrict function that only allows authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,authenticatedOnly){ | ||
function syncElement(value) { | ||
var allowed = visorPermissions.checkPermissionsForRoute(value); | ||
$animate[!allowed ? 'addClass' : 'removeClass'](element, attr.restrictedClass || 'visor-restricted'); | ||
}; | ||
attr.$observe('classIfRestricted', syncElement); | ||
scope.$on('$destroy', unListen); | ||
} | ||
}; | ||
}]) | ||
})(); | ||
(function () { | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name visor | ||
* @description | ||
* | ||
* # Visor | ||
* | ||
* `Visor` is an authentication and authorization module. | ||
* | ||
* <div doc-module-components="visor"></div> | ||
* | ||
* See {@link visor.visor `visor`} for usage. | ||
*/ | ||
angular.module("visor", ["visor.permissions", "visor.ui-router", "visor.ngRoute", "delayLocationChange", "visor.allowed"]) | ||
/** | ||
* @ngdoc service | ||
* @name visor.authenticatedOnly | ||
* @description | ||
* | ||
* # authenticatedOnly | ||
* | ||
* `authenticatedOnly` is a restrict function that only allows authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,authenticatedOnly){ | ||
* $stateProvider.state("private",{ | ||
@@ -167,22 +263,22 @@ * restrict: authenticatedOnly | ||
* } | ||
* </pre> | ||
*/ | ||
.constant("authenticatedOnly",function(authData){ | ||
return !!authData; | ||
}) | ||
* </pre> | ||
*/ | ||
.constant("authenticatedOnly", function (authData) { | ||
return !!authData; | ||
}) | ||
/** | ||
* @ngdoc service | ||
* @name visor.notForAuthenticated | ||
* @description | ||
* | ||
* # notForAuthenticated | ||
* | ||
* `notForAuthenticated` is a restrict function that does not allow authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,notForAuthenticated){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.notForAuthenticated | ||
* @description | ||
* | ||
* # notForAuthenticated | ||
* | ||
* `notForAuthenticated` is a restrict function that does not allow authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,notForAuthenticated){ | ||
* $stateProvider.state("private",{ | ||
@@ -192,27 +288,27 @@ * restrict: notForAuthenticated | ||
* } | ||
* </pre> | ||
*/ | ||
.constant("notForAuthenticated",function(authData){ | ||
return authData === undefined; | ||
}) | ||
* </pre> | ||
*/ | ||
.constant("notForAuthenticated", function (authData) { | ||
return authData === undefined; | ||
}) | ||
/** | ||
* @ngdoc service | ||
* @name visor.visorProvider | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visorProvider` provides configuration options to define how authentication and authorization works. | ||
* | ||
* The only required configuration is {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.visorProvider | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visorProvider` provides configuration options to define how authentication and authorization works. | ||
* | ||
* The only required configuration is {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
@@ -224,103 +320,104 @@ * return $http.get("/api/user/me").then(function(res){ | ||
* } | ||
* </pre> | ||
*/ | ||
.provider("visor",[function(){ | ||
function addNextToUrl(url,$location,restrictedUrl){ | ||
if (config.shouldAddNext){ | ||
if (url.indexOf("?") >=0) { | ||
return url.replace(/\?/,"?next=" + encodeURIComponent(restrictedUrl) + "&"); | ||
} | ||
return url + "?next=" + encodeURIComponent(restrictedUrl); | ||
} else { | ||
return url; | ||
} | ||
} | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#authenticateOnStartup | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* If `true` visor will try to authenticate before any route is accessed (it will stop the routing until the authentication promise is resolved). | ||
* If `false` will only authenticate when a user tries to access a route with restriction. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.authenticateOnStartup = true; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#loginRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an unauthenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function. | ||
* | ||
* Defaults to `/login` | ||
*/ | ||
config.loginRoute = "/login"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#homeRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after manual authentication. | ||
* Only meaningful when using the default {@link visor.visorProvider#doAfterManualAuthentication `visorProvider.doAfterManualAuthentication`} function. | ||
* | ||
* Defaults to `/` | ||
*/ | ||
config.homeRoute = "/"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#notAuthorizedRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an authenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthorized `visorProvider.doOnNotAuthorized`} function. | ||
* | ||
* Defaults to `/access_denied` | ||
*/ | ||
config.notAuthorizedRoute = "/access_denied"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#shouldAddNext | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* When using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function, visor adds a `next` parameter to the login url provided in {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* Once a user manually authenticates, that route is used to redirect back to the original requested url. | ||
* | ||
* If `false` will not add the next url in {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`}. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.shouldAddNext = true; | ||
* </pre> | ||
*/ | ||
.provider("visor", [function () { | ||
function addNextToUrl(url, $location, restrictedUrl) { | ||
if (config.shouldAddNext) { | ||
if (url.indexOf("?") >= 0) { | ||
return url.replace(/\?/, "?next=" + encodeURIComponent(restrictedUrl) + "&"); | ||
} | ||
return url + "?next=" + encodeURIComponent(restrictedUrl); | ||
} else { | ||
return url; | ||
} | ||
} | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#authenticate | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* This function needs to be configured in order to use visor. | ||
* | ||
* `visorProvider.authentication` defines the authentication function that will be called to provide | ||
* the authentication info at startup. | ||
* It must return a promise that will resolve to an object if the user is authenticated or rejected if | ||
* the user isn't authenticated. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#authenticateOnStartup | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* If `true` visor will try to authenticate before any route is accessed (it will stop the routing until the authentication promise is resolved). | ||
* If `false` will only authenticate when a user tries to access a route with restriction. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.authenticateOnStartup = true; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#loginRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an unauthenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function. | ||
* | ||
* Defaults to `/login` | ||
*/ | ||
config.loginRoute = "/login"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#homeRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after manual authentication. | ||
* Only meaningful when using the default {@link visor.visorProvider#doAfterManualAuthentication `visorProvider.doAfterManualAuthentication`} function. | ||
* | ||
* Defaults to `/` | ||
*/ | ||
config.homeRoute = "/"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#notAuthorizedRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an authenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthorized `visorProvider.doOnNotAuthorized`} function. | ||
* | ||
* Defaults to `/access_denied` | ||
*/ | ||
config.notAuthorizedRoute = "/access_denied"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#shouldAddNext | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* When using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function, visor adds a `next` parameter to the login url provided in {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* Once a user manually authenticates, that route is used to redirect back to the original requested url. | ||
* | ||
* If `false` will not add the next url in {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`}. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.shouldAddNext = true; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#authenticate | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* This function needs to be configured in order to use visor. | ||
* | ||
* `visorProvider.authentication` defines the authentication function that will be called to provide | ||
* the authentication info at startup. | ||
* It must return a promise that will resolve to an object if the user is authenticated or rejected if | ||
* the user isn't authenticated. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
@@ -332,25 +429,25 @@ * return $http.get("/api/user/me").then(function(res){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.authenticate = function(){ | ||
throw new Error("visorProvider.authenticate must be defined to use visor"); | ||
}; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthenticated | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take when a user tries to access a restricted route but is not authenticated. | ||
* By default it redirect to {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* If {@link visor.visorProvider#shouldAddNext `shouldAddNext`} is enabled, a `next` parameter with the restricted url is added to the login url. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.authenticate = function () { | ||
throw new Error("visorProvider.authenticate must be defined to use visor"); | ||
}; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthenticated | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take when a user tries to access a restricted route but is not authenticated. | ||
* By default it redirect to {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* If {@link visor.visorProvider#shouldAddNext `shouldAddNext`} is enabled, a `next` parameter with the restricted url is added to the login url. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to an error page instead of login | ||
@@ -363,22 +460,22 @@ * visorProvider.doOnNotAuthenticated = function(restrictedUrl,$state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthenticated = ["$location","restrictedUrl",function($location,restrictedUrl){ | ||
$location.url(addNextToUrl(config.loginRoute,$location,restrictedUrl)) | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doAfterManualAuthentication | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take after a user is authenticated using {@link visor.visor#setAuthenticated `visor.setAuthenticated`}. | ||
* By default it redirect to next parameter if exists or to {@link visor.visorProvider#homeRoute `homeRoute`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthenticated = ["$location", "restrictedUrl", function ($location, restrictedUrl) { | ||
$location.url(addNextToUrl(config.loginRoute, $location, restrictedUrl)) | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doAfterManualAuthentication | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take after a user is authenticated using {@link visor.visor#setAuthenticated `visor.setAuthenticated`}. | ||
* By default it redirect to next parameter if exists or to {@link visor.visorProvider#homeRoute `homeRoute`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to a new user welcome page | ||
@@ -389,24 +486,24 @@ * visorProvider.doAfterManualAuthentication = function($state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doAfterManualAuthentication = ["$location",function($location){ | ||
$location.url($location.search().next || config.homeRoute); | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthorized | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action taken when an already authenticated user tries to access a route he is not allowed to view. | ||
* By default it redirect to {@link visor.visorProvider#notAuthorizedRoute `notAuthorizedRoute`}. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.doAfterManualAuthentication = ["$location", function ($location) { | ||
$location.url($location.search().next || config.homeRoute); | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthorized | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action taken when an already authenticated user tries to access a route he is not allowed to view. | ||
* By default it redirect to {@link visor.visorProvider#notAuthorizedRoute `notAuthorizedRoute`}. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to an error page with the restricted url message | ||
@@ -419,217 +516,235 @@ * visorProvider.doOnNotAuthorized = function(restrictedUrl,$state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthorized =["$location",function($location){ | ||
$location.url(config.notAuthorizedRoute) | ||
}]; | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthorized = ["$location", function ($location) { | ||
$location.url(config.notAuthorizedRoute) | ||
}]; | ||
/** | ||
* @ngdoc service | ||
* @name visor.visor | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visor` is an authentication and authorization service to be used alongside ngRoute or ui-router. | ||
* | ||
* It handles authentication while {@link visor.permissions.visorPermissions `visorPermissions`} handles routing and | ||
* restrciting access. | ||
* | ||
* To use first define how visor is to authenticate by setting `visor.authenticate`, and then add | ||
* restriction functions to routes/states. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
* return $http.get("/api/user/me").then(function(res){ | ||
* return res.data; | ||
* }) | ||
* }; | ||
* $stateProvider.state("private",{ | ||
* restrict: function(user){ return user && user.can_see_private;} | ||
* }) | ||
* } | ||
* </pre> | ||
*/ | ||
this.$get = ["$injector","$q","$rootScope","$location","visorPermissions",function($injector,$q,$rootScope,$location,visorPermissions){ | ||
// keeps the original auth promise so we won't call authenticate twice. | ||
var _authenticationPromise = false; | ||
function onAuthenticationSuccess(authData) { | ||
Visor.authData =authData; | ||
visorPermissions.invokeParameters = [Visor.authData]; | ||
} | ||
function onAuthenticationFailed(){ | ||
Visor.authData = undefined; | ||
visorPermissions.invokeParameters = []; | ||
} | ||
var Visor = { | ||
/** | ||
* | ||
* Authenticate with visor. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {boolean} retry If true, will force reauthentication. Otherwise, the second call to authenticate will | ||
* return the result of the previous authentication call. | ||
* | ||
* @returns {promise} Promise that will always resolve as true, with the value returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* If {@link visor.visorProvider#authenticate `visorProvider.authenticate`} failed, the promise will resolve with `undefined`. | ||
*/ | ||
authenticate:function(retry){ | ||
if (_authenticationPromise && !retry) { | ||
return _authenticationPromise; | ||
} | ||
var deferred = $q.defer(); | ||
_authenticationPromise = deferred.promise; | ||
$injector.invoke(config.authenticate) | ||
.then(onAuthenticationSuccess,onAuthenticationFailed) | ||
['finally'](function(){ | ||
deferred.resolve(Visor.authData) | ||
}); | ||
return deferred.promise; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @ngdoc service | ||
* @name visor.visor | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* Notify `visor` that an authentication was successful. | ||
* | ||
* Typical use is to call this function after a use logs in to the system. | ||
* | ||
* <div class="alert alert-info"> | ||
* **Note**: `authData` should be the identical to the result of the promise returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* </div> | ||
* | ||
* | ||
* @param {Any} authData The authentication data to be used in future restrict functions. | ||
*/ | ||
setAuthenticated: function(authData){ | ||
onAuthenticationSuccess(authData); | ||
_authenticationPromise = $q.when(authData); | ||
$injector.invoke(config.doAfterManualAuthentication,null,{authData:authData}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#isAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* Determine if user was successfuly authenticated. | ||
* `visor` is an authentication and authorization service to be used alongside ngRoute or ui-router. | ||
* | ||
* It handles authentication while {@link visor.permissions.visorPermissions `visorPermissions`} handles routing and | ||
* restrciting access. | ||
* | ||
* @returns {boolean} True if the user was authenticated. False otherwise. | ||
*/ | ||
isAuthenticated: function(){ | ||
return !!Visor.authData; | ||
}, | ||
/** | ||
* To use first define how visor is to authenticate by setting `visor.authenticate`, and then add | ||
* restriction functions to routes/states. | ||
* | ||
* Notify visor that a use tried to access a url that is restricted to it. | ||
* @example | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {string} restrictedUrl The url that the user was restricted access to. | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
* return $http.get("/api/user/me").then(function(res){ | ||
* return res.data; | ||
* }) | ||
* }; | ||
* $stateProvider.state("private",{ | ||
* restrict: function(user){ return user && user.can_see_private;} | ||
* }) | ||
* } | ||
* </pre> | ||
*/ | ||
onNotAllowed: function(restrictedUrl){ | ||
if (Visor.isAuthenticated()) { | ||
$injector.invoke(config.doOnNotAuthorized,null,{restrictedUrl:restrictedUrl}); | ||
} else { | ||
$injector.invoke(config.doOnNotAuthenticated,null,{restrictedUrl:restrictedUrl}); | ||
this.$get = ["$injector", "$q", "$rootScope", "$location", "visorPermissions", function ($injector, $q, $rootScope, $location, visorPermissions) { | ||
// keeps the original auth promise so we won't call authenticate twice. | ||
var _authenticationPromise = false; | ||
function onAuthenticationSuccess(authData) { | ||
Visor.authData = authData; | ||
visorPermissions.invokeParameters = [Visor.authData]; | ||
visorPermissions.clearPermissionCache(); | ||
} | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setUnauthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that a user is no longer authenticated. | ||
* | ||
* Typical use is to call this function after a user logs out of the system. | ||
*/ | ||
setUnauthenticated: function(){ | ||
onAuthenticationFailed() | ||
}, | ||
config: config | ||
}; | ||
return Visor; | ||
}] | ||
}]) | ||
.run(["visor","delayLocationChange",function(visor,delayLocationChange){ | ||
if (visor.config.authenticateOnStartup) { | ||
delayLocationChange(visor.authenticate()) | ||
} | ||
}]) | ||
.config(["visorPermissionsProvider",function(visorPermissionsProvider){ | ||
visorPermissionsProvider.doBeforeFirstCheck.push(["visor",function(Visor){ | ||
return Visor.authenticate(); | ||
}]); | ||
visorPermissionsProvider.onNotAllowed = ["visor","restrictedUrl",function(Visor,restrictedUrl){ | ||
Visor.onNotAllowed(restrictedUrl); | ||
}] | ||
}]) | ||
function onAuthenticationFailed() { | ||
Visor.authData = undefined; | ||
visorPermissions.invokeParameters = []; | ||
visorPermissions.clearPermissionCache(); | ||
} | ||
var Visor = { | ||
/** | ||
* | ||
* Authenticate with visor. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {boolean} retry If true, will force reauthentication. Otherwise, the second call to authenticate will | ||
* return the result of the previous authentication call. | ||
* | ||
* @returns {promise} Promise that will always resolve as true, with the value returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* If {@link visor.visorProvider#authenticate `visorProvider.authenticate`} failed, the promise will resolve with `undefined`. | ||
*/ | ||
authenticate: function (retry) { | ||
if (_authenticationPromise && !retry) { | ||
return _authenticationPromise; | ||
} | ||
var deferred = $q.defer(); | ||
_authenticationPromise = deferred.promise; | ||
$injector.invoke(config.authenticate) | ||
.then(onAuthenticationSuccess, onAuthenticationFailed) | ||
['finally'](function () { | ||
deferred.resolve(Visor.authData) | ||
}); | ||
return deferred.promise; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that an authentication was successful. | ||
* | ||
* Typical use is to call this function after a use logs in to the system. | ||
* | ||
* <div class="alert alert-info"> | ||
* **Note**: `authData` should be the identical to the result of the promise returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* </div> | ||
* | ||
* | ||
* @param {Any} authData The authentication data to be used in future restrict functions. | ||
*/ | ||
setAuthenticated: function (authData) { | ||
onAuthenticationSuccess(authData); | ||
_authenticationPromise = $q.when(authData); | ||
$injector.invoke(config.doAfterManualAuthentication, null, {authData: authData}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#isAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* Determine if user was successfuly authenticated. | ||
* | ||
* | ||
* @returns {boolean} True if the user was authenticated. False otherwise. | ||
*/ | ||
isAuthenticated: function () { | ||
return !!Visor.authData; | ||
}, | ||
/** | ||
* | ||
* Notify visor that a use tried to access a url that is restricted to it. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {string} restrictedUrl The url that the user was restricted access to. | ||
* | ||
*/ | ||
onNotAllowed: function (restrictedUrl) { | ||
if (Visor.isAuthenticated()) { | ||
$injector.invoke(config.doOnNotAuthorized, null, {restrictedUrl: restrictedUrl}); | ||
} else { | ||
$injector.invoke(config.doOnNotAuthenticated, null, {restrictedUrl: restrictedUrl}); | ||
} | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setUnauthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that a user is no longer authenticated. | ||
* | ||
* Typical use is to call this function after a user logs out of the system. | ||
*/ | ||
setUnauthenticated: function () { | ||
onAuthenticationFailed() | ||
}, | ||
config: config | ||
}; | ||
return Visor; | ||
}] | ||
}]) | ||
.run(["visor", "delayLocationChange", function (visor, delayLocationChange) { | ||
if (visor.config.authenticateOnStartup) { | ||
delayLocationChange(visor.authenticate()) | ||
} | ||
}]) | ||
.config(["visorPermissionsProvider", function (visorPermissionsProvider) { | ||
visorPermissionsProvider.doBeforeFirstCheck.push(["visor", function (Visor) { | ||
return Visor.authenticate(); | ||
}]); | ||
visorPermissionsProvider.onNotAllowed = ["visor", "restrictedUrl", function (Visor, restrictedUrl) { | ||
Visor.onNotAllowed(restrictedUrl); | ||
}] | ||
}]) | ||
})(); | ||
(function(){ | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ngRoute | ||
* @description | ||
* | ||
* # Visor.ngRoute | ||
* | ||
* `Visor.ngRoute` automatically add supports for permissions in ngRoute, if ngRoute exists. | ||
* | ||
*/ | ||
angular.module('visor.ngRoute',['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions','$injector',function($rootScope, visorPermissions,$injector){ | ||
var ngRouteModuleExists = false; | ||
try { | ||
$injector.get("$route"); | ||
ngRouteModuleExists = true; | ||
}catch (e){} | ||
if (ngRouteModuleExists) { | ||
$rootScope.$on('$routeChangeStart', function(e,next){ | ||
next.resolve = next.resolve || {}; | ||
visorPermissions.onRouteChange(next,function delayChange(promise){ | ||
next.resolve._visorDelay = function(){return promise;}; | ||
}); | ||
}); | ||
} | ||
}]) | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ngRoute | ||
* @description | ||
* | ||
* # Visor.ngRoute | ||
* | ||
* `Visor.ngRoute` automatically add supports for permissions in ngRoute, if ngRoute exists. | ||
* | ||
*/ | ||
angular.module('visor.ngRoute', ['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions', '$injector', function ($rootScope, visorPermissions, $injector) { | ||
var ngRouteModuleExists = false; | ||
var $route = null; | ||
try { | ||
$route = $injector.get("$route"); | ||
ngRouteModuleExists = true; | ||
} catch (e) { | ||
} | ||
if (ngRouteModuleExists) { | ||
visorPermissions.getRoute = function (routeId) { | ||
for (var path in $route.routes) { | ||
var route = $route.routes[path]; | ||
if (route.regexp.exec(routeId)) { | ||
return route; | ||
} | ||
} | ||
return null; | ||
}; | ||
$rootScope.$on('$routeChangeStart', function (e, next) { | ||
next.resolve = next.resolve || {}; | ||
visorPermissions.onRouteChange(next, function delayChange(promise) { | ||
next.resolve._visorDelay = function () { | ||
return promise; | ||
}; | ||
}); | ||
}); | ||
} | ||
}]) | ||
})(); | ||
(function(){ | ||
/** | ||
* @ngdoc overview | ||
* @name visor.permissions | ||
* @description | ||
* | ||
* # Visor.Permissions | ||
* | ||
* `Visor.Permissions` provides support for handling permissions and restricting access to routes based | ||
* on those restrictions. | ||
* | ||
* | ||
* See {@link visor.permissions.visorPermissions `visorPermissions service`} for usage. | ||
*/ | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.permissions | ||
* @description | ||
* | ||
* # Visor.Permissions | ||
* | ||
* `Visor.Permissions` provides support for handling permissions and restricting access to routes based | ||
* on those restrictions. | ||
* | ||
* | ||
* See {@link visor.permissions.visorPermissions `visorPermissions service`} for usage. | ||
*/ | ||
angular.module("visor.permissions",[]) | ||
angular.module("visor.permissions", []) | ||
@@ -652,38 +767,38 @@ /** | ||
*/ | ||
.provider("visorPermissions",[function(){ | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* A function that determines how permissions should be resolved from a route object. | ||
* It receives the `next` route object as the only parameter and must return a `permission function`, | ||
* or an Array of `permission functions`. | ||
* | ||
* A route object is an object that is sent to | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange }. | ||
* This configuration should be set by the same plugin that calls | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange } to guarantee compatibility. | ||
* | ||
* A `permission function` is a function that receives {@link visor.permissions.VisorPermissions.invokeParameters} | ||
* and returns a boolean indicating whether a route change should occur (I.E. the user has permission to access | ||
* the route) | ||
* | ||
* Default: a function that returns the permission function that is in the `next` route object's `restrict` | ||
* attribute (if any). | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#getPermissionsFromNext} | ||
* @example | ||
* | ||
* <pre> | ||
* // a plugin module that will allow all paths to go through | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
.provider("visorPermissions", [function () { | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* A function that determines how permissions should be resolved from a route object. | ||
* It receives the `next` route object as the only parameter and must return a `permission function`, | ||
* or an Array of `permission functions`. | ||
* | ||
* A route object is an object that is sent to | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange }. | ||
* This configuration should be set by the same plugin that calls | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange } to guarantee compatibility. | ||
* | ||
* A `permission function` is a function that receives {@link visor.permissions.VisorPermissions.invokeParameters} | ||
* and returns a boolean indicating whether a route change should occur (I.E. the user has permission to access | ||
* the route) | ||
* | ||
* Default: a function that returns the permission function that is in the `next` route object's `restrict` | ||
* attribute (if any). | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#getPermissionsFromNext} | ||
* @example | ||
* | ||
* <pre> | ||
* // a plugin module that will allow all paths to go through | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* visorPermissionsProvider.getPermissionsFromNext = function(next){ | ||
@@ -695,26 +810,26 @@ * return function(){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.getPermissionsFromNext = function(next){ | ||
return next.restrict? [next.restrict] : []; | ||
}; | ||
* </pre> | ||
*/ | ||
config.getPermissionsFromNext = function (next) { | ||
return next.restrict ? [next.restrict] : []; | ||
}; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#doBeforeFirstCheck | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* | ||
* A list of functions to run before the first permission check is performed (I.E. the first time a route that | ||
* requires permissions is navigated to). | ||
* These functions must return a promise. | ||
* | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#doBeforeFirstCheck | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* | ||
* A list of functions to run before the first permission check is performed (I.E. the first time a route that | ||
* requires permissions is navigated to). | ||
* These functions must return a promise. | ||
* | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* visorPermissionsProvider.doBeforeFirstCheck.push(["$http",function($http){ | ||
@@ -724,191 +839,306 @@ * return $http.get("/do/something"); | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doBeforeFirstCheck = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#onNotAllowed | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function to call when a permission failed to validate. | ||
* | ||
* The function is injected, with local `restrictedUrl` containing the url navigated to. | ||
* | ||
*/ | ||
config.onNotAllowed = function(){}; | ||
* </pre> | ||
*/ | ||
config.doBeforeFirstCheck = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#onNotAllowed | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function to call when a permission failed to validate. | ||
* | ||
* The function is injected, with local `restrictedUrl` containing the url navigated to. | ||
* | ||
*/ | ||
config.onNotAllowed = function () { | ||
}; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* a list of values to send to each `permission function` to be used to determine if a route is allowed. | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#invokeParameters} | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* a list of values to send to each `permission function` to be used to determine if a route is allowed. | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#invokeParameters} | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* var userInfo = {username:"theUser",isAdmin:false}; | ||
* visorPermissionsProvider.invokeParameters.push(userInfo); | ||
* }); | ||
* </pre> | ||
*/ | ||
config.invokeParameters = []; | ||
var finishedBeforeCheck = false; | ||
* </pre> | ||
*/ | ||
config.invokeParameters = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getRoute | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function that transforms a routeId to a route object that can be used in getPermissionsFromNext | ||
* | ||
*/ | ||
config.getRoute = function (routeId) { | ||
throw new Error("method not implemented"); | ||
} | ||
var finishedBeforeCheck = false; | ||
/** | ||
* @ngdoc service | ||
* @name visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* `visorPermissions` checks for permissions and notifies when a routes that isn't allowed is requested. | ||
* | ||
* In order to work, routing module plugins (such as the provided {@link visor.ngRoute visor.ngRoute} and | ||
* {@link visor.ui-router visor.ui-router} must configure `visorPermissions` and call | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange} when a route has changed. | ||
* | ||
*/ | ||
this.$get = ["$q","$injector","$location",function($q,$injector,$location){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* `visorPermissions` checks for permissions and notifies when a routes that isn't allowed is requested. | ||
* | ||
* In order to work, routing module plugins (such as the provided {@link visor.ngRoute visor.ngRoute} and | ||
* {@link visor.ui-router visor.ui-router} must configure `visorPermissions` and call | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange} when a route has changed. | ||
* | ||
*/ | ||
this.$get = ["$q", "$injector", "$location", function ($q, $injector, $location) { | ||
function handlePermission(next,permissions){ | ||
if (!angular.isArray(permissions)) { | ||
permissions = [permissions]; | ||
function checkPermissions(permissions) { | ||
if (!permissions || permissions.length === 0) { | ||
return true; | ||
} | ||
if (!angular.isArray(permissions)) { | ||
permissions = [permissions]; | ||
} | ||
var isAllowed = true; | ||
permissions.forEach(function (permission) { | ||
isAllowed = isAllowed && permission.apply(null, VisorPermissions.invokeParameters); | ||
}); | ||
return isAllowed; | ||
} | ||
var isAllowed = true; | ||
permissions.forEach(function(permission){ | ||
isAllowed = isAllowed && permission.apply(null,VisorPermissions.invokeParameters); | ||
}); | ||
if (isAllowed) { | ||
return true; | ||
} else { | ||
VisorPermissions.invokeNotAllowed(config.onNotAllowed); | ||
return false; | ||
function handlePermission(next, permissions) { | ||
var isAllowed = checkPermissions(permissions); | ||
if (isAllowed) { | ||
return true; | ||
} else { | ||
VisorPermissions.invokeNotAllowed(config.onNotAllowed); | ||
return false; | ||
} | ||
} | ||
} | ||
var VisorPermissions = { | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#onRouteChange | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be called by routing module plugins | ||
* </div> | ||
* | ||
* A function to be called when a route changes, triggers the route permission checks. | ||
* | ||
* @param {*} next route object to be sent to `permission functions`. | ||
* | ||
* @param {function} delayChange a function to be called if visorPermissions requires that the route | ||
* change be delayed. in such case the delayChange function will be called with a promise that will be | ||
* resolved or rejected depending on whether the route is allowed. | ||
* | ||
* @returns {Any} true if next is allowed, false if not allowed. a string containing "delayed" if | ||
* the check is delayed. | ||
*/ | ||
onRouteChange: function(next,delayChange){ | ||
var permissions = VisorPermissions.getPermissionsFromNext(next); | ||
if (!permissions || permissions.length == 0) { | ||
return true; // don't do beforeChecks without permissions | ||
var onCacheClearListeners = []; | ||
var cachedRoutes = {}; | ||
var VisorPermissions = { | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#onRouteChange | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be called by routing module plugins | ||
* </div> | ||
* | ||
* A function to be called when a route changes, triggers the route permission checks. | ||
* | ||
* @param {*} next route object to be sent to `permission functions`. | ||
* | ||
* @param {function} delayChange a function to be called if visorPermissions requires that the route | ||
* change be delayed. in such case the delayChange function will be called with a promise that will be | ||
* resolved or rejected depending on whether the route is allowed. | ||
* | ||
* @returns {Any} true if next is allowed, false if not allowed. a string containing "delayed" if | ||
* the check is delayed. | ||
*/ | ||
onRouteChange: function (next, delayChange) { | ||
var permissions = VisorPermissions.getPermissionsFromNext(next); | ||
if (!permissions || permissions.length == 0) { | ||
return true; // don't do beforeChecks without permissions | ||
} | ||
if (!finishedBeforeCheck) { | ||
var waitForMe = $q.defer(); | ||
delayChange(waitForMe.promise); | ||
$q.all(config.doBeforeFirstCheck.map(function (cb) { | ||
return $injector.invoke(cb) | ||
})) | ||
['finally'](function () { | ||
finishedBeforeCheck = true; | ||
if (handlePermission(next, permissions)) { | ||
waitForMe.resolve(true); | ||
} else { | ||
waitForMe.reject(false); | ||
} | ||
}); | ||
return "delayed"; | ||
} else { | ||
return handlePermission(next, permissions) | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
getPermissionsFromNext: config.getPermissionsFromNext, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#checkPermissionsForRoute | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* A function to check if a route is currently allowed or restricted | ||
* | ||
* Heavily uses caching | ||
* | ||
* @param {*} routeId an identifier for a route (depending on the routing module, could be a string, | ||
* regex or some kind of object) | ||
* | ||
* @returns {Boolean|undefined} true if route is allowed | ||
*/ | ||
checkPermissionsForRoute: function (routeId) { | ||
var result = cachedRoutes[routeId]; | ||
if (result !== undefined) { | ||
return result; | ||
} | ||
var route = VisorPermissions.getRoute(routeId); | ||
if (!route) { | ||
return undefined; | ||
} | ||
var permissions = VisorPermissions.getPermissionsFromNext(route); | ||
result = checkPermissions(permissions); | ||
cachedRoutes[routeId] = result; | ||
return result; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#clearPermissionCache | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* Clears the cache used by checkPermissionsForRoute - should be called when | ||
* the permission context changes (I.E. after authentication) | ||
*/ | ||
clearPermissionCache: function () { | ||
cachedRoutes = {} | ||
onCacheClearListeners.forEach(function (handler) { | ||
handler && handler(); | ||
}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#notifyOnCacheClear | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* Notify handler when the permission cache used by checkPermissionsForRoute is cleared | ||
* | ||
* @param {function} handler the handler function to call | ||
* | ||
* @returns {function} a dereigster function | ||
*/ | ||
notifyOnCacheClear: function (handler) { | ||
onCacheClearListeners.push(handler); | ||
return function () { | ||
var i = onCacheClearListeners.indexOf(handler); | ||
if (i != -1) { | ||
onCacheClearListeners.splice(i, 1); | ||
} | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getRoute | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getRoute getRoute}. | ||
*/ | ||
getRoute: config.getRoute, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.invokeParameters#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
invokeParameters: config.invokeParameters, | ||
invokeNotAllowed: function (notAllowedFn) { | ||
$injector.invoke(notAllowedFn, null, {restrictedUrl: $location.url()}) | ||
} | ||
if (!finishedBeforeCheck) { | ||
var waitForMe = $q.defer(); | ||
delayChange(waitForMe.promise); | ||
$q.all(config.doBeforeFirstCheck.map(function(cb){ | ||
return $injector.invoke(cb) | ||
})) | ||
['finally'](function(){ | ||
finishedBeforeCheck = true; | ||
if (handlePermission(next,permissions)) { | ||
waitForMe.resolve(true); | ||
}; | ||
return VisorPermissions; | ||
}] | ||
}]) | ||
})(); | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ui-router | ||
* @description | ||
* | ||
* # Visor.ui-router | ||
* | ||
* `Visor.ui-router` automatically add supports for permissions in ui-router, if ui-router exists. | ||
* | ||
*/ | ||
angular.module('visor.ui-router', ['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions', '$injector', '$timeout', '$location', function ($rootScope, visorPermissions, $injector, $timeout, $location) { | ||
var uiModuleExists = false; | ||
try { | ||
$injector.get('$state'); | ||
uiModuleExists = true; | ||
} catch (e) { | ||
} | ||
if (uiModuleExists) { | ||
$injector.invoke(['$state', function ($state) { | ||
// we need to check parent states for permissions as well | ||
visorPermissions.getPermissionsFromNext = function (next) { | ||
var perms = []; | ||
while (next) { | ||
if (next.restrict) perms.unshift(next.restrict); | ||
if (next.parent) { | ||
next = $state.get(next.parent) | ||
} else if (next.name.indexOf('.') > 0) { | ||
var chain = next.name.split('.'); | ||
chain.pop(); //remove the leftmost | ||
var parent = chain.join('.'); | ||
next = $state.get(parent); | ||
} else { | ||
waitForMe.reject(false); | ||
next = null; | ||
} | ||
}); | ||
return "delayed"; | ||
} else { | ||
return handlePermission(next,permissions) | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
getPermissionsFromNext: config.getPermissionsFromNext, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.invokeParameters#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
invokeParameters:config.invokeParameters, | ||
invokeNotAllowed: function(notAllowedFn){$injector.invoke(notAllowedFn,null,{restrictedUrl:$location.url()})} | ||
}; | ||
return VisorPermissions; | ||
}] | ||
}]) | ||
})(); | ||
(function(){ | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ui-router | ||
* @description | ||
* | ||
* # Visor.ui-router | ||
* | ||
* `Visor.ui-router` automatically add supports for permissions in ui-router, if ui-router exists. | ||
* | ||
*/ | ||
angular.module('visor.ui-router',['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions','$injector','$timeout','$location',function($rootScope, visorPermissions,$injector,$timeout,$location){ | ||
var uiModuleExists = false; | ||
try { | ||
$injector.get("$state"); | ||
uiModuleExists = true; | ||
}catch (e){} | ||
if (uiModuleExists) { | ||
$injector.invoke(["$state",function($state){ | ||
// we need to check parent states for permissions as well | ||
visorPermissions.getPermissionsFromNext = function(next){ | ||
var perms = []; | ||
while(next) { | ||
if (next.restrict) perms.unshift(next.restrict); | ||
if (next.parent) { | ||
next = $state.get(next.parent) | ||
} else if(next.name.indexOf(".") >0) { | ||
next = $state.get(next.name.replace(/(.*\.)?([^.]+)\.[^.]*$/,"$2")) | ||
} else { | ||
next = null; | ||
} | ||
} | ||
return perms; | ||
} | ||
var $urlRouter = $injector.get("$urlRouter"); | ||
} | ||
return perms; | ||
}; | ||
var $urlRouter = $injector.get('$urlRouter'); | ||
var toUrl = null; | ||
var bypass = false; | ||
$rootScope.$on('$stateChangeStart', function(e,toState,toParams){ | ||
$rootScope.$on('$stateChangeStart', function (e, toState, toParams) { | ||
if (bypass) { | ||
@@ -918,25 +1148,28 @@ bypass = false; | ||
} | ||
toUrl = $state.href(toState,toParams).replace(/^#/,''); | ||
var shouldContinue = visorPermissions.onRouteChange(toState,function delayChange(promise){ | ||
promise.then(function(){ | ||
bypass= true; | ||
$state.go(toState,toParams); | ||
toUrl = $state.href(toState, toParams).replace(/^#/, ''); | ||
var shouldContinue = visorPermissions.onRouteChange(toState, function delayChange(promise) { | ||
promise.then(function () { | ||
bypass = true; | ||
$state.go(toState, toParams); | ||
}) | ||
}); | ||
if (!shouldContinue || shouldContinue === "delayed") { | ||
if (!shouldContinue || shouldContinue === 'delayed') { | ||
e.preventDefault(); | ||
} | ||
}); | ||
visorPermissions.invokeNotAllowed = function(notAllowed){ | ||
visorPermissions.invokeNotAllowed = function (notAllowed) { | ||
//timeout is required because when using preventDefault on $stateChangeStart, the url is | ||
//reverted to it's original location, and no change at this time will override this. | ||
$timeout(function(){ | ||
$injector.invoke(notAllowed,null,{restrictedUrl:toUrl}) | ||
},0); | ||
$timeout(function () { | ||
$injector.invoke(notAllowed, null, {restrictedUrl: toUrl}) | ||
}, 0); | ||
} | ||
}]); | ||
visorPermissions.getRoute = function (routeId) { | ||
return $state.get(routeId); | ||
}; | ||
}]); | ||
} | ||
}]) | ||
} | ||
}]) | ||
})();})(window, window.angular); |
/**visor | ||
* Angular authentication and authorization library | ||
* @version v0.0.9 | ||
* @version v0.1.0 | ||
* @link https://github.com/illniyar/visor.git | ||
@@ -8,2 +8,2 @@ * @license MIT License, http://www.opensource.org/licenses/MIT | ||
"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="visor"),function(a,b,c){!function(){"use strict";b.module("delayLocationChange",[]).service("delayLocationChange",["$rootScope","$q","$timeout","$location","$injector",function(a,b,c,d,e){function f(){m--,o&&0>=m&&g()}function g(){d.absUrl()===i?a.$broadcast("$locationChangeSuccess",i,j):d.url(k)}function h(a){m++,a["finally"](f)}var i,j,k,l=function(a){a.then?h(a):o?h(e.invoke(fn)):n.push(a)},m=0,n=[],o=!1,p=a.$on("$locationChangeStart",function(a,b,g){o=!0,k=d.url(),p(),a.preventDefault(),n.forEach(function(a){h(e.invoke(a))}),0!==m||i||(m++,c(f,1)),i=b,j=g});return l}])}(),function(){"use strict";b.module("visor",["visor.permissions","visor.ui-router","visor.ngRoute","delayLocationChange"]).constant("authenticatedOnly",function(a){return!!a}).constant("notForAuthenticated",function(a){return a===c}).provider("visor",[function(){function a(a,c,d){return b.shouldAddNext?a.indexOf("?")>=0?a.replace(/\?/,"?next="+encodeURIComponent(d)+"&"):a+"?next="+encodeURIComponent(d):a}var b=this;b.authenticateOnStartup=!0,b.loginRoute="/login",b.homeRoute="/",b.notAuthorizedRoute="/access_denied",b.shouldAddNext=!0,b.authenticate=function(){throw new Error("visorProvider.authenticate must be defined to use visor")},b.doOnNotAuthenticated=["$location","restrictedUrl",function(c,d){c.url(a(b.loginRoute,c,d))}],b.doAfterManualAuthentication=["$location",function(a){a.url(a.search().next||b.homeRoute)}],b.doOnNotAuthorized=["$location",function(a){a.url(b.notAuthorizedRoute)}],this.$get=["$injector","$q","$rootScope","$location","visorPermissions",function(a,d,e,f,g){function h(a){k.authData=a,g.invokeParameters=[k.authData]}function i(){k.authData=c,g.invokeParameters=[]}var j=!1,k={authenticate:function(c){if(j&&!c)return j;var e=d.defer();return j=e.promise,a.invoke(b.authenticate).then(h,i)["finally"](function(){e.resolve(k.authData)}),e.promise},setAuthenticated:function(c){h(c),j=d.when(c),a.invoke(b.doAfterManualAuthentication,null,{authData:c})},isAuthenticated:function(){return!!k.authData},onNotAllowed:function(c){k.isAuthenticated()?a.invoke(b.doOnNotAuthorized,null,{restrictedUrl:c}):a.invoke(b.doOnNotAuthenticated,null,{restrictedUrl:c})},setUnauthenticated:function(){i()},config:b};return k}]}]).run(["visor","delayLocationChange",function(a,b){a.config.authenticateOnStartup&&b(a.authenticate())}]).config(["visorPermissionsProvider",function(a){a.doBeforeFirstCheck.push(["visor",function(a){return a.authenticate()}]),a.onNotAllowed=["visor","restrictedUrl",function(a,b){a.onNotAllowed(b)}]}])}(),function(){b.module("visor.ngRoute",["visor.permissions"]).run(["$rootScope","visorPermissions","$injector",function(a,b,c){var d=!1;try{c.get("$route"),d=!0}catch(e){}d&&a.$on("$routeChangeStart",function(a,c){c.resolve=c.resolve||{},b.onRouteChange(c,function(a){c.resolve._visorDelay=function(){return a}})})}])}(),function(){b.module("visor.permissions",[]).provider("visorPermissions",[function(){var a=this;a.getPermissionsFromNext=function(a){return a.restrict?[a.restrict]:[]},a.doBeforeFirstCheck=[],a.onNotAllowed=function(){},a.invokeParameters=[];var c=!1;this.$get=["$q","$injector","$location",function(d,e,f){function g(c,d){b.isArray(d)||(d=[d]);var e=!0;return d.forEach(function(a){e=e&&a.apply(null,h.invokeParameters)}),e?!0:(h.invokeNotAllowed(a.onNotAllowed),!1)}var h={onRouteChange:function(b,f){var i=h.getPermissionsFromNext(b);if(!i||0==i.length)return!0;if(c)return g(b,i);var j=d.defer();return f(j.promise),d.all(a.doBeforeFirstCheck.map(function(a){return e.invoke(a)}))["finally"](function(){c=!0,g(b,i)?j.resolve(!0):j.reject(!1)}),"delayed"},getPermissionsFromNext:a.getPermissionsFromNext,invokeParameters:a.invokeParameters,invokeNotAllowed:function(a){e.invoke(a,null,{restrictedUrl:f.url()})}};return h}]}])}(),function(){b.module("visor.ui-router",["visor.permissions"]).run(["$rootScope","visorPermissions","$injector","$timeout","$location",function(a,b,c,d,e){var f=!1;try{c.get("$state"),f=!0}catch(g){}f&&c.invoke(["$state",function(e){b.getPermissionsFromNext=function(a){for(var b=[];a;)a.restrict&&b.unshift(a.restrict),a=a.parent?e.get(a.parent):a.name.indexOf(".")>0?e.get(a.name.replace(/(.*\.)?([^.]+)\.[^.]*$/,"$2")):null;return b};var f=(c.get("$urlRouter"),null),g=!1;a.$on("$stateChangeStart",function(a,c,d){if(g)return void(g=!1);f=e.href(c,d).replace(/^#/,"");var h=b.onRouteChange(c,function(a){a.then(function(){g=!0,e.go(c,d)})});h&&"delayed"!==h||a.preventDefault()}),b.invokeNotAllowed=function(a){d(function(){c.invoke(a,null,{restrictedUrl:f})},0)}}])}])}()}(window,window.angular); | ||
"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="visor"),function(a,b,c){!function(){"use strict";b.module("delayLocationChange",[]).service("delayLocationChange",["$rootScope","$q","$timeout","$location","$injector",function(a,b,c,d,e){function f(){m--,o&&0>=m&&g()}function g(){d.absUrl()===i?a.$broadcast("$locationChangeSuccess",i,j):d.url(k)}function h(a){m++,a["finally"](f)}var i,j,k,l=function(a){a.then?h(a):o?h(e.invoke(fn)):n.push(a)},m=0,n=[],o=!1,p=a.$on("$locationChangeStart",function(a,b,g){o=!0,k=d.url(),p(),a.preventDefault(),n.forEach(function(a){h(e.invoke(a))}),0!==m||i||(m++,c(f,1)),i=b,j=g});return l}])}(),function(){"use strict";b.module("visor.allowed",["visor.permissions"]).directive("showIfAllowed",["visorPermissions","$animate",function(a,b){return{restrict:"A",link:function(c,d,e){function f(c){var e=a.checkPermissionsForRoute(c);b[e?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})}var g=a.notifyOnCacheClear(function(){f(e.showIfAllowed)});e.$observe("showIfAllowed",f),c.$on("$destroy",g)}}}]).directive("classIfRestricted",["visorPermissions","$animate",function(a,b){return{restrict:"A",link:function(c,d,e){function f(c){var f=a.checkPermissionsForRoute(c);b[f?"removeClass":"addClass"](d,e.restrictedClass||"visor-restricted")}var g=a.notifyOnCacheClear(function(){f(e.classIfRestricted)});e.$observe("classIfRestricted",f),c.$on("$destroy",g)}}}])}(),function(){"use strict";b.module("visor",["visor.permissions","visor.ui-router","visor.ngRoute","delayLocationChange","visor.allowed"]).constant("authenticatedOnly",function(a){return!!a}).constant("notForAuthenticated",function(a){return a===c}).provider("visor",[function(){function a(a,c,d){return b.shouldAddNext?a.indexOf("?")>=0?a.replace(/\?/,"?next="+encodeURIComponent(d)+"&"):a+"?next="+encodeURIComponent(d):a}var b=this;b.authenticateOnStartup=!0,b.loginRoute="/login",b.homeRoute="/",b.notAuthorizedRoute="/access_denied",b.shouldAddNext=!0,b.authenticate=function(){throw new Error("visorProvider.authenticate must be defined to use visor")},b.doOnNotAuthenticated=["$location","restrictedUrl",function(c,d){c.url(a(b.loginRoute,c,d))}],b.doAfterManualAuthentication=["$location",function(a){a.url(a.search().next||b.homeRoute)}],b.doOnNotAuthorized=["$location",function(a){a.url(b.notAuthorizedRoute)}],this.$get=["$injector","$q","$rootScope","$location","visorPermissions",function(a,d,e,f,g){function h(a){k.authData=a,g.invokeParameters=[k.authData],g.clearPermissionCache()}function i(){k.authData=c,g.invokeParameters=[],g.clearPermissionCache()}var j=!1,k={authenticate:function(c){if(j&&!c)return j;var e=d.defer();return j=e.promise,a.invoke(b.authenticate).then(h,i)["finally"](function(){e.resolve(k.authData)}),e.promise},setAuthenticated:function(c){h(c),j=d.when(c),a.invoke(b.doAfterManualAuthentication,null,{authData:c})},isAuthenticated:function(){return!!k.authData},onNotAllowed:function(c){k.isAuthenticated()?a.invoke(b.doOnNotAuthorized,null,{restrictedUrl:c}):a.invoke(b.doOnNotAuthenticated,null,{restrictedUrl:c})},setUnauthenticated:function(){i()},config:b};return k}]}]).run(["visor","delayLocationChange",function(a,b){a.config.authenticateOnStartup&&b(a.authenticate())}]).config(["visorPermissionsProvider",function(a){a.doBeforeFirstCheck.push(["visor",function(a){return a.authenticate()}]),a.onNotAllowed=["visor","restrictedUrl",function(a,b){a.onNotAllowed(b)}]}])}(),function(){b.module("visor.ngRoute",["visor.permissions"]).run(["$rootScope","visorPermissions","$injector",function(a,b,c){var d=!1,e=null;try{e=c.get("$route"),d=!0}catch(f){}d&&(b.getRoute=function(a){for(var b in e.routes){var c=e.routes[b];if(c.regexp.exec(a))return c}return null},a.$on("$routeChangeStart",function(a,c){c.resolve=c.resolve||{},b.onRouteChange(c,function(a){c.resolve._visorDelay=function(){return a}})}))}])}(),function(){b.module("visor.permissions",[]).provider("visorPermissions",[function(){var a=this;a.getPermissionsFromNext=function(a){return a.restrict?[a.restrict]:[]},a.doBeforeFirstCheck=[],a.onNotAllowed=function(){},a.invokeParameters=[],a.getRoute=function(a){throw new Error("method not implemented")};var d=!1;this.$get=["$q","$injector","$location",function(e,f,g){function h(a){if(!a||0===a.length)return!0;b.isArray(a)||(a=[a]);var c=!0;return a.forEach(function(a){c=c&&a.apply(null,l.invokeParameters)}),c}function i(b,c){var d=h(c);return d?!0:(l.invokeNotAllowed(a.onNotAllowed),!1)}var j=[],k={},l={onRouteChange:function(b,c){var g=l.getPermissionsFromNext(b);if(!g||0==g.length)return!0;if(d)return i(b,g);var h=e.defer();return c(h.promise),e.all(a.doBeforeFirstCheck.map(function(a){return f.invoke(a)}))["finally"](function(){d=!0,i(b,g)?h.resolve(!0):h.reject(!1)}),"delayed"},getPermissionsFromNext:a.getPermissionsFromNext,checkPermissionsForRoute:function(a){var b=k[a];if(b!==c)return b;var d=l.getRoute(a);if(!d)return c;var e=l.getPermissionsFromNext(d);return b=h(e),k[a]=b,b},clearPermissionCache:function(){k={},j.forEach(function(a){a&&a()})},notifyOnCacheClear:function(a){return j.push(a),function(){var b=j.indexOf(a);-1!=b&&j.splice(b,1)}},getRoute:a.getRoute,invokeParameters:a.invokeParameters,invokeNotAllowed:function(a){f.invoke(a,null,{restrictedUrl:g.url()})}};return l}]}])}(),function(){b.module("visor.ui-router",["visor.permissions"]).run(["$rootScope","visorPermissions","$injector","$timeout","$location",function(a,b,c,d,e){var f=!1;try{c.get("$state"),f=!0}catch(g){}f&&c.invoke(["$state",function(e){b.getPermissionsFromNext=function(a){for(var b=[];a;)if(a.restrict&&b.unshift(a.restrict),a.parent)a=e.get(a.parent);else if(a.name.indexOf(".")>0){var c=a.name.split(".");c.pop();var d=c.join(".");a=e.get(d)}else a=null;return b};var f=(c.get("$urlRouter"),null),g=!1;a.$on("$stateChangeStart",function(a,c,d){if(g)return void(g=!1);f=e.href(c,d).replace(/^#/,"");var h=b.onRouteChange(c,function(a){a.then(function(){g=!0,e.go(c,d)})});h&&"delayed"!==h||a.preventDefault()}),b.invokeNotAllowed=function(a){d(function(){c.invoke(a,null,{restrictedUrl:f})},0)},b.getRoute=function(a){return e.get(a)}}])}])}()}(window,window.angular); |
@@ -6,40 +6,41 @@ /** | ||
*/ | ||
(function(window, angular, undefined) {'use strict'; | ||
(function (window, angular, undefined) { | ||
'use strict'; | ||
/** | ||
* @ngdoc module | ||
* @name ngCookies | ||
* @description | ||
* | ||
* # ngCookies | ||
* | ||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. | ||
* | ||
* | ||
* <div doc-module-components="ngCookies"></div> | ||
* | ||
* See {@link ngCookies.$cookies `$cookies`} and | ||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage. | ||
*/ | ||
/** | ||
* @ngdoc module | ||
* @name ngCookies | ||
* @description | ||
* | ||
* # ngCookies | ||
* | ||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. | ||
* | ||
* | ||
* <div doc-module-components="ngCookies"></div> | ||
* | ||
* See {@link ngCookies.$cookies `$cookies`} and | ||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage. | ||
*/ | ||
angular.module('ngCookies', ['ng']). | ||
/** | ||
* @ngdoc service | ||
* @name $cookies | ||
* | ||
* @description | ||
* Provides read/write access to browser's cookies. | ||
* | ||
* Only a simple Object is exposed and by adding or removing properties to/from this object, new | ||
* cookies are created/deleted at the end of current $eval. | ||
* The object's properties can only be strings. | ||
* | ||
* Requires the {@link ngCookies `ngCookies`} module to be installed. | ||
* | ||
* @example | ||
* | ||
* ```js | ||
* angular.module('cookiesExample', ['ngCookies']) | ||
* .controller('ExampleController', ['$cookies', function($cookies) { | ||
angular.module('ngCookies', ['ng']). | ||
/** | ||
* @ngdoc service | ||
* @name $cookies | ||
* | ||
* @description | ||
* Provides read/write access to browser's cookies. | ||
* | ||
* Only a simple Object is exposed and by adding or removing properties to/from this object, new | ||
* cookies are created/deleted at the end of current $eval. | ||
* The object's properties can only be strings. | ||
* | ||
* Requires the {@link ngCookies `ngCookies`} module to be installed. | ||
* | ||
* @example | ||
* | ||
* ```js | ||
* angular.module('cookiesExample', ['ngCookies']) | ||
* .controller('ExampleController', ['$cookies', function($cookies) { | ||
* // Retrieving a cookie | ||
@@ -50,101 +51,101 @@ * var favoriteCookie = $cookies.myFavorite; | ||
* }]); | ||
* ``` | ||
*/ | ||
factory('$cookies', ['$rootScope', '$browser', function($rootScope, $browser) { | ||
var cookies = {}, | ||
lastCookies = {}, | ||
lastBrowserCookies, | ||
runEval = false, | ||
copy = angular.copy, | ||
isUndefined = angular.isUndefined; | ||
* ``` | ||
*/ | ||
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { | ||
var cookies = {}, | ||
lastCookies = {}, | ||
lastBrowserCookies, | ||
runEval = false, | ||
copy = angular.copy, | ||
isUndefined = angular.isUndefined; | ||
//creates a poller fn that copies all cookies from the $browser to service & inits the service | ||
$browser.addPollFn(function() { | ||
var currentCookies = $browser.cookies(); | ||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl | ||
lastBrowserCookies = currentCookies; | ||
copy(currentCookies, lastCookies); | ||
copy(currentCookies, cookies); | ||
if (runEval) $rootScope.$apply(); | ||
} | ||
})(); | ||
//creates a poller fn that copies all cookies from the $browser to service & inits the service | ||
$browser.addPollFn(function () { | ||
var currentCookies = $browser.cookies(); | ||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl | ||
lastBrowserCookies = currentCookies; | ||
copy(currentCookies, lastCookies); | ||
copy(currentCookies, cookies); | ||
if (runEval) $rootScope.$apply(); | ||
} | ||
})(); | ||
runEval = true; | ||
runEval = true; | ||
//at the end of each eval, push cookies | ||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not | ||
// strings or browser refuses to store some cookies, we update the model in the push fn. | ||
$rootScope.$watch(push); | ||
//at the end of each eval, push cookies | ||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not | ||
// strings or browser refuses to store some cookies, we update the model in the push fn. | ||
$rootScope.$watch(push); | ||
return cookies; | ||
return cookies; | ||
/** | ||
* Pushes all the cookies from the service to the browser and verifies if all cookies were | ||
* stored. | ||
*/ | ||
function push() { | ||
var name, | ||
value, | ||
browserCookies, | ||
updated; | ||
/** | ||
* Pushes all the cookies from the service to the browser and verifies if all cookies were | ||
* stored. | ||
*/ | ||
function push() { | ||
var name, | ||
value, | ||
browserCookies, | ||
updated; | ||
//delete any cookies deleted in $cookies | ||
for (name in lastCookies) { | ||
if (isUndefined(cookies[name])) { | ||
$browser.cookies(name, undefined); | ||
} | ||
} | ||
//delete any cookies deleted in $cookies | ||
for (name in lastCookies) { | ||
if (isUndefined(cookies[name])) { | ||
$browser.cookies(name, undefined); | ||
} | ||
} | ||
//update all cookies updated in $cookies | ||
for (name in cookies) { | ||
value = cookies[name]; | ||
if (!angular.isString(value)) { | ||
value = '' + value; | ||
cookies[name] = value; | ||
} | ||
if (value !== lastCookies[name]) { | ||
$browser.cookies(name, value); | ||
updated = true; | ||
} | ||
} | ||
//update all cookies updated in $cookies | ||
for (name in cookies) { | ||
value = cookies[name]; | ||
if (!angular.isString(value)) { | ||
value = '' + value; | ||
cookies[name] = value; | ||
} | ||
if (value !== lastCookies[name]) { | ||
$browser.cookies(name, value); | ||
updated = true; | ||
} | ||
} | ||
//verify what was actually stored | ||
if (updated) { | ||
updated = false; | ||
browserCookies = $browser.cookies(); | ||
//verify what was actually stored | ||
if (updated) { | ||
updated = false; | ||
browserCookies = $browser.cookies(); | ||
for (name in cookies) { | ||
if (cookies[name] !== browserCookies[name]) { | ||
//delete or reset all cookies that the browser dropped from $cookies | ||
if (isUndefined(browserCookies[name])) { | ||
delete cookies[name]; | ||
} else { | ||
cookies[name] = browserCookies[name]; | ||
} | ||
updated = true; | ||
for (name in cookies) { | ||
if (cookies[name] !== browserCookies[name]) { | ||
//delete or reset all cookies that the browser dropped from $cookies | ||
if (isUndefined(browserCookies[name])) { | ||
delete cookies[name]; | ||
} else { | ||
cookies[name] = browserCookies[name]; | ||
} | ||
updated = true; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}]). | ||
}]). | ||
/** | ||
* @ngdoc service | ||
* @name $cookieStore | ||
* @requires $cookies | ||
* | ||
* @description | ||
* Provides a key-value (string-object) storage, that is backed by session cookies. | ||
* Objects put or retrieved from this storage are automatically serialized or | ||
* deserialized by angular's toJson/fromJson. | ||
* | ||
* Requires the {@link ngCookies `ngCookies`} module to be installed. | ||
* | ||
* @example | ||
* | ||
* ```js | ||
* angular.module('cookieStoreExample', ['ngCookies']) | ||
* .controller('ExampleController', ['$cookieStore', function($cookieStore) { | ||
/** | ||
* @ngdoc service | ||
* @name $cookieStore | ||
* @requires $cookies | ||
* | ||
* @description | ||
* Provides a key-value (string-object) storage, that is backed by session cookies. | ||
* Objects put or retrieved from this storage are automatically serialized or | ||
* deserialized by angular's toJson/fromJson. | ||
* | ||
* Requires the {@link ngCookies `ngCookies`} module to be installed. | ||
* | ||
* @example | ||
* | ||
* ```js | ||
* angular.module('cookieStoreExample', ['ngCookies']) | ||
* .controller('ExampleController', ['$cookieStore', function($cookieStore) { | ||
* // Put cookie | ||
@@ -157,53 +158,53 @@ * $cookieStore.put('myFavorite','oatmeal'); | ||
* }]); | ||
* ``` | ||
*/ | ||
factory('$cookieStore', ['$cookies', function($cookies) { | ||
* ``` | ||
*/ | ||
factory('$cookieStore', ['$cookies', function ($cookies) { | ||
return { | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#get | ||
* | ||
* @description | ||
* Returns the value of given cookie key | ||
* | ||
* @param {string} key Id to use for lookup. | ||
* @returns {Object} Deserialized cookie value. | ||
*/ | ||
get: function(key) { | ||
var value = $cookies[key]; | ||
return value ? angular.fromJson(value) : value; | ||
}, | ||
return { | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#get | ||
* | ||
* @description | ||
* Returns the value of given cookie key | ||
* | ||
* @param {string} key Id to use for lookup. | ||
* @returns {Object} Deserialized cookie value. | ||
*/ | ||
get: function (key) { | ||
var value = $cookies[key]; | ||
return value ? angular.fromJson(value) : value; | ||
}, | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#put | ||
* | ||
* @description | ||
* Sets a value for given cookie key | ||
* | ||
* @param {string} key Id for the `value`. | ||
* @param {Object} value Value to be stored. | ||
*/ | ||
put: function(key, value) { | ||
$cookies[key] = angular.toJson(value); | ||
}, | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#put | ||
* | ||
* @description | ||
* Sets a value for given cookie key | ||
* | ||
* @param {string} key Id for the `value`. | ||
* @param {Object} value Value to be stored. | ||
*/ | ||
put: function (key, value) { | ||
$cookies[key] = angular.toJson(value); | ||
}, | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#remove | ||
* | ||
* @description | ||
* Remove given cookie | ||
* | ||
* @param {string} key Id of the key-value pair to delete. | ||
*/ | ||
remove: function(key) { | ||
delete $cookies[key]; | ||
} | ||
}; | ||
/** | ||
* @ngdoc method | ||
* @name $cookieStore#remove | ||
* | ||
* @description | ||
* Remove given cookie | ||
* | ||
* @param {string} key Id of the key-value pair to delete. | ||
*/ | ||
remove: function (key) { | ||
delete $cookies[key]; | ||
} | ||
}; | ||
}]); | ||
}]); | ||
})(window, window.angular); |
@@ -6,328 +6,329 @@ /** | ||
*/ | ||
(function(window, angular, undefined) {'use strict'; | ||
(function (window, angular, undefined) { | ||
'use strict'; | ||
/** | ||
* @ngdoc module | ||
* @name ngRoute | ||
* @description | ||
* | ||
* # ngRoute | ||
* | ||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps. | ||
* | ||
* ## Example | ||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. | ||
* | ||
* | ||
* <div doc-module-components="ngRoute"></div> | ||
*/ | ||
/* global -ngRouteModule */ | ||
var ngRouteModule = angular.module('ngRoute', ['ng']). | ||
provider('$route', $RouteProvider), | ||
$routeMinErr = angular.$$minErr('ngRoute'); | ||
/** | ||
* @ngdoc module | ||
* @name ngRoute | ||
* @description | ||
* | ||
* # ngRoute | ||
* | ||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps. | ||
* | ||
* ## Example | ||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. | ||
* | ||
* | ||
* <div doc-module-components="ngRoute"></div> | ||
*/ | ||
/* global -ngRouteModule */ | ||
var ngRouteModule = angular.module('ngRoute', ['ng']). | ||
provider('$route', $RouteProvider), | ||
$routeMinErr = angular.$$minErr('ngRoute'); | ||
/** | ||
* @ngdoc provider | ||
* @name $routeProvider | ||
* | ||
* @description | ||
* | ||
* Used for configuring routes. | ||
* | ||
* ## Example | ||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. | ||
* | ||
* ## Dependencies | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
*/ | ||
function $RouteProvider() { | ||
function inherit(parent, extra) { | ||
return angular.extend(Object.create(parent), extra); | ||
} | ||
/** | ||
* @ngdoc provider | ||
* @name $routeProvider | ||
* | ||
* @description | ||
* | ||
* Used for configuring routes. | ||
* | ||
* ## Example | ||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. | ||
* | ||
* ## Dependencies | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
*/ | ||
function $RouteProvider() { | ||
function inherit(parent, extra) { | ||
return angular.extend(Object.create(parent), extra); | ||
} | ||
var routes = {}; | ||
var routes = {}; | ||
/** | ||
* @ngdoc method | ||
* @name $routeProvider#when | ||
* | ||
* @param {string} path Route path (matched against `$location.path`). If `$location.path` | ||
* contains redundant trailing slash or is missing one, the route will still match and the | ||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the | ||
* route definition. | ||
* | ||
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up | ||
* to the next slash are matched and stored in `$routeParams` under the given `name` | ||
* when the route matches. | ||
* * `path` can contain named groups starting with a colon and ending with a star: | ||
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` | ||
* when the route matches. | ||
* * `path` can contain optional named groups with a question mark: e.g.`:name?`. | ||
* | ||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match | ||
* `/color/brown/largecode/code/with/slashes/edit` and extract: | ||
* | ||
* * `color: brown` | ||
* * `largecode: code/with/slashes`. | ||
* | ||
* | ||
* @param {Object} route Mapping information to be assigned to `$route.current` on route | ||
* match. | ||
* | ||
* Object properties: | ||
* | ||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with | ||
* newly created scope or the name of a {@link angular.Module#controller registered | ||
/** | ||
* @ngdoc method | ||
* @name $routeProvider#when | ||
* | ||
* @param {string} path Route path (matched against `$location.path`). If `$location.path` | ||
* contains redundant trailing slash or is missing one, the route will still match and the | ||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the | ||
* route definition. | ||
* | ||
* * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up | ||
* to the next slash are matched and stored in `$routeParams` under the given `name` | ||
* when the route matches. | ||
* * `path` can contain named groups starting with a colon and ending with a star: | ||
* e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` | ||
* when the route matches. | ||
* * `path` can contain optional named groups with a question mark: e.g.`:name?`. | ||
* | ||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match | ||
* `/color/brown/largecode/code/with/slashes/edit` and extract: | ||
* | ||
* * `color: brown` | ||
* * `largecode: code/with/slashes`. | ||
* | ||
* | ||
* @param {Object} route Mapping information to be assigned to `$route.current` on route | ||
* match. | ||
* | ||
* Object properties: | ||
* | ||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with | ||
* newly created scope or the name of a {@link angular.Module#controller registered | ||
* controller} if passed as a string. | ||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be | ||
* published to scope under the `controllerAs` name. | ||
* - `template` – `{string=|function()=}` – html template as a string or a function that | ||
* returns an html template as a string which should be used by {@link | ||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. | ||
* This property takes precedence over `templateUrl`. | ||
* | ||
* If `template` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Array.<Object>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route | ||
* | ||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html | ||
* template that should be used by {@link ngRoute.directive:ngView ngView}. | ||
* | ||
* If `templateUrl` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Array.<Object>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route | ||
* | ||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should | ||
* be injected into the controller. If any of these dependencies are promises, the router | ||
* will wait for them all to be resolved or one to be rejected before the controller is | ||
* instantiated. | ||
* If all the promises are resolved successfully, the values of the resolved promises are | ||
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is | ||
* fired. If any of the promises are rejected the | ||
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object | ||
* is: | ||
* | ||
* - `key` – `{string}`: a name of a dependency to be injected into the controller. | ||
* - `factory` - `{string|function}`: If `string` then it is an alias for a service. | ||
* Otherwise if function, then it is {@link auto.$injector#invoke injected} | ||
* and the return value is treated as the dependency. If the result is a promise, it is | ||
* resolved before its value is injected into the controller. Be aware that | ||
* `ngRoute.$routeParams` will still refer to the previous route within these resolve | ||
* functions. Use `$route.current.params` to access the new route parameters, instead. | ||
* | ||
* - `redirectTo` – {(string|function())=} – value to update | ||
* {@link ng.$location $location} path with and trigger route redirection. | ||
* | ||
* If `redirectTo` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Object.<string>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route templateUrl. | ||
* - `{string}` - current `$location.path()` | ||
* - `{Object}` - current `$location.search()` | ||
* | ||
* The custom `redirectTo` function is expected to return a string which will be used | ||
* to update `$location.path()` and `$location.search()`. | ||
* | ||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` | ||
* or `$location.hash()` changes. | ||
* | ||
* If the option is set to `false` and url in the browser changes, then | ||
* `$routeUpdate` event is broadcasted on the root scope. | ||
* | ||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive | ||
* | ||
* If the option is set to `true`, then the particular route can be matched without being | ||
* case sensitive | ||
* | ||
* @returns {Object} self | ||
* | ||
* @description | ||
* Adds a new route definition to the `$route` service. | ||
*/ | ||
this.when = function(path, route) { | ||
//copy original route object to preserve params inherited from proto chain | ||
var routeCopy = angular.copy(route); | ||
if (angular.isUndefined(routeCopy.reloadOnSearch)) { | ||
routeCopy.reloadOnSearch = true; | ||
} | ||
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { | ||
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; | ||
} | ||
routes[path] = angular.extend( | ||
routeCopy, | ||
path && pathRegExp(path, routeCopy) | ||
); | ||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be | ||
* published to scope under the `controllerAs` name. | ||
* - `template` – `{string=|function()=}` – html template as a string or a function that | ||
* returns an html template as a string which should be used by {@link | ||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. | ||
* This property takes precedence over `templateUrl`. | ||
* | ||
* If `template` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Array.<Object>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route | ||
* | ||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html | ||
* template that should be used by {@link ngRoute.directive:ngView ngView}. | ||
* | ||
* If `templateUrl` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Array.<Object>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route | ||
* | ||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should | ||
* be injected into the controller. If any of these dependencies are promises, the router | ||
* will wait for them all to be resolved or one to be rejected before the controller is | ||
* instantiated. | ||
* If all the promises are resolved successfully, the values of the resolved promises are | ||
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is | ||
* fired. If any of the promises are rejected the | ||
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object | ||
* is: | ||
* | ||
* - `key` – `{string}`: a name of a dependency to be injected into the controller. | ||
* - `factory` - `{string|function}`: If `string` then it is an alias for a service. | ||
* Otherwise if function, then it is {@link auto.$injector#invoke injected} | ||
* and the return value is treated as the dependency. If the result is a promise, it is | ||
* resolved before its value is injected into the controller. Be aware that | ||
* `ngRoute.$routeParams` will still refer to the previous route within these resolve | ||
* functions. Use `$route.current.params` to access the new route parameters, instead. | ||
* | ||
* - `redirectTo` – {(string|function())=} – value to update | ||
* {@link ng.$location $location} path with and trigger route redirection. | ||
* | ||
* If `redirectTo` is a function, it will be called with the following parameters: | ||
* | ||
* - `{Object.<string>}` - route parameters extracted from the current | ||
* `$location.path()` by applying the current route templateUrl. | ||
* - `{string}` - current `$location.path()` | ||
* - `{Object}` - current `$location.search()` | ||
* | ||
* The custom `redirectTo` function is expected to return a string which will be used | ||
* to update `$location.path()` and `$location.search()`. | ||
* | ||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` | ||
* or `$location.hash()` changes. | ||
* | ||
* If the option is set to `false` and url in the browser changes, then | ||
* `$routeUpdate` event is broadcasted on the root scope. | ||
* | ||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive | ||
* | ||
* If the option is set to `true`, then the particular route can be matched without being | ||
* case sensitive | ||
* | ||
* @returns {Object} self | ||
* | ||
* @description | ||
* Adds a new route definition to the `$route` service. | ||
*/ | ||
this.when = function (path, route) { | ||
//copy original route object to preserve params inherited from proto chain | ||
var routeCopy = angular.copy(route); | ||
if (angular.isUndefined(routeCopy.reloadOnSearch)) { | ||
routeCopy.reloadOnSearch = true; | ||
} | ||
if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { | ||
routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; | ||
} | ||
routes[path] = angular.extend( | ||
routeCopy, | ||
path && pathRegExp(path, routeCopy) | ||
); | ||
// create redirection for trailing slashes | ||
if (path) { | ||
var redirectPath = (path[path.length - 1] == '/') | ||
? path.substr(0, path.length - 1) | ||
: path + '/'; | ||
// create redirection for trailing slashes | ||
if (path) { | ||
var redirectPath = (path[path.length - 1] == '/') | ||
? path.substr(0, path.length - 1) | ||
: path + '/'; | ||
routes[redirectPath] = angular.extend( | ||
{redirectTo: path}, | ||
pathRegExp(redirectPath, routeCopy) | ||
); | ||
} | ||
routes[redirectPath] = angular.extend( | ||
{redirectTo: path}, | ||
pathRegExp(redirectPath, routeCopy) | ||
); | ||
} | ||
return this; | ||
}; | ||
return this; | ||
}; | ||
/** | ||
* @ngdoc property | ||
* @name $routeProvider#caseInsensitiveMatch | ||
* @description | ||
* | ||
* A boolean property indicating if routes defined | ||
* using this provider should be matched using a case sensitive | ||
* algorithm. Defaults to `false`. | ||
*/ | ||
this.caseInsensitiveMatch = false; | ||
/** | ||
* @ngdoc property | ||
* @name $routeProvider#caseInsensitiveMatch | ||
* @description | ||
* | ||
* A boolean property indicating if routes defined | ||
* using this provider should be matched using a case sensitive | ||
* algorithm. Defaults to `false`. | ||
*/ | ||
this.caseInsensitiveMatch = false; | ||
/** | ||
* @param path {string} path | ||
* @param opts {Object} options | ||
* @return {?Object} | ||
* | ||
* @description | ||
* Normalizes the given path, returning a regular expression | ||
* and the original path. | ||
* | ||
* Inspired by pathRexp in visionmedia/express/lib/utils.js. | ||
*/ | ||
function pathRegExp(path, opts) { | ||
var insensitive = opts.caseInsensitiveMatch, | ||
ret = { | ||
originalPath: path, | ||
regexp: path | ||
}, | ||
keys = ret.keys = []; | ||
/** | ||
* @param path {string} path | ||
* @param opts {Object} options | ||
* @return {?Object} | ||
* | ||
* @description | ||
* Normalizes the given path, returning a regular expression | ||
* and the original path. | ||
* | ||
* Inspired by pathRexp in visionmedia/express/lib/utils.js. | ||
*/ | ||
function pathRegExp(path, opts) { | ||
var insensitive = opts.caseInsensitiveMatch, | ||
ret = { | ||
originalPath: path, | ||
regexp: path | ||
}, | ||
keys = ret.keys = []; | ||
path = path | ||
.replace(/([().])/g, '\\$1') | ||
.replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { | ||
var optional = option === '?' ? option : null; | ||
var star = option === '*' ? option : null; | ||
keys.push({ name: key, optional: !!optional }); | ||
slash = slash || ''; | ||
return '' | ||
+ (optional ? '' : slash) | ||
+ '(?:' | ||
+ (optional ? slash : '') | ||
+ (star && '(.+?)' || '([^/]+)') | ||
+ (optional || '') | ||
+ ')' | ||
+ (optional || ''); | ||
}) | ||
.replace(/([\/$\*])/g, '\\$1'); | ||
path = path | ||
.replace(/([().])/g, '\\$1') | ||
.replace(/(\/)?:(\w+)([\?\*])?/g, function (_, slash, key, option) { | ||
var optional = option === '?' ? option : null; | ||
var star = option === '*' ? option : null; | ||
keys.push({name: key, optional: !!optional}); | ||
slash = slash || ''; | ||
return '' | ||
+ (optional ? '' : slash) | ||
+ '(?:' | ||
+ (optional ? slash : '') | ||
+ (star && '(.+?)' || '([^/]+)') | ||
+ (optional || '') | ||
+ ')' | ||
+ (optional || ''); | ||
}) | ||
.replace(/([\/$\*])/g, '\\$1'); | ||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); | ||
return ret; | ||
} | ||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); | ||
return ret; | ||
} | ||
/** | ||
* @ngdoc method | ||
* @name $routeProvider#otherwise | ||
* | ||
* @description | ||
* Sets route definition that will be used on route change when no other route definition | ||
* is matched. | ||
* | ||
* @param {Object|string} params Mapping information to be assigned to `$route.current`. | ||
* If called with a string, the value maps to `redirectTo`. | ||
* @returns {Object} self | ||
*/ | ||
this.otherwise = function(params) { | ||
if (typeof params === 'string') { | ||
params = {redirectTo: params}; | ||
} | ||
this.when(null, params); | ||
return this; | ||
}; | ||
/** | ||
* @ngdoc method | ||
* @name $routeProvider#otherwise | ||
* | ||
* @description | ||
* Sets route definition that will be used on route change when no other route definition | ||
* is matched. | ||
* | ||
* @param {Object|string} params Mapping information to be assigned to `$route.current`. | ||
* If called with a string, the value maps to `redirectTo`. | ||
* @returns {Object} self | ||
*/ | ||
this.otherwise = function (params) { | ||
if (typeof params === 'string') { | ||
params = {redirectTo: params}; | ||
} | ||
this.when(null, params); | ||
return this; | ||
}; | ||
this.$get = ['$rootScope', | ||
'$location', | ||
'$routeParams', | ||
'$q', | ||
'$injector', | ||
'$templateRequest', | ||
'$sce', | ||
function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { | ||
this.$get = ['$rootScope', | ||
'$location', | ||
'$routeParams', | ||
'$q', | ||
'$injector', | ||
'$templateRequest', | ||
'$sce', | ||
function ($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { | ||
/** | ||
* @ngdoc service | ||
* @name $route | ||
* @requires $location | ||
* @requires $routeParams | ||
* | ||
* @property {Object} current Reference to the current route definition. | ||
* The route definition contains: | ||
* | ||
* - `controller`: The controller constructor as define in route definition. | ||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for | ||
* controller instantiation. The `locals` contain | ||
* the resolved values of the `resolve` map. Additionally the `locals` also contain: | ||
* | ||
* - `$scope` - The current route scope. | ||
* - `$template` - The current route template HTML. | ||
* | ||
* @property {Object} routes Object with all route configuration Objects as its properties. | ||
* | ||
* @description | ||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials). | ||
* It watches `$location.url()` and tries to map the path to an existing route definition. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. | ||
* | ||
* The `$route` service is typically used in conjunction with the | ||
* {@link ngRoute.directive:ngView `ngView`} directive and the | ||
* {@link ngRoute.$routeParams `$routeParams`} service. | ||
* | ||
* @example | ||
* This example shows how changing the URL hash causes the `$route` to match a route against the | ||
* URL, and the `ngView` pulls in the partial. | ||
* | ||
* <example name="$route-service" module="ngRouteExample" | ||
* deps="angular-route.js" fixBase="true"> | ||
* <file name="index.html"> | ||
* <div ng-controller="MainController"> | ||
* Choose: | ||
* <a href="Book/Moby">Moby</a> | | ||
* <a href="Book/Moby/ch/1">Moby: Ch1</a> | | ||
* <a href="Book/Gatsby">Gatsby</a> | | ||
* <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | | ||
* <a href="Book/Scarlet">Scarlet Letter</a><br/> | ||
* | ||
* <div ng-view></div> | ||
* | ||
* <hr /> | ||
* | ||
* <pre>$location.path() = {{$location.path()}}</pre> | ||
* <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> | ||
* <pre>$route.current.params = {{$route.current.params}}</pre> | ||
* <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> | ||
* <pre>$routeParams = {{$routeParams}}</pre> | ||
* </div> | ||
* </file> | ||
* | ||
* <file name="book.html"> | ||
* controller: {{name}}<br /> | ||
* Book Id: {{params.bookId}}<br /> | ||
* </file> | ||
* | ||
* <file name="chapter.html"> | ||
* controller: {{name}}<br /> | ||
* Book Id: {{params.bookId}}<br /> | ||
* Chapter Id: {{params.chapterId}} | ||
* </file> | ||
* | ||
* <file name="script.js"> | ||
* angular.module('ngRouteExample', ['ngRoute']) | ||
* | ||
* .controller('MainController', function($scope, $route, $routeParams, $location) { | ||
/** | ||
* @ngdoc service | ||
* @name $route | ||
* @requires $location | ||
* @requires $routeParams | ||
* | ||
* @property {Object} current Reference to the current route definition. | ||
* The route definition contains: | ||
* | ||
* - `controller`: The controller constructor as define in route definition. | ||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for | ||
* controller instantiation. The `locals` contain | ||
* the resolved values of the `resolve` map. Additionally the `locals` also contain: | ||
* | ||
* - `$scope` - The current route scope. | ||
* - `$template` - The current route template HTML. | ||
* | ||
* @property {Object} routes Object with all route configuration Objects as its properties. | ||
* | ||
* @description | ||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials). | ||
* It watches `$location.url()` and tries to map the path to an existing route definition. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. | ||
* | ||
* The `$route` service is typically used in conjunction with the | ||
* {@link ngRoute.directive:ngView `ngView`} directive and the | ||
* {@link ngRoute.$routeParams `$routeParams`} service. | ||
* | ||
* @example | ||
* This example shows how changing the URL hash causes the `$route` to match a route against the | ||
* URL, and the `ngView` pulls in the partial. | ||
* | ||
* <example name="$route-service" module="ngRouteExample" | ||
* deps="angular-route.js" fixBase="true"> | ||
* <file name="index.html"> | ||
* <div ng-controller="MainController"> | ||
* Choose: | ||
* <a href="Book/Moby">Moby</a> | | ||
* <a href="Book/Moby/ch/1">Moby: Ch1</a> | | ||
* <a href="Book/Gatsby">Gatsby</a> | | ||
* <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | | ||
* <a href="Book/Scarlet">Scarlet Letter</a><br/> | ||
* | ||
* <div ng-view></div> | ||
* | ||
* <hr /> | ||
* | ||
* <pre>$location.path() = {{$location.path()}}</pre> | ||
* <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> | ||
* <pre>$route.current.params = {{$route.current.params}}</pre> | ||
* <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> | ||
* <pre>$routeParams = {{$routeParams}}</pre> | ||
* </div> | ||
* </file> | ||
* | ||
* <file name="book.html"> | ||
* controller: {{name}}<br /> | ||
* Book Id: {{params.bookId}}<br /> | ||
* </file> | ||
* | ||
* <file name="chapter.html"> | ||
* controller: {{name}}<br /> | ||
* Book Id: {{params.bookId}}<br /> | ||
* Chapter Id: {{params.chapterId}} | ||
* </file> | ||
* | ||
* <file name="script.js"> | ||
* angular.module('ngRouteExample', ['ngRoute']) | ||
* | ||
* .controller('MainController', function($scope, $route, $routeParams, $location) { | ||
* $scope.$route = $route; | ||
@@ -337,14 +338,14 @@ * $scope.$location = $location; | ||
* }) | ||
* | ||
* .controller('BookController', function($scope, $routeParams) { | ||
* | ||
* .controller('BookController', function($scope, $routeParams) { | ||
* $scope.name = "BookController"; | ||
* $scope.params = $routeParams; | ||
* }) | ||
* | ||
* .controller('ChapterController', function($scope, $routeParams) { | ||
* | ||
* .controller('ChapterController', function($scope, $routeParams) { | ||
* $scope.name = "ChapterController"; | ||
* $scope.params = $routeParams; | ||
* }) | ||
* | ||
* .config(function($routeProvider, $locationProvider) { | ||
* | ||
* .config(function($routeProvider, $locationProvider) { | ||
* $routeProvider | ||
@@ -371,7 +372,7 @@ * .when('/Book/:bookId', { | ||
* }); | ||
* | ||
* </file> | ||
* | ||
* <file name="protractor.js" type="protractor"> | ||
* it('should load and compile correct template', function() { | ||
* | ||
* </file> | ||
* | ||
* <file name="protractor.js" type="protractor"> | ||
* it('should load and compile correct template', function() { | ||
* element(by.linkText('Moby: Ch1')).click(); | ||
@@ -389,405 +390,408 @@ * var content = element(by.css('[ng-view]')).getText(); | ||
* }); | ||
* </file> | ||
* </example> | ||
*/ | ||
* </file> | ||
* </example> | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeStart | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted before a route change. At this point the route services starts | ||
* resolving all of the dependencies needed for the route change to occur. | ||
* Typically this involves fetching the view template as well as any dependencies | ||
* defined in `resolve` route property. Once all of the dependencies are resolved | ||
* `$routeChangeSuccess` is fired. | ||
* | ||
* The route change (and the `$location` change that triggered it) can be prevented | ||
* by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} | ||
* for more details about event object. | ||
* | ||
* @param {Object} angularEvent Synthetic event object. | ||
* @param {Route} next Future route information. | ||
* @param {Route} current Current route information. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeStart | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted before a route change. At this point the route services starts | ||
* resolving all of the dependencies needed for the route change to occur. | ||
* Typically this involves fetching the view template as well as any dependencies | ||
* defined in `resolve` route property. Once all of the dependencies are resolved | ||
* `$routeChangeSuccess` is fired. | ||
* | ||
* The route change (and the `$location` change that triggered it) can be prevented | ||
* by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} | ||
* for more details about event object. | ||
* | ||
* @param {Object} angularEvent Synthetic event object. | ||
* @param {Route} next Future route information. | ||
* @param {Route} current Current route information. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeSuccess | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted after a route dependencies are resolved. | ||
* {@link ngRoute.directive:ngView ngView} listens for the directive | ||
* to instantiate the controller and render the view. | ||
* | ||
* @param {Object} angularEvent Synthetic event object. | ||
* @param {Route} current Current route information. | ||
* @param {Route|Undefined} previous Previous route information, or undefined if current is | ||
* first route entered. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeSuccess | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted after a route dependencies are resolved. | ||
* {@link ngRoute.directive:ngView ngView} listens for the directive | ||
* to instantiate the controller and render the view. | ||
* | ||
* @param {Object} angularEvent Synthetic event object. | ||
* @param {Route} current Current route information. | ||
* @param {Route|Undefined} previous Previous route information, or undefined if current is | ||
* first route entered. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeError | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted if any of the resolve promises are rejected. | ||
* | ||
* @param {Object} angularEvent Synthetic event object | ||
* @param {Route} current Current route information. | ||
* @param {Route} previous Previous route information. | ||
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeChangeError | ||
* @eventType broadcast on root scope | ||
* @description | ||
* Broadcasted if any of the resolve promises are rejected. | ||
* | ||
* @param {Object} angularEvent Synthetic event object | ||
* @param {Route} current Current route information. | ||
* @param {Route} previous Previous route information. | ||
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeUpdate | ||
* @eventType broadcast on root scope | ||
* @description | ||
* | ||
* The `reloadOnSearch` property has been set to false, and we are reusing the same | ||
* instance of the Controller. | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name $route#$routeUpdate | ||
* @eventType broadcast on root scope | ||
* @description | ||
* | ||
* The `reloadOnSearch` property has been set to false, and we are reusing the same | ||
* instance of the Controller. | ||
*/ | ||
var forceReload = false, | ||
preparedRoute, | ||
preparedRouteIsUpdateOnly, | ||
$route = { | ||
routes: routes, | ||
var forceReload = false, | ||
preparedRoute, | ||
preparedRouteIsUpdateOnly, | ||
$route = { | ||
routes: routes, | ||
/** | ||
* @ngdoc method | ||
* @name $route#reload | ||
* | ||
* @description | ||
* Causes `$route` service to reload the current route even if | ||
* {@link ng.$location $location} hasn't changed. | ||
* | ||
* As a result of that, {@link ngRoute.directive:ngView ngView} | ||
* creates new scope and reinstantiates the controller. | ||
*/ | ||
reload: function() { | ||
forceReload = true; | ||
$rootScope.$evalAsync(function() { | ||
// Don't support cancellation of a reload for now... | ||
prepareRoute(); | ||
commitRoute(); | ||
}); | ||
}, | ||
/** | ||
* @ngdoc method | ||
* @name $route#reload | ||
* | ||
* @description | ||
* Causes `$route` service to reload the current route even if | ||
* {@link ng.$location $location} hasn't changed. | ||
* | ||
* As a result of that, {@link ngRoute.directive:ngView ngView} | ||
* creates new scope and reinstantiates the controller. | ||
*/ | ||
reload: function () { | ||
forceReload = true; | ||
$rootScope.$evalAsync(function () { | ||
// Don't support cancellation of a reload for now... | ||
prepareRoute(); | ||
commitRoute(); | ||
}); | ||
}, | ||
/** | ||
* @ngdoc method | ||
* @name $route#updateParams | ||
* | ||
* @description | ||
* Causes `$route` service to update the current URL, replacing | ||
* current route parameters with those specified in `newParams`. | ||
* Provided property names that match the route's path segment | ||
* definitions will be interpolated into the location's path, while | ||
* remaining properties will be treated as query params. | ||
* | ||
* @param {Object} newParams mapping of URL parameter names to values | ||
*/ | ||
updateParams: function(newParams) { | ||
if (this.current && this.current.$$route) { | ||
var searchParams = {}, self=this; | ||
/** | ||
* @ngdoc method | ||
* @name $route#updateParams | ||
* | ||
* @description | ||
* Causes `$route` service to update the current URL, replacing | ||
* current route parameters with those specified in `newParams`. | ||
* Provided property names that match the route's path segment | ||
* definitions will be interpolated into the location's path, while | ||
* remaining properties will be treated as query params. | ||
* | ||
* @param {Object} newParams mapping of URL parameter names to values | ||
*/ | ||
updateParams: function (newParams) { | ||
if (this.current && this.current.$$route) { | ||
var searchParams = {}, self = this; | ||
angular.forEach(Object.keys(newParams), function(key) { | ||
if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; | ||
}); | ||
angular.forEach(Object.keys(newParams), function (key) { | ||
if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; | ||
}); | ||
newParams = angular.extend({}, this.current.params, newParams); | ||
$location.path(interpolate(this.current.$$route.originalPath, newParams)); | ||
$location.search(angular.extend({}, $location.search(), searchParams)); | ||
} | ||
else { | ||
throw $routeMinErr('norout', 'Tried updating route when with no current route'); | ||
} | ||
} | ||
}; | ||
newParams = angular.extend({}, this.current.params, newParams); | ||
$location.path(interpolate(this.current.$$route.originalPath, newParams)); | ||
$location.search(angular.extend({}, $location.search(), searchParams)); | ||
} | ||
else { | ||
throw $routeMinErr('norout', 'Tried updating route when with no current route'); | ||
} | ||
} | ||
}; | ||
$rootScope.$on('$locationChangeStart', prepareRoute); | ||
$rootScope.$on('$locationChangeSuccess', commitRoute); | ||
$rootScope.$on('$locationChangeStart', prepareRoute); | ||
$rootScope.$on('$locationChangeSuccess', commitRoute); | ||
return $route; | ||
return $route; | ||
///////////////////////////////////////////////////// | ||
///////////////////////////////////////////////////// | ||
/** | ||
* @param on {string} current url | ||
* @param route {Object} route regexp to match the url against | ||
* @return {?Object} | ||
* | ||
* @description | ||
* Check if the route matches the current url. | ||
* | ||
* Inspired by match in | ||
* visionmedia/express/lib/router/router.js. | ||
*/ | ||
function switchRouteMatcher(on, route) { | ||
var keys = route.keys, | ||
params = {}; | ||
/** | ||
* @param on {string} current url | ||
* @param route {Object} route regexp to match the url against | ||
* @return {?Object} | ||
* | ||
* @description | ||
* Check if the route matches the current url. | ||
* | ||
* Inspired by match in | ||
* visionmedia/express/lib/router/router.js. | ||
*/ | ||
function switchRouteMatcher(on, route) { | ||
var keys = route.keys, | ||
params = {}; | ||
if (!route.regexp) return null; | ||
if (!route.regexp) return null; | ||
var m = route.regexp.exec(on); | ||
if (!m) return null; | ||
var m = route.regexp.exec(on); | ||
if (!m) return null; | ||
for (var i = 1, len = m.length; i < len; ++i) { | ||
var key = keys[i - 1]; | ||
for (var i = 1, len = m.length; i < len; ++i) { | ||
var key = keys[i - 1]; | ||
var val = m[i]; | ||
var val = m[i]; | ||
if (key && val) { | ||
params[key.name] = val; | ||
} | ||
} | ||
return params; | ||
} | ||
if (key && val) { | ||
params[key.name] = val; | ||
} | ||
} | ||
return params; | ||
} | ||
function prepareRoute($locationEvent) { | ||
var lastRoute = $route.current; | ||
function prepareRoute($locationEvent) { | ||
var lastRoute = $route.current; | ||
preparedRoute = parseRoute(); | ||
preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route | ||
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams) | ||
&& !preparedRoute.reloadOnSearch && !forceReload; | ||
preparedRoute = parseRoute(); | ||
preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route | ||
&& angular.equals(preparedRoute.pathParams, lastRoute.pathParams) | ||
&& !preparedRoute.reloadOnSearch && !forceReload; | ||
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { | ||
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { | ||
if ($locationEvent) { | ||
$locationEvent.preventDefault(); | ||
} | ||
} | ||
} | ||
} | ||
if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { | ||
if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { | ||
if ($locationEvent) { | ||
$locationEvent.preventDefault(); | ||
} | ||
} | ||
} | ||
} | ||
function commitRoute() { | ||
var lastRoute = $route.current; | ||
var nextRoute = preparedRoute; | ||
function commitRoute() { | ||
var lastRoute = $route.current; | ||
var nextRoute = preparedRoute; | ||
if (preparedRouteIsUpdateOnly) { | ||
lastRoute.params = nextRoute.params; | ||
angular.copy(lastRoute.params, $routeParams); | ||
$rootScope.$broadcast('$routeUpdate', lastRoute); | ||
} else if (nextRoute || lastRoute) { | ||
forceReload = false; | ||
$route.current = nextRoute; | ||
if (nextRoute) { | ||
if (nextRoute.redirectTo) { | ||
if (angular.isString(nextRoute.redirectTo)) { | ||
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) | ||
.replace(); | ||
} else { | ||
$location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) | ||
.replace(); | ||
} | ||
} | ||
} | ||
if (preparedRouteIsUpdateOnly) { | ||
lastRoute.params = nextRoute.params; | ||
angular.copy(lastRoute.params, $routeParams); | ||
$rootScope.$broadcast('$routeUpdate', lastRoute); | ||
} else if (nextRoute || lastRoute) { | ||
forceReload = false; | ||
$route.current = nextRoute; | ||
if (nextRoute) { | ||
if (nextRoute.redirectTo) { | ||
if (angular.isString(nextRoute.redirectTo)) { | ||
$location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) | ||
.replace(); | ||
} else { | ||
$location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) | ||
.replace(); | ||
} | ||
} | ||
} | ||
$q.when(nextRoute). | ||
then(function() { | ||
if (nextRoute) { | ||
var locals = angular.extend({}, nextRoute.resolve), | ||
template, templateUrl; | ||
$q.when(nextRoute). | ||
then(function () { | ||
if (nextRoute) { | ||
var locals = angular.extend({}, nextRoute.resolve), | ||
template, templateUrl; | ||
angular.forEach(locals, function(value, key) { | ||
locals[key] = angular.isString(value) ? | ||
$injector.get(value) : $injector.invoke(value, null, null, key); | ||
}); | ||
angular.forEach(locals, function (value, key) { | ||
locals[key] = angular.isString(value) ? | ||
$injector.get(value) : $injector.invoke(value, null, null, key); | ||
}); | ||
if (angular.isDefined(template = nextRoute.template)) { | ||
if (angular.isFunction(template)) { | ||
template = template(nextRoute.params); | ||
if (angular.isDefined(template = nextRoute.template)) { | ||
if (angular.isFunction(template)) { | ||
template = template(nextRoute.params); | ||
} | ||
} else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { | ||
if (angular.isFunction(templateUrl)) { | ||
templateUrl = templateUrl(nextRoute.params); | ||
} | ||
templateUrl = $sce.getTrustedResourceUrl(templateUrl); | ||
if (angular.isDefined(templateUrl)) { | ||
nextRoute.loadedTemplateUrl = templateUrl; | ||
template = $templateRequest(templateUrl); | ||
} | ||
} | ||
if (angular.isDefined(template)) { | ||
locals['$template'] = template; | ||
} | ||
return $q.all(locals); | ||
} | ||
}). | ||
// after route change | ||
then(function (locals) { | ||
if (nextRoute == $route.current) { | ||
if (nextRoute) { | ||
nextRoute.locals = locals; | ||
angular.copy(nextRoute.params, $routeParams); | ||
} | ||
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); | ||
} | ||
}, function (error) { | ||
if (nextRoute == $route.current) { | ||
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); | ||
} | ||
}); | ||
} | ||
} | ||
} else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { | ||
if (angular.isFunction(templateUrl)) { | ||
templateUrl = templateUrl(nextRoute.params); | ||
/** | ||
* @returns {Object} the current active route, by matching it against the URL | ||
*/ | ||
function parseRoute() { | ||
// Match a route | ||
var params, match; | ||
angular.forEach(routes, function (route, path) { | ||
if (!match && (params = switchRouteMatcher($location.path(), route))) { | ||
match = inherit(route, { | ||
params: angular.extend({}, $location.search(), params), | ||
pathParams: params | ||
}); | ||
match.$$route = route; | ||
} | ||
}); | ||
// No route matched; fallback to "otherwise" route | ||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams: {}}); | ||
} | ||
templateUrl = $sce.getTrustedResourceUrl(templateUrl); | ||
if (angular.isDefined(templateUrl)) { | ||
nextRoute.loadedTemplateUrl = templateUrl; | ||
template = $templateRequest(templateUrl); | ||
/** | ||
* @returns {string} interpolation of the redirect path with the parameters | ||
*/ | ||
function interpolate(string, params) { | ||
var result = []; | ||
angular.forEach((string || '').split(':'), function (segment, i) { | ||
if (i === 0) { | ||
result.push(segment); | ||
} else { | ||
var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); | ||
var key = segmentMatch[1]; | ||
result.push(params[key]); | ||
result.push(segmentMatch[2] || ''); | ||
delete params[key]; | ||
} | ||
}); | ||
return result.join(''); | ||
} | ||
} | ||
if (angular.isDefined(template)) { | ||
locals['$template'] = template; | ||
} | ||
return $q.all(locals); | ||
} | ||
}). | ||
// after route change | ||
then(function(locals) { | ||
if (nextRoute == $route.current) { | ||
if (nextRoute) { | ||
nextRoute.locals = locals; | ||
angular.copy(nextRoute.params, $routeParams); | ||
} | ||
$rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); | ||
} | ||
}, function(error) { | ||
if (nextRoute == $route.current) { | ||
$rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); | ||
} | ||
}); | ||
} | ||
}]; | ||
} | ||
ngRouteModule.provider('$routeParams', $RouteParamsProvider); | ||
/** | ||
* @returns {Object} the current active route, by matching it against the URL | ||
*/ | ||
function parseRoute() { | ||
// Match a route | ||
var params, match; | ||
angular.forEach(routes, function(route, path) { | ||
if (!match && (params = switchRouteMatcher($location.path(), route))) { | ||
match = inherit(route, { | ||
params: angular.extend({}, $location.search(), params), | ||
pathParams: params}); | ||
match.$$route = route; | ||
} | ||
}); | ||
// No route matched; fallback to "otherwise" route | ||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); | ||
} | ||
/** | ||
* @returns {string} interpolation of the redirect path with the parameters | ||
* @ngdoc service | ||
* @name $routeParams | ||
* @requires $route | ||
* | ||
* @description | ||
* The `$routeParams` service allows you to retrieve the current set of route parameters. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* The route parameters are a combination of {@link ng.$location `$location`}'s | ||
* {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. | ||
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. | ||
* | ||
* In case of parameter name collision, `path` params take precedence over `search` params. | ||
* | ||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged | ||
* (but its properties will likely change) even when a route change occurs. | ||
* | ||
* Note that the `$routeParams` are only updated *after* a route change completes successfully. | ||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions. | ||
* Instead you can use `$route.current.params` to access the new route's parameters. | ||
* | ||
* @example | ||
* ```js | ||
* // Given: | ||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby | ||
* // Route: /Chapter/:chapterId/Section/:sectionId | ||
* // | ||
* // Then | ||
* $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} | ||
* ``` | ||
*/ | ||
function interpolate(string, params) { | ||
var result = []; | ||
angular.forEach((string || '').split(':'), function(segment, i) { | ||
if (i === 0) { | ||
result.push(segment); | ||
} else { | ||
var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); | ||
var key = segmentMatch[1]; | ||
result.push(params[key]); | ||
result.push(segmentMatch[2] || ''); | ||
delete params[key]; | ||
} | ||
}); | ||
return result.join(''); | ||
function $RouteParamsProvider() { | ||
this.$get = function () { | ||
return {}; | ||
}; | ||
} | ||
}]; | ||
} | ||
ngRouteModule.provider('$routeParams', $RouteParamsProvider); | ||
ngRouteModule.directive('ngView', ngViewFactory); | ||
ngRouteModule.directive('ngView', ngViewFillContentFactory); | ||
/** | ||
* @ngdoc service | ||
* @name $routeParams | ||
* @requires $route | ||
* | ||
* @description | ||
* The `$routeParams` service allows you to retrieve the current set of route parameters. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* The route parameters are a combination of {@link ng.$location `$location`}'s | ||
* {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. | ||
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. | ||
* | ||
* In case of parameter name collision, `path` params take precedence over `search` params. | ||
* | ||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged | ||
* (but its properties will likely change) even when a route change occurs. | ||
* | ||
* Note that the `$routeParams` are only updated *after* a route change completes successfully. | ||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions. | ||
* Instead you can use `$route.current.params` to access the new route's parameters. | ||
* | ||
* @example | ||
* ```js | ||
* // Given: | ||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby | ||
* // Route: /Chapter/:chapterId/Section/:sectionId | ||
* // | ||
* // Then | ||
* $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} | ||
* ``` | ||
*/ | ||
function $RouteParamsProvider() { | ||
this.$get = function() { return {}; }; | ||
} | ||
ngRouteModule.directive('ngView', ngViewFactory); | ||
ngRouteModule.directive('ngView', ngViewFillContentFactory); | ||
/** | ||
* @ngdoc directive | ||
* @name ngView | ||
* @restrict ECA | ||
* | ||
* @description | ||
* # Overview | ||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by | ||
* including the rendered template of the current route into the main layout (`index.html`) file. | ||
* Every time the current route changes, the included view changes with it according to the | ||
* configuration of the `$route` service. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* @animations | ||
* enter - animation is used to bring new content into the browser. | ||
* leave - animation is used to animate existing content away. | ||
* | ||
* The enter and leave animation occur concurrently. | ||
* | ||
* @scope | ||
* @priority 400 | ||
* @param {string=} onload Expression to evaluate whenever the view updates. | ||
* | ||
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll | ||
/** | ||
* @ngdoc directive | ||
* @name ngView | ||
* @restrict ECA | ||
* | ||
* @description | ||
* # Overview | ||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by | ||
* including the rendered template of the current route into the main layout (`index.html`) file. | ||
* Every time the current route changes, the included view changes with it according to the | ||
* configuration of the `$route` service. | ||
* | ||
* Requires the {@link ngRoute `ngRoute`} module to be installed. | ||
* | ||
* @animations | ||
* enter - animation is used to bring new content into the browser. | ||
* leave - animation is used to animate existing content away. | ||
* | ||
* The enter and leave animation occur concurrently. | ||
* | ||
* @scope | ||
* @priority 400 | ||
* @param {string=} onload Expression to evaluate whenever the view updates. | ||
* | ||
* @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll | ||
* $anchorScroll} to scroll the viewport after the view is updated. | ||
* | ||
* - If the attribute is not set, disable scrolling. | ||
* - If the attribute is set without value, enable scrolling. | ||
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated | ||
* as an expression yields a truthy value. | ||
* @example | ||
<example name="ngView-directive" module="ngViewExample" | ||
deps="angular-route.js;angular-animate.js" | ||
animations="true" fixBase="true"> | ||
<file name="index.html"> | ||
<div ng-controller="MainCtrl as main"> | ||
Choose: | ||
<a href="Book/Moby">Moby</a> | | ||
<a href="Book/Moby/ch/1">Moby: Ch1</a> | | ||
<a href="Book/Gatsby">Gatsby</a> | | ||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | | ||
<a href="Book/Scarlet">Scarlet Letter</a><br/> | ||
* | ||
* - If the attribute is not set, disable scrolling. | ||
* - If the attribute is set without value, enable scrolling. | ||
* - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated | ||
* as an expression yields a truthy value. | ||
* @example | ||
<example name="ngView-directive" module="ngViewExample" | ||
deps="angular-route.js;angular-animate.js" | ||
animations="true" fixBase="true"> | ||
<file name="index.html"> | ||
<div ng-controller="MainCtrl as main"> | ||
Choose: | ||
<a href="Book/Moby">Moby</a> | | ||
<a href="Book/Moby/ch/1">Moby: Ch1</a> | | ||
<a href="Book/Gatsby">Gatsby</a> | | ||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | | ||
<a href="Book/Scarlet">Scarlet Letter</a><br/> | ||
<div class="view-animate-container"> | ||
<div ng-view class="view-animate"></div> | ||
</div> | ||
<hr /> | ||
<div class="view-animate-container"> | ||
<div ng-view class="view-animate"></div> | ||
</div> | ||
<hr /> | ||
<pre>$location.path() = {{main.$location.path()}}</pre> | ||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> | ||
<pre>$route.current.params = {{main.$route.current.params}}</pre> | ||
<pre>$routeParams = {{main.$routeParams}}</pre> | ||
</div> | ||
</file> | ||
<pre>$location.path() = {{main.$location.path()}}</pre> | ||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> | ||
<pre>$route.current.params = {{main.$route.current.params}}</pre> | ||
<pre>$routeParams = {{main.$routeParams}}</pre> | ||
</div> | ||
</file> | ||
<file name="book.html"> | ||
<div> | ||
controller: {{book.name}}<br /> | ||
Book Id: {{book.params.bookId}}<br /> | ||
</div> | ||
</file> | ||
<file name="book.html"> | ||
<div> | ||
controller: {{book.name}}<br /> | ||
Book Id: {{book.params.bookId}}<br /> | ||
</div> | ||
</file> | ||
<file name="chapter.html"> | ||
<div> | ||
controller: {{chapter.name}}<br /> | ||
Book Id: {{chapter.params.bookId}}<br /> | ||
Chapter Id: {{chapter.params.chapterId}} | ||
</div> | ||
</file> | ||
<file name="chapter.html"> | ||
<div> | ||
controller: {{chapter.name}}<br /> | ||
Book Id: {{chapter.params.bookId}}<br /> | ||
Chapter Id: {{chapter.params.chapterId}} | ||
</div> | ||
</file> | ||
<file name="animations.css"> | ||
.view-animate-container { | ||
<file name="animations.css"> | ||
.view-animate-container { | ||
position:relative; | ||
@@ -802,7 +806,7 @@ height:100px!important; | ||
.view-animate { | ||
.view-animate { | ||
padding:10px; | ||
} | ||
.view-animate.ng-enter, .view-animate.ng-leave { | ||
.view-animate.ng-enter, .view-animate.ng-leave { | ||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; | ||
@@ -823,17 +827,17 @@ transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; | ||
.view-animate.ng-enter { | ||
.view-animate.ng-enter { | ||
left:100%; | ||
} | ||
.view-animate.ng-enter.ng-enter-active { | ||
.view-animate.ng-enter.ng-enter-active { | ||
left:0; | ||
} | ||
.view-animate.ng-leave.ng-leave-active { | ||
.view-animate.ng-leave.ng-leave-active { | ||
left:-100%; | ||
} | ||
</file> | ||
</file> | ||
<file name="script.js"> | ||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) | ||
.config(['$routeProvider', '$locationProvider', | ||
function($routeProvider, $locationProvider) { | ||
<file name="script.js"> | ||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) | ||
.config(['$routeProvider', '$locationProvider', | ||
function($routeProvider, $locationProvider) { | ||
$routeProvider | ||
@@ -853,4 +857,4 @@ .when('/Book/:bookId', { | ||
}]) | ||
.controller('MainCtrl', ['$route', '$routeParams', '$location', | ||
function($route, $routeParams, $location) { | ||
.controller('MainCtrl', ['$route', '$routeParams', '$location', | ||
function($route, $routeParams, $location) { | ||
this.$route = $route; | ||
@@ -860,7 +864,7 @@ this.$location = $location; | ||
}]) | ||
.controller('BookCtrl', ['$routeParams', function($routeParams) { | ||
.controller('BookCtrl', ['$routeParams', function($routeParams) { | ||
this.name = "BookCtrl"; | ||
this.params = $routeParams; | ||
}]) | ||
.controller('ChapterCtrl', ['$routeParams', function($routeParams) { | ||
.controller('ChapterCtrl', ['$routeParams', function($routeParams) { | ||
this.name = "ChapterCtrl"; | ||
@@ -870,6 +874,6 @@ this.params = $routeParams; | ||
</file> | ||
</file> | ||
<file name="protractor.js" type="protractor"> | ||
it('should load and compile correct template', function() { | ||
<file name="protractor.js" type="protractor"> | ||
it('should load and compile correct template', function() { | ||
element(by.linkText('Moby: Ch1')).click(); | ||
@@ -887,85 +891,85 @@ var content = element(by.css('[ng-view]')).getText(); | ||
}); | ||
</file> | ||
</example> | ||
*/ | ||
</file> | ||
</example> | ||
*/ | ||
/** | ||
* @ngdoc event | ||
* @name ngView#$viewContentLoaded | ||
* @eventType emit on the current ngView scope | ||
* @description | ||
* Emitted every time the ngView content is reloaded. | ||
*/ | ||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; | ||
function ngViewFactory($route, $anchorScroll, $animate) { | ||
return { | ||
restrict: 'ECA', | ||
terminal: true, | ||
priority: 400, | ||
transclude: 'element', | ||
link: function(scope, $element, attr, ctrl, $transclude) { | ||
var currentScope, | ||
currentElement, | ||
previousLeaveAnimation, | ||
autoScrollExp = attr.autoscroll, | ||
onloadExp = attr.onload || ''; | ||
/** | ||
* @ngdoc event | ||
* @name ngView#$viewContentLoaded | ||
* @eventType emit on the current ngView scope | ||
* @description | ||
* Emitted every time the ngView content is reloaded. | ||
*/ | ||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; | ||
function ngViewFactory($route, $anchorScroll, $animate) { | ||
return { | ||
restrict: 'ECA', | ||
terminal: true, | ||
priority: 400, | ||
transclude: 'element', | ||
link: function (scope, $element, attr, ctrl, $transclude) { | ||
var currentScope, | ||
currentElement, | ||
previousLeaveAnimation, | ||
autoScrollExp = attr.autoscroll, | ||
onloadExp = attr.onload || ''; | ||
scope.$on('$routeChangeSuccess', update); | ||
update(); | ||
scope.$on('$routeChangeSuccess', update); | ||
update(); | ||
function cleanupLastView() { | ||
if (previousLeaveAnimation) { | ||
$animate.cancel(previousLeaveAnimation); | ||
previousLeaveAnimation = null; | ||
} | ||
function cleanupLastView() { | ||
if (previousLeaveAnimation) { | ||
$animate.cancel(previousLeaveAnimation); | ||
previousLeaveAnimation = null; | ||
} | ||
if (currentScope) { | ||
currentScope.$destroy(); | ||
currentScope = null; | ||
} | ||
if (currentElement) { | ||
previousLeaveAnimation = $animate.leave(currentElement); | ||
previousLeaveAnimation.then(function() { | ||
previousLeaveAnimation = null; | ||
}); | ||
currentElement = null; | ||
} | ||
} | ||
if (currentScope) { | ||
currentScope.$destroy(); | ||
currentScope = null; | ||
} | ||
if (currentElement) { | ||
previousLeaveAnimation = $animate.leave(currentElement); | ||
previousLeaveAnimation.then(function () { | ||
previousLeaveAnimation = null; | ||
}); | ||
currentElement = null; | ||
} | ||
} | ||
function update() { | ||
var locals = $route.current && $route.current.locals, | ||
template = locals && locals.$template; | ||
function update() { | ||
var locals = $route.current && $route.current.locals, | ||
template = locals && locals.$template; | ||
if (angular.isDefined(template)) { | ||
var newScope = scope.$new(); | ||
var current = $route.current; | ||
if (angular.isDefined(template)) { | ||
var newScope = scope.$new(); | ||
var current = $route.current; | ||
// Note: This will also link all children of ng-view that were contained in the original | ||
// html. If that content contains controllers, ... they could pollute/change the scope. | ||
// However, using ng-view on an element with additional content does not make sense... | ||
// Note: We can't remove them in the cloneAttchFn of $transclude as that | ||
// function is called before linking the content, which would apply child | ||
// directives to non existing elements. | ||
var clone = $transclude(newScope, function(clone) { | ||
$animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { | ||
if (angular.isDefined(autoScrollExp) | ||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { | ||
$anchorScroll(); | ||
// Note: This will also link all children of ng-view that were contained in the original | ||
// html. If that content contains controllers, ... they could pollute/change the scope. | ||
// However, using ng-view on an element with additional content does not make sense... | ||
// Note: We can't remove them in the cloneAttchFn of $transclude as that | ||
// function is called before linking the content, which would apply child | ||
// directives to non existing elements. | ||
var clone = $transclude(newScope, function (clone) { | ||
$animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { | ||
if (angular.isDefined(autoScrollExp) | ||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { | ||
$anchorScroll(); | ||
} | ||
}); | ||
cleanupLastView(); | ||
}); | ||
currentElement = clone; | ||
currentScope = current.scope = newScope; | ||
currentScope.$emit('$viewContentLoaded'); | ||
currentScope.$eval(onloadExp); | ||
} else { | ||
cleanupLastView(); | ||
} | ||
} | ||
}); | ||
cleanupLastView(); | ||
}); | ||
currentElement = clone; | ||
currentScope = current.scope = newScope; | ||
currentScope.$emit('$viewContentLoaded'); | ||
currentScope.$eval(onloadExp); | ||
} else { | ||
cleanupLastView(); | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
@@ -977,31 +981,31 @@ // This directive is called during the $transclude call of the first `ngView` directive. | ||
// is called. | ||
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; | ||
function ngViewFillContentFactory($compile, $controller, $route) { | ||
return { | ||
restrict: 'ECA', | ||
priority: -400, | ||
link: function(scope, $element) { | ||
var current = $route.current, | ||
locals = current.locals; | ||
ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; | ||
function ngViewFillContentFactory($compile, $controller, $route) { | ||
return { | ||
restrict: 'ECA', | ||
priority: -400, | ||
link: function (scope, $element) { | ||
var current = $route.current, | ||
locals = current.locals; | ||
$element.html(locals.$template); | ||
$element.html(locals.$template); | ||
var link = $compile($element.contents()); | ||
var link = $compile($element.contents()); | ||
if (current.controller) { | ||
locals.$scope = scope; | ||
var controller = $controller(current.controller, locals); | ||
if (current.controllerAs) { | ||
scope[current.controllerAs] = controller; | ||
} | ||
$element.data('$ngControllerController', controller); | ||
$element.children().data('$ngControllerController', controller); | ||
} | ||
if (current.controller) { | ||
locals.$scope = scope; | ||
var controller = $controller(current.controller, locals); | ||
if (current.controllerAs) { | ||
scope[current.controllerAs] = controller; | ||
} | ||
$element.data('$ngControllerController', controller); | ||
$element.children().data('$ngControllerController', controller); | ||
} | ||
link(scope); | ||
link(scope); | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
})(window, window.angular); |
@@ -1,56 +0,62 @@ | ||
(function(){ | ||
angular.module("visorSampleApp",["visor","ngRoute","ngCookies"]) | ||
.config(function(visorProvider,$routeProvider){ | ||
visorProvider.authenticate = function($cookieStore,$q,$rootScope){ | ||
var user = $cookieStore.get("user"); | ||
if (user) { | ||
$rootScope.user = user; | ||
return $q.when(user); | ||
} else { | ||
return $q.reject(null); | ||
} | ||
}; | ||
visorProvider.doOnNotAuthorized = function($location,restrictedUrl){ | ||
$location.url("/access_denied?prevUrl=" + encodeURIComponent(restrictedUrl)); | ||
} | ||
$routeProvider.when("/home",{ | ||
templateUrl:"app/home.html" | ||
}) | ||
.when("/login",{ | ||
templateUrl:"app/login.html", | ||
controller:function($scope,visor,$rootScope,$cookieStore){ | ||
$scope.login = function(){ | ||
var user = {is_admin:$scope.is_admin}; | ||
$cookieStore.put("user",user); | ||
$rootScope.user = user; | ||
visor.setAuthenticated(user); | ||
} | ||
}, | ||
restrict:function(user){return user === undefined;} | ||
}) | ||
.when("/private",{ | ||
templateUrl:"app/private.html", | ||
restrict:function(user){return !!user} | ||
}) | ||
.when("/access_denied",{ | ||
templateUrl:"app/access_denied.html", | ||
controller:function($scope,$routeParams){ | ||
$scope.prevUrl = $routeParams.prevUrl; | ||
} | ||
}) | ||
.when("/admin",{ | ||
templateUrl:"app/admin.html", | ||
restrict:function(user){return user && user.is_admin;} | ||
}) | ||
.otherwise({redirectTo:"/home"}); | ||
}) | ||
.controller("MainCtrl",function($scope,$cookieStore,$rootScope,$route,visor,$location){ | ||
$scope.$route = $route; | ||
$scope.logout = function(){ | ||
$cookieStore.remove("user"); | ||
$rootScope.user = undefined; | ||
visor.setUnauthenticated(); | ||
$location.url("/home"); | ||
} | ||
}) | ||
(function () { | ||
angular.module("visorSampleApp", ["visor", "ngRoute", "ngCookies"]) | ||
.config(function (visorProvider, $routeProvider) { | ||
visorProvider.authenticate = function ($cookieStore, $q, $rootScope) { | ||
var user = $cookieStore.get("user"); | ||
if (user) { | ||
$rootScope.user = user; | ||
return $q.when(user); | ||
} else { | ||
return $q.reject(null); | ||
} | ||
}; | ||
visorProvider.doOnNotAuthorized = function ($location, restrictedUrl) { | ||
$location.url("/access_denied?prevUrl=" + encodeURIComponent(restrictedUrl)); | ||
} | ||
$routeProvider.when("/home", { | ||
templateUrl: "app/home.html" | ||
}) | ||
.when("/login", { | ||
templateUrl: "app/login.html", | ||
controller: function ($scope, visor, $rootScope, $cookieStore) { | ||
$scope.login = function () { | ||
var user = {is_admin: $scope.is_admin}; | ||
$cookieStore.put("user", user); | ||
$rootScope.user = user; | ||
visor.setAuthenticated(user); | ||
} | ||
}, | ||
restrict: function (user) { | ||
return user === undefined; | ||
} | ||
}) | ||
.when("/private", { | ||
templateUrl: "app/private.html", | ||
restrict: function (user) { | ||
return !!user | ||
} | ||
}) | ||
.when("/access_denied", { | ||
templateUrl: "app/access_denied.html", | ||
controller: function ($scope, $routeParams) { | ||
$scope.prevUrl = $routeParams.prevUrl; | ||
} | ||
}) | ||
.when("/admin", { | ||
templateUrl: "app/admin.html", | ||
restrict: function (user) { | ||
return user && user.is_admin; | ||
} | ||
}) | ||
.otherwise({redirectTo: "/home"}); | ||
}) | ||
.controller("MainCtrl", function ($scope, $cookieStore, $rootScope, $route, visor, $location) { | ||
$scope.$route = $route; | ||
$scope.logout = function () { | ||
$cookieStore.remove("user"); | ||
$rootScope.user = undefined; | ||
visor.setUnauthenticated(); | ||
$location.url("/home"); | ||
} | ||
}) | ||
})(); |
@@ -1,62 +0,68 @@ | ||
(function(){ | ||
angular.module("visorSampleApp",["visor","ui.router","ngCookies"]) | ||
.config(function(visorProvider,$stateProvider,$urlRouterProvider){ | ||
visorProvider.authenticate = function($cookieStore,$q,$rootScope){ | ||
var user = $cookieStore.get("user"); | ||
if (user) { | ||
$rootScope.user = user; | ||
return $q.when(user); | ||
} else { | ||
return $q.reject(null); | ||
} | ||
}; | ||
visorProvider.doOnNotAuthorized = function($state,restrictedUrl){ | ||
$state.go("access_denied",{prevUrl:restrictedUrl}); | ||
} | ||
$stateProvider.state("home",{ | ||
templateUrl:"app/home.html", | ||
url:"/home" | ||
}) | ||
.state("login",{ | ||
templateUrl:"app/login.html", | ||
url:"/login", | ||
controller:function($scope,visor,$rootScope,$cookieStore){ | ||
$scope.login = function(){ | ||
var user = {is_admin:$scope.is_admin}; | ||
$cookieStore.put("user",user); | ||
$rootScope.user = user; | ||
visor.setAuthenticated(user); | ||
} | ||
}, | ||
restrict:function(user){return user === undefined;} | ||
}) | ||
.state("private",{ | ||
templateUrl:"app/private.html", | ||
url:"/private", | ||
restrict:function(user){return !!user} | ||
}) | ||
.state("access_denied",{ | ||
templateUrl:"app/access_denied.html", | ||
controller:function($scope,$stateParams){ | ||
$scope.prevUrl = $stateParams.prevUrl; | ||
}, | ||
url:"/access_denied?prevUrl" | ||
}) | ||
.state("admin",{ | ||
templateUrl:"app/admin.html", | ||
url:"/admin", | ||
restrict:function(user){return user && user.is_admin;} | ||
}); | ||
$urlRouterProvider.otherwise("/home"); | ||
}) | ||
.controller("MainCtrl",function($scope,$cookieStore,$state,$rootScope,visor){ | ||
$scope.logout = function(){ | ||
$cookieStore.remove("user"); | ||
$rootScope.user = undefined; | ||
visor.setUnauthenticated(); | ||
$state.go("home"); | ||
} | ||
}).run(function($state,$rootScope){ | ||
$rootScope.$state = $state; | ||
}) | ||
(function () { | ||
angular.module("visorSampleApp", ["visor", "ui.router", "ngCookies"]) | ||
.config(function (visorProvider, $stateProvider, $urlRouterProvider) { | ||
visorProvider.authenticate = function ($cookieStore, $q, $rootScope) { | ||
var user = $cookieStore.get("user"); | ||
if (user) { | ||
$rootScope.user = user; | ||
return $q.when(user); | ||
} else { | ||
return $q.reject(null); | ||
} | ||
}; | ||
visorProvider.doOnNotAuthorized = function ($state, restrictedUrl) { | ||
$state.go("access_denied", {prevUrl: restrictedUrl}); | ||
} | ||
$stateProvider.state("home", { | ||
templateUrl: "app/home.html", | ||
url: "/home" | ||
}) | ||
.state("login", { | ||
templateUrl: "app/login.html", | ||
url: "/login", | ||
controller: function ($scope, visor, $rootScope, $cookieStore) { | ||
$scope.login = function () { | ||
var user = {is_admin: $scope.is_admin}; | ||
$cookieStore.put("user", user); | ||
$rootScope.user = user; | ||
visor.setAuthenticated(user); | ||
} | ||
}, | ||
restrict: function (user) { | ||
return user === undefined; | ||
} | ||
}) | ||
.state("private", { | ||
templateUrl: "app/private.html", | ||
url: "/private", | ||
restrict: function (user) { | ||
return !!user | ||
} | ||
}) | ||
.state("access_denied", { | ||
templateUrl: "app/access_denied.html", | ||
controller: function ($scope, $stateParams) { | ||
$scope.prevUrl = $stateParams.prevUrl; | ||
}, | ||
url: "/access_denied?prevUrl" | ||
}) | ||
.state("admin", { | ||
templateUrl: "app/admin.html", | ||
url: "/admin", | ||
restrict: function (user) { | ||
return user && user.is_admin; | ||
} | ||
}); | ||
$urlRouterProvider.otherwise("/home"); | ||
}) | ||
.controller("MainCtrl", function ($scope, $cookieStore, $state, $rootScope, visor) { | ||
$scope.logout = function () { | ||
$cookieStore.remove("user"); | ||
$rootScope.user = undefined; | ||
visor.setUnauthenticated(); | ||
$state.go("home"); | ||
} | ||
}).run(function ($state, $rootScope) { | ||
$rootScope.$state = $state; | ||
}) | ||
})(); |
@@ -1,37 +0,37 @@ | ||
(function(){ | ||
"use strict"; | ||
(function () { | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` module contains the {@link delayLocationChange.delayLocationChange `delayLocationChange`} service. | ||
* | ||
*/ | ||
angular.module("delayLocationChange",[]) | ||
/** | ||
* @ngdoc overview | ||
* @name delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` module contains the {@link delayLocationChange.delayLocationChange `delayLocationChange`} service. | ||
* | ||
*/ | ||
angular.module("delayLocationChange", []) | ||
.service("delayLocationChange",["$rootScope","$q","$timeout","$location","$injector", | ||
function($rootScope,$q,$timeout,$location,$injector){ | ||
.service("delayLocationChange", ["$rootScope", "$q", "$timeout", "$location", "$injector", | ||
function ($rootScope, $q, $timeout, $location, $injector) { | ||
/** | ||
* @ngdoc service | ||
* @name delayLocationChange.delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` allows multiple services to stop the first location change (I.E. the rendering of the first page) until a promise is complete. | ||
* | ||
* | ||
* @param {promise|function()} waitFor - if a promise, will delay until promise is resolved | ||
* , if a function, will delay until the result of running the function, which must return a promise, will be resolved. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.module("myModule",["delayLocationChange"]) | ||
* .run(function(delayLocationChange){ | ||
/** | ||
* @ngdoc service | ||
* @name delayLocationChange.delayLocationChange | ||
* @description | ||
* | ||
* # delayLocationChange | ||
* | ||
* `delayLocationChange` allows multiple services to stop the first location change (I.E. the rendering of the first page) until a promise is complete. | ||
* | ||
* | ||
* @param {promise|function()} waitFor - if a promise, will delay until promise is resolved | ||
* , if a function, will delay until the result of running the function, which must return a promise, will be resolved. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.module("myModule",["delayLocationChange"]) | ||
* .run(function(delayLocationChange){ | ||
* delayLocationChange(function($http){ | ||
@@ -44,73 +44,76 @@ * return $http.get("/something/that/is/needed") | ||
* }; | ||
* </pre> | ||
*/ | ||
var service = function(arg){ | ||
if (arg.then) { | ||
//handles a promise | ||
addPromise(arg); | ||
} else { | ||
//assume it's a function | ||
if (changeStarted) { | ||
addPromise($injector.invoke(fn)); | ||
} else { | ||
//need to wait until angular started the locationChange, otherwise | ||
//something might start running before it's should | ||
waitingFunctions.push(arg); | ||
} | ||
} | ||
}; | ||
* </pre> | ||
*/ | ||
var service = function (arg) { | ||
if (arg.then) { | ||
//handles a promise | ||
addPromise(arg); | ||
} else { | ||
//assume it's a function | ||
if (changeStarted) { | ||
addPromise($injector.invoke(fn)); | ||
} else { | ||
//need to wait until angular started the locationChange, otherwise | ||
//something might start running before it's should | ||
waitingFunctions.push(arg); | ||
} | ||
} | ||
}; | ||
// we make sure that all promises finish by counting the number of promises | ||
//we recieved | ||
var unfinishedPromises = 0; | ||
var waitingFunctions = []; | ||
var changeStarted = false,_toUrl,_fromUrl,nextUrl; | ||
// we make sure that all promises finish by counting the number of promises | ||
//we recieved | ||
var unfinishedPromises = 0; | ||
var waitingFunctions = []; | ||
var changeStarted = false, _toUrl, _fromUrl, nextUrl; | ||
//checkPromises both determines if all promises were resolved and initiates | ||
//the delayed location change if no more promises remain | ||
function checkPromises(){ | ||
unfinishedPromises--; | ||
if (changeStarted && unfinishedPromises <= 0){ | ||
reloadChange(); | ||
} | ||
} | ||
//checkPromises both determines if all promises were resolved and initiates | ||
//the delayed location change if no more promises remain | ||
function checkPromises() { | ||
unfinishedPromises--; | ||
if (changeStarted && unfinishedPromises <= 0) { | ||
reloadChange(); | ||
} | ||
} | ||
function reloadChange(){ | ||
if ($location.absUrl() === _toUrl) { | ||
//we are running on the assumption (that might prove false at some point) | ||
//that nothing happens between canceling $locationChangeStart and emitting | ||
//$locationChangeSuccess | ||
$rootScope.$broadcast("$locationChangeSuccess",_toUrl,_fromUrl); | ||
} else { | ||
$location.url(nextUrl); | ||
} | ||
} | ||
function reloadChange() { | ||
if ($location.absUrl() === _toUrl) { | ||
//we are running on the assumption (that might prove false at some point) | ||
//that nothing happens between canceling $locationChangeStart and emitting | ||
//$locationChangeSuccess | ||
$rootScope.$broadcast("$locationChangeSuccess", _toUrl, _fromUrl); | ||
} else { | ||
$location.url(nextUrl); | ||
} | ||
} | ||
function addPromise(promise){ | ||
unfinishedPromises++; | ||
//to access using array notation because finally is a reserved word | ||
promise['finally'](checkPromises); | ||
} | ||
var unlisten = $rootScope.$on("$locationChangeStart",function(e,toUrl,fromUrl){ | ||
changeStarted = true; | ||
nextUrl = $location.url(); | ||
unlisten(); | ||
//We are relying on the fact that since the url never actually changed, | ||
//the fact that angular will return to the previous ulr when doing preventDefault, will not | ||
// have any effect | ||
e.preventDefault(); | ||
waitingFunctions.forEach(function(fn){addPromise($injector.invoke(fn))}); | ||
function addPromise(promise) { | ||
unfinishedPromises++; | ||
//to access using array notation because finally is a reserved word | ||
promise['finally'](checkPromises); | ||
} | ||
if(unfinishedPromises === 0 && !_toUrl){ //firstCall and no promises | ||
//we need to let at least one run through to verify | ||
//no promises will be added | ||
unfinishedPromises++; | ||
$timeout(checkPromises,1); | ||
} | ||
_toUrl = toUrl; | ||
_fromUrl = fromUrl; | ||
}); | ||
var unlisten = $rootScope.$on("$locationChangeStart", function (e, toUrl, fromUrl) { | ||
changeStarted = true; | ||
nextUrl = $location.url(); | ||
unlisten(); | ||
//We are relying on the fact that since the url never actually changed, | ||
//the fact that angular will return to the previous ulr when doing preventDefault, will not | ||
// have any effect | ||
e.preventDefault(); | ||
waitingFunctions.forEach(function (fn) { | ||
addPromise($injector.invoke(fn)) | ||
}); | ||
return service; | ||
}]); | ||
if (unfinishedPromises === 0 && !_toUrl) { //firstCall and no promises | ||
//we need to let at least one run through to verify | ||
//no promises will be added | ||
unfinishedPromises++; | ||
$timeout(checkPromises, 1); | ||
} | ||
_toUrl = toUrl; | ||
_fromUrl = fromUrl; | ||
}); | ||
return service; | ||
}]); | ||
})(); |
794
src/visor.js
@@ -0,35 +1,33 @@ | ||
(function () { | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name visor | ||
* @description | ||
* | ||
* # Visor | ||
* | ||
* `Visor` is an authentication and authorization module. | ||
* | ||
* <div doc-module-components="visor"></div> | ||
* | ||
* See {@link visor.visor `visor`} for usage. | ||
*/ | ||
angular.module("visor", ["visor.permissions", "visor.ui-router", "visor.ngRoute", "delayLocationChange", "visor.allowed"]) | ||
(function(){ | ||
"use strict"; | ||
/** | ||
* @ngdoc overview | ||
* @name visor | ||
* @description | ||
* | ||
* # Visor | ||
* | ||
* `Visor` is an authentication and authorization module. | ||
* | ||
* <div doc-module-components="visor"></div> | ||
* | ||
* See {@link visor.visor `visor`} for usage. | ||
*/ | ||
angular.module("visor",["visor.permissions","visor.ui-router","visor.ngRoute","delayLocationChange"]) | ||
/** | ||
* @ngdoc service | ||
* @name visor.authenticatedOnly | ||
* @description | ||
* | ||
* # authenticatedOnly | ||
* | ||
* `authenticatedOnly` is a restrict function that only allows authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,authenticatedOnly){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.authenticatedOnly | ||
* @description | ||
* | ||
* # authenticatedOnly | ||
* | ||
* `authenticatedOnly` is a restrict function that only allows authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,authenticatedOnly){ | ||
* $stateProvider.state("private",{ | ||
@@ -39,22 +37,22 @@ * restrict: authenticatedOnly | ||
* } | ||
* </pre> | ||
*/ | ||
.constant("authenticatedOnly",function(authData){ | ||
return !!authData; | ||
}) | ||
* </pre> | ||
*/ | ||
.constant("authenticatedOnly", function (authData) { | ||
return !!authData; | ||
}) | ||
/** | ||
* @ngdoc service | ||
* @name visor.notForAuthenticated | ||
* @description | ||
* | ||
* # notForAuthenticated | ||
* | ||
* `notForAuthenticated` is a restrict function that does not allow authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,notForAuthenticated){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.notForAuthenticated | ||
* @description | ||
* | ||
* # notForAuthenticated | ||
* | ||
* `notForAuthenticated` is a restrict function that does not allow authenticated users access to a route. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function($stateProvider,notForAuthenticated){ | ||
* $stateProvider.state("private",{ | ||
@@ -64,27 +62,27 @@ * restrict: notForAuthenticated | ||
* } | ||
* </pre> | ||
*/ | ||
.constant("notForAuthenticated",function(authData){ | ||
return authData === undefined; | ||
}) | ||
* </pre> | ||
*/ | ||
.constant("notForAuthenticated", function (authData) { | ||
return authData === undefined; | ||
}) | ||
/** | ||
* @ngdoc service | ||
* @name visor.visorProvider | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visorProvider` provides configuration options to define how authentication and authorization works. | ||
* | ||
* The only required configuration is {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.visorProvider | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visorProvider` provides configuration options to define how authentication and authorization works. | ||
* | ||
* The only required configuration is {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
@@ -96,103 +94,104 @@ * return $http.get("/api/user/me").then(function(res){ | ||
* } | ||
* </pre> | ||
*/ | ||
.provider("visor",[function(){ | ||
function addNextToUrl(url,$location,restrictedUrl){ | ||
if (config.shouldAddNext){ | ||
if (url.indexOf("?") >=0) { | ||
return url.replace(/\?/,"?next=" + encodeURIComponent(restrictedUrl) + "&"); | ||
} | ||
return url + "?next=" + encodeURIComponent(restrictedUrl); | ||
} else { | ||
return url; | ||
} | ||
} | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#authenticateOnStartup | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* If `true` visor will try to authenticate before any route is accessed (it will stop the routing until the authentication promise is resolved). | ||
* If `false` will only authenticate when a user tries to access a route with restriction. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.authenticateOnStartup = true; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#loginRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an unauthenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function. | ||
* | ||
* Defaults to `/login` | ||
*/ | ||
config.loginRoute = "/login"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#homeRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after manual authentication. | ||
* Only meaningful when using the default {@link visor.visorProvider#doAfterManualAuthentication `visorProvider.doAfterManualAuthentication`} function. | ||
* | ||
* Defaults to `/` | ||
*/ | ||
config.homeRoute = "/"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#notAuthorizedRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an authenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthorized `visorProvider.doOnNotAuthorized`} function. | ||
* | ||
* Defaults to `/access_denied` | ||
*/ | ||
config.notAuthorizedRoute = "/access_denied"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#shouldAddNext | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* When using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function, visor adds a `next` parameter to the login url provided in {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* Once a user manually authenticates, that route is used to redirect back to the original requested url. | ||
* | ||
* If `false` will not add the next url in {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`}. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.shouldAddNext = true; | ||
* </pre> | ||
*/ | ||
.provider("visor", [function () { | ||
function addNextToUrl(url, $location, restrictedUrl) { | ||
if (config.shouldAddNext) { | ||
if (url.indexOf("?") >= 0) { | ||
return url.replace(/\?/, "?next=" + encodeURIComponent(restrictedUrl) + "&"); | ||
} | ||
return url + "?next=" + encodeURIComponent(restrictedUrl); | ||
} else { | ||
return url; | ||
} | ||
} | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#authenticate | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* This function needs to be configured in order to use visor. | ||
* | ||
* `visorProvider.authentication` defines the authentication function that will be called to provide | ||
* the authentication info at startup. | ||
* It must return a promise that will resolve to an object if the user is authenticated or rejected if | ||
* the user isn't authenticated. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#authenticateOnStartup | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* If `true` visor will try to authenticate before any route is accessed (it will stop the routing until the authentication promise is resolved). | ||
* If `false` will only authenticate when a user tries to access a route with restriction. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.authenticateOnStartup = true; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#loginRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an unauthenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function. | ||
* | ||
* Defaults to `/login` | ||
*/ | ||
config.loginRoute = "/login"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#homeRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after manual authentication. | ||
* Only meaningful when using the default {@link visor.visorProvider#doAfterManualAuthentication `visorProvider.doAfterManualAuthentication`} function. | ||
* | ||
* Defaults to `/` | ||
*/ | ||
config.homeRoute = "/"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#notAuthorizedRoute | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The route to go to after an authenticated user tries to access a restricted url. | ||
* Only meaningful when using the default {@link visor.visorProvider#doOnNotAuthorized `visorProvider.doOnNotAuthorized`} function. | ||
* | ||
* Defaults to `/access_denied` | ||
*/ | ||
config.notAuthorizedRoute = "/access_denied"; | ||
/** | ||
* @ngdoc property | ||
* @name visor.visorProvider#shouldAddNext | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* When using the default {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`} function, visor adds a `next` parameter to the login url provided in {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* Once a user manually authenticates, that route is used to redirect back to the original requested url. | ||
* | ||
* If `false` will not add the next url in {@link visor.visorProvider#doOnNotAuthenticated `visorProvider.doOnNotAuthenticated`}. | ||
* | ||
* Defaults to `true` | ||
*/ | ||
config.shouldAddNext = true; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#authenticate | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* This function needs to be configured in order to use visor. | ||
* | ||
* `visorProvider.authentication` defines the authentication function that will be called to provide | ||
* the authentication info at startup. | ||
* It must return a promise that will resolve to an object if the user is authenticated or rejected if | ||
* the user isn't authenticated. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
@@ -204,25 +203,25 @@ * return $http.get("/api/user/me").then(function(res){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.authenticate = function(){ | ||
throw new Error("visorProvider.authenticate must be defined to use visor"); | ||
}; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthenticated | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take when a user tries to access a restricted route but is not authenticated. | ||
* By default it redirect to {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* If {@link visor.visorProvider#shouldAddNext `shouldAddNext`} is enabled, a `next` parameter with the restricted url is added to the login url. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.authenticate = function () { | ||
throw new Error("visorProvider.authenticate must be defined to use visor"); | ||
}; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthenticated | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take when a user tries to access a restricted route but is not authenticated. | ||
* By default it redirect to {@link visor.visorProvider#loginRoute `loginRoute`}. | ||
* If {@link visor.visorProvider#shouldAddNext `shouldAddNext`} is enabled, a `next` parameter with the restricted url is added to the login url. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to an error page instead of login | ||
@@ -235,22 +234,22 @@ * visorProvider.doOnNotAuthenticated = function(restrictedUrl,$state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthenticated = ["$location","restrictedUrl",function($location,restrictedUrl){ | ||
$location.url(addNextToUrl(config.loginRoute,$location,restrictedUrl)) | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doAfterManualAuthentication | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take after a user is authenticated using {@link visor.visor#setAuthenticated `visor.setAuthenticated`}. | ||
* By default it redirect to next parameter if exists or to {@link visor.visorProvider#homeRoute `homeRoute`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthenticated = ["$location", "restrictedUrl", function ($location, restrictedUrl) { | ||
$location.url(addNextToUrl(config.loginRoute, $location, restrictedUrl)) | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doAfterManualAuthentication | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action to take after a user is authenticated using {@link visor.visor#setAuthenticated `visor.setAuthenticated`}. | ||
* By default it redirect to next parameter if exists or to {@link visor.visorProvider#homeRoute `homeRoute`}. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to a new user welcome page | ||
@@ -261,24 +260,24 @@ * visorProvider.doAfterManualAuthentication = function($state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doAfterManualAuthentication = ["$location",function($location){ | ||
$location.url($location.search().next || config.homeRoute); | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthorized | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action taken when an already authenticated user tries to access a route he is not allowed to view. | ||
* By default it redirect to {@link visor.visorProvider#notAuthorizedRoute `notAuthorizedRoute`}. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* </pre> | ||
*/ | ||
config.doAfterManualAuthentication = ["$location", function ($location) { | ||
$location.url($location.search().next || config.homeRoute); | ||
}]; | ||
/** | ||
* @ngdoc function | ||
* @name visor.visorProvider#doOnNotAuthorized | ||
* @propertyOf visor.visorProvider | ||
* | ||
* @description | ||
* | ||
* The action taken when an already authenticated user tries to access a route he is not allowed to view. | ||
* By default it redirect to {@link visor.visorProvider#notAuthorizedRoute `notAuthorizedRoute`}. | ||
* | ||
* The url that was restricted is provided by an injected argument named `restrictedUrl` | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* //redirect to an error page with the restricted url message | ||
@@ -291,171 +290,176 @@ * visorProvider.doOnNotAuthorized = function(restrictedUrl,$state){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthorized =["$location",function($location){ | ||
$location.url(config.notAuthorizedRoute) | ||
}]; | ||
* </pre> | ||
*/ | ||
config.doOnNotAuthorized = ["$location", function ($location) { | ||
$location.url(config.notAuthorizedRoute) | ||
}]; | ||
/** | ||
* @ngdoc service | ||
* @name visor.visor | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* @description | ||
* | ||
* `visor` is an authentication and authorization service to be used alongside ngRoute or ui-router. | ||
* | ||
* It handles authentication while {@link visor.permissions.visorPermissions `visorPermissions`} handles routing and | ||
* restrciting access. | ||
* | ||
* To use first define how visor is to authenticate by setting `visor.authenticate`, and then add | ||
* restriction functions to routes/states. | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
* return $http.get("/api/user/me").then(function(res){ | ||
* return res.data; | ||
* }) | ||
* }; | ||
* $stateProvider.state("private",{ | ||
* restrict: function(user){ return user && user.can_see_private;} | ||
* }) | ||
* } | ||
* </pre> | ||
*/ | ||
this.$get = ["$injector","$q","$rootScope","$location","visorPermissions",function($injector,$q,$rootScope,$location,visorPermissions){ | ||
// keeps the original auth promise so we won't call authenticate twice. | ||
var _authenticationPromise = false; | ||
function onAuthenticationSuccess(authData) { | ||
Visor.authData =authData; | ||
visorPermissions.invokeParameters = [Visor.authData]; | ||
} | ||
function onAuthenticationFailed(){ | ||
Visor.authData = undefined; | ||
visorPermissions.invokeParameters = []; | ||
} | ||
var Visor = { | ||
/** | ||
* | ||
* Authenticate with visor. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {boolean} retry If true, will force reauthentication. Otherwise, the second call to authenticate will | ||
* return the result of the previous authentication call. | ||
* | ||
* @returns {promise} Promise that will always resolve as true, with the value returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* If {@link visor.visorProvider#authenticate `visorProvider.authenticate`} failed, the promise will resolve with `undefined`. | ||
*/ | ||
authenticate:function(retry){ | ||
if (_authenticationPromise && !retry) { | ||
return _authenticationPromise; | ||
} | ||
var deferred = $q.defer(); | ||
_authenticationPromise = deferred.promise; | ||
$injector.invoke(config.authenticate) | ||
.then(onAuthenticationSuccess,onAuthenticationFailed) | ||
['finally'](function(){ | ||
deferred.resolve(Visor.authData) | ||
}); | ||
return deferred.promise; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @ngdoc service | ||
* @name visor.visor | ||
* @description | ||
* | ||
* @requires visor.visorPermissions | ||
* @requires visor.delayLocationChange | ||
* | ||
* Notify `visor` that an authentication was successful. | ||
* | ||
* Typical use is to call this function after a use logs in to the system. | ||
* | ||
* <div class="alert alert-info"> | ||
* **Note**: `authData` should be the identical to the result of the promise returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* </div> | ||
* | ||
* | ||
* @param {Any} authData The authentication data to be used in future restrict functions. | ||
*/ | ||
setAuthenticated: function(authData){ | ||
onAuthenticationSuccess(authData); | ||
_authenticationPromise = $q.when(authData); | ||
$injector.invoke(config.doAfterManualAuthentication,null,{authData:authData}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#isAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* Determine if user was successfuly authenticated. | ||
* `visor` is an authentication and authorization service to be used alongside ngRoute or ui-router. | ||
* | ||
* It handles authentication while {@link visor.permissions.visorPermissions `visorPermissions`} handles routing and | ||
* restrciting access. | ||
* | ||
* @returns {boolean} True if the user was authenticated. False otherwise. | ||
*/ | ||
isAuthenticated: function(){ | ||
return !!Visor.authData; | ||
}, | ||
/** | ||
* To use first define how visor is to authenticate by setting `visor.authenticate`, and then add | ||
* restriction functions to routes/states. | ||
* | ||
* Notify visor that a use tried to access a url that is restricted to it. | ||
* @example | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {string} restrictedUrl The url that the user was restricted access to. | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor"]) | ||
* .config(function(visorProvider,$stateProvider){ | ||
* visorProvider.authenticate = function($http){ | ||
* return $http.get("/api/user/me").then(function(res){ | ||
* return res.data; | ||
* }) | ||
* }; | ||
* $stateProvider.state("private",{ | ||
* restrict: function(user){ return user && user.can_see_private;} | ||
* }) | ||
* } | ||
* </pre> | ||
*/ | ||
onNotAllowed: function(restrictedUrl){ | ||
if (Visor.isAuthenticated()) { | ||
$injector.invoke(config.doOnNotAuthorized,null,{restrictedUrl:restrictedUrl}); | ||
} else { | ||
$injector.invoke(config.doOnNotAuthenticated,null,{restrictedUrl:restrictedUrl}); | ||
this.$get = ["$injector", "$q", "$rootScope", "$location", "visorPermissions", function ($injector, $q, $rootScope, $location, visorPermissions) { | ||
// keeps the original auth promise so we won't call authenticate twice. | ||
var _authenticationPromise = false; | ||
function onAuthenticationSuccess(authData) { | ||
Visor.authData = authData; | ||
visorPermissions.invokeParameters = [Visor.authData]; | ||
visorPermissions.clearPermissionCache(); | ||
} | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setUnauthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that a user is no longer authenticated. | ||
* | ||
* Typical use is to call this function after a user logs out of the system. | ||
*/ | ||
setUnauthenticated: function(){ | ||
onAuthenticationFailed() | ||
}, | ||
config: config | ||
}; | ||
return Visor; | ||
}] | ||
}]) | ||
.run(["visor","delayLocationChange",function(visor,delayLocationChange){ | ||
if (visor.config.authenticateOnStartup) { | ||
delayLocationChange(visor.authenticate()) | ||
} | ||
}]) | ||
.config(["visorPermissionsProvider",function(visorPermissionsProvider){ | ||
visorPermissionsProvider.doBeforeFirstCheck.push(["visor",function(Visor){ | ||
return Visor.authenticate(); | ||
}]); | ||
visorPermissionsProvider.onNotAllowed = ["visor","restrictedUrl",function(Visor,restrictedUrl){ | ||
Visor.onNotAllowed(restrictedUrl); | ||
}] | ||
}]) | ||
function onAuthenticationFailed() { | ||
Visor.authData = undefined; | ||
visorPermissions.invokeParameters = []; | ||
visorPermissions.clearPermissionCache(); | ||
} | ||
var Visor = { | ||
/** | ||
* | ||
* Authenticate with visor. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {boolean} retry If true, will force reauthentication. Otherwise, the second call to authenticate will | ||
* return the result of the previous authentication call. | ||
* | ||
* @returns {promise} Promise that will always resolve as true, with the value returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* If {@link visor.visorProvider#authenticate `visorProvider.authenticate`} failed, the promise will resolve with `undefined`. | ||
*/ | ||
authenticate: function (retry) { | ||
if (_authenticationPromise && !retry) { | ||
return _authenticationPromise; | ||
} | ||
var deferred = $q.defer(); | ||
_authenticationPromise = deferred.promise; | ||
$injector.invoke(config.authenticate) | ||
.then(onAuthenticationSuccess, onAuthenticationFailed) | ||
['finally'](function () { | ||
deferred.resolve(Visor.authData) | ||
}); | ||
return deferred.promise; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that an authentication was successful. | ||
* | ||
* Typical use is to call this function after a use logs in to the system. | ||
* | ||
* <div class="alert alert-info"> | ||
* **Note**: `authData` should be the identical to the result of the promise returned from {@link visor.visorProvider#authenticate `visorProvider.authenticate`}. | ||
* </div> | ||
* | ||
* | ||
* @param {Any} authData The authentication data to be used in future restrict functions. | ||
*/ | ||
setAuthenticated: function (authData) { | ||
onAuthenticationSuccess(authData); | ||
_authenticationPromise = $q.when(authData); | ||
$injector.invoke(config.doAfterManualAuthentication, null, {authData: authData}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#isAuthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* Determine if user was successfuly authenticated. | ||
* | ||
* | ||
* @returns {boolean} True if the user was authenticated. False otherwise. | ||
*/ | ||
isAuthenticated: function () { | ||
return !!Visor.authData; | ||
}, | ||
/** | ||
* | ||
* Notify visor that a use tried to access a url that is restricted to it. | ||
* | ||
* **Note**: This function was intended for internal use. | ||
* | ||
* | ||
* @param {string} restrictedUrl The url that the user was restricted access to. | ||
* | ||
*/ | ||
onNotAllowed: function (restrictedUrl) { | ||
if (Visor.isAuthenticated()) { | ||
$injector.invoke(config.doOnNotAuthorized, null, {restrictedUrl: restrictedUrl}); | ||
} else { | ||
$injector.invoke(config.doOnNotAuthenticated, null, {restrictedUrl: restrictedUrl}); | ||
} | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.visor#setUnauthenticated | ||
* @methodOf visor.visor | ||
* | ||
* @description | ||
* | ||
* | ||
* Notify `visor` that a user is no longer authenticated. | ||
* | ||
* Typical use is to call this function after a user logs out of the system. | ||
*/ | ||
setUnauthenticated: function () { | ||
onAuthenticationFailed() | ||
}, | ||
config: config | ||
}; | ||
return Visor; | ||
}] | ||
}]) | ||
.run(["visor", "delayLocationChange", function (visor, delayLocationChange) { | ||
if (visor.config.authenticateOnStartup) { | ||
delayLocationChange(visor.authenticate()) | ||
} | ||
}]) | ||
.config(["visorPermissionsProvider", function (visorPermissionsProvider) { | ||
visorPermissionsProvider.doBeforeFirstCheck.push(["visor", function (Visor) { | ||
return Visor.authenticate(); | ||
}]); | ||
visorPermissionsProvider.onNotAllowed = ["visor", "restrictedUrl", function (Visor, restrictedUrl) { | ||
Visor.onNotAllowed(restrictedUrl); | ||
}] | ||
}]) | ||
})(); |
@@ -1,29 +0,42 @@ | ||
(function(){ | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ngRoute | ||
* @description | ||
* | ||
* # Visor.ngRoute | ||
* | ||
* `Visor.ngRoute` automatically add supports for permissions in ngRoute, if ngRoute exists. | ||
* | ||
*/ | ||
angular.module('visor.ngRoute',['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions','$injector',function($rootScope, visorPermissions,$injector){ | ||
var ngRouteModuleExists = false; | ||
try { | ||
$injector.get("$route"); | ||
ngRouteModuleExists = true; | ||
}catch (e){} | ||
if (ngRouteModuleExists) { | ||
$rootScope.$on('$routeChangeStart', function(e,next){ | ||
next.resolve = next.resolve || {}; | ||
visorPermissions.onRouteChange(next,function delayChange(promise){ | ||
next.resolve._visorDelay = function(){return promise;}; | ||
}); | ||
}); | ||
} | ||
}]) | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ngRoute | ||
* @description | ||
* | ||
* # Visor.ngRoute | ||
* | ||
* `Visor.ngRoute` automatically add supports for permissions in ngRoute, if ngRoute exists. | ||
* | ||
*/ | ||
angular.module('visor.ngRoute', ['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions', '$injector', function ($rootScope, visorPermissions, $injector) { | ||
var ngRouteModuleExists = false; | ||
var $route = null; | ||
try { | ||
$route = $injector.get("$route"); | ||
ngRouteModuleExists = true; | ||
} catch (e) { | ||
} | ||
if (ngRouteModuleExists) { | ||
visorPermissions.getRoute = function (routeId) { | ||
for (var path in $route.routes) { | ||
var route = $route.routes[path]; | ||
if (route.regexp.exec(routeId)) { | ||
return route; | ||
} | ||
} | ||
return null; | ||
}; | ||
$rootScope.$on('$routeChangeStart', function (e, next) { | ||
next.resolve = next.resolve || {}; | ||
visorPermissions.onRouteChange(next, function delayChange(promise) { | ||
next.resolve._visorDelay = function () { | ||
return promise; | ||
}; | ||
}); | ||
}); | ||
} | ||
}]) | ||
})(); |
@@ -1,17 +0,17 @@ | ||
(function(){ | ||
/** | ||
* @ngdoc overview | ||
* @name visor.permissions | ||
* @description | ||
* | ||
* # Visor.Permissions | ||
* | ||
* `Visor.Permissions` provides support for handling permissions and restricting access to routes based | ||
* on those restrictions. | ||
* | ||
* | ||
* See {@link visor.permissions.visorPermissions `visorPermissions service`} for usage. | ||
*/ | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.permissions | ||
* @description | ||
* | ||
* # Visor.Permissions | ||
* | ||
* `Visor.Permissions` provides support for handling permissions and restricting access to routes based | ||
* on those restrictions. | ||
* | ||
* | ||
* See {@link visor.permissions.visorPermissions `visorPermissions service`} for usage. | ||
*/ | ||
angular.module("visor.permissions",[]) | ||
angular.module("visor.permissions", []) | ||
@@ -34,38 +34,38 @@ /** | ||
*/ | ||
.provider("visorPermissions",[function(){ | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* A function that determines how permissions should be resolved from a route object. | ||
* It receives the `next` route object as the only parameter and must return a `permission function`, | ||
* or an Array of `permission functions`. | ||
* | ||
* A route object is an object that is sent to | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange }. | ||
* This configuration should be set by the same plugin that calls | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange } to guarantee compatibility. | ||
* | ||
* A `permission function` is a function that receives {@link visor.permissions.VisorPermissions.invokeParameters} | ||
* and returns a boolean indicating whether a route change should occur (I.E. the user has permission to access | ||
* the route) | ||
* | ||
* Default: a function that returns the permission function that is in the `next` route object's `restrict` | ||
* attribute (if any). | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#getPermissionsFromNext} | ||
* @example | ||
* | ||
* <pre> | ||
* // a plugin module that will allow all paths to go through | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
.provider("visorPermissions", [function () { | ||
var config = this; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* A function that determines how permissions should be resolved from a route object. | ||
* It receives the `next` route object as the only parameter and must return a `permission function`, | ||
* or an Array of `permission functions`. | ||
* | ||
* A route object is an object that is sent to | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange }. | ||
* This configuration should be set by the same plugin that calls | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange } to guarantee compatibility. | ||
* | ||
* A `permission function` is a function that receives {@link visor.permissions.VisorPermissions.invokeParameters} | ||
* and returns a boolean indicating whether a route change should occur (I.E. the user has permission to access | ||
* the route) | ||
* | ||
* Default: a function that returns the permission function that is in the `next` route object's `restrict` | ||
* attribute (if any). | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#getPermissionsFromNext} | ||
* @example | ||
* | ||
* <pre> | ||
* // a plugin module that will allow all paths to go through | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* visorPermissionsProvider.getPermissionsFromNext = function(next){ | ||
@@ -77,26 +77,26 @@ * return function(){ | ||
* }); | ||
* </pre> | ||
*/ | ||
config.getPermissionsFromNext = function(next){ | ||
return next.restrict? [next.restrict] : []; | ||
}; | ||
* </pre> | ||
*/ | ||
config.getPermissionsFromNext = function (next) { | ||
return next.restrict ? [next.restrict] : []; | ||
}; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#doBeforeFirstCheck | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* | ||
* A list of functions to run before the first permission check is performed (I.E. the first time a route that | ||
* requires permissions is navigated to). | ||
* These functions must return a promise. | ||
* | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#doBeforeFirstCheck | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* | ||
* A list of functions to run before the first permission check is performed (I.E. the first time a route that | ||
* requires permissions is navigated to). | ||
* These functions must return a promise. | ||
* | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* visorPermissionsProvider.doBeforeFirstCheck.push(["$http",function($http){ | ||
@@ -106,151 +106,262 @@ * return $http.get("/do/something"); | ||
* }); | ||
* </pre> | ||
*/ | ||
config.doBeforeFirstCheck = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#onNotAllowed | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function to call when a permission failed to validate. | ||
* | ||
* The function is injected, with local `restrictedUrl` containing the url navigated to. | ||
* | ||
*/ | ||
config.onNotAllowed = function(){}; | ||
* </pre> | ||
*/ | ||
config.doBeforeFirstCheck = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#onNotAllowed | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function to call when a permission failed to validate. | ||
* | ||
* The function is injected, with local `restrictedUrl` containing the url navigated to. | ||
* | ||
*/ | ||
config.onNotAllowed = function () { | ||
}; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* a list of values to send to each `permission function` to be used to determine if a route is allowed. | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#invokeParameters} | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* a list of values to send to each `permission function` to be used to determine if a route is allowed. | ||
* | ||
* Can also be changed at runtime by changing {@link visor.permissions.visorPermissions#invokeParameters} | ||
* | ||
* @example | ||
* | ||
* <pre> | ||
* angular.moudle("myModule",["visor.permissions"]) | ||
* .config(function(visorPermissionsProvider){ | ||
* var userInfo = {username:"theUser",isAdmin:false}; | ||
* visorPermissionsProvider.invokeParameters.push(userInfo); | ||
* }); | ||
* </pre> | ||
*/ | ||
config.invokeParameters = []; | ||
var finishedBeforeCheck = false; | ||
* </pre> | ||
*/ | ||
config.invokeParameters = []; | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissionsProvider#getRoute | ||
* @propertyOf visor.permissions.visorPermissionsProvider | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be changed by routing module plugins | ||
* </div> | ||
* | ||
* function that transforms a routeId to a route object that can be used in getPermissionsFromNext | ||
* | ||
*/ | ||
config.getRoute = function (routeId) { | ||
throw new Error("method not implemented"); | ||
} | ||
var finishedBeforeCheck = false; | ||
/** | ||
* @ngdoc service | ||
* @name visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* `visorPermissions` checks for permissions and notifies when a routes that isn't allowed is requested. | ||
* | ||
* In order to work, routing module plugins (such as the provided {@link visor.ngRoute visor.ngRoute} and | ||
* {@link visor.ui-router visor.ui-router} must configure `visorPermissions` and call | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange} when a route has changed. | ||
* | ||
*/ | ||
this.$get = ["$q","$injector","$location",function($q,$injector,$location){ | ||
/** | ||
* @ngdoc service | ||
* @name visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* `visorPermissions` checks for permissions and notifies when a routes that isn't allowed is requested. | ||
* | ||
* In order to work, routing module plugins (such as the provided {@link visor.ngRoute visor.ngRoute} and | ||
* {@link visor.ui-router visor.ui-router} must configure `visorPermissions` and call | ||
* {@link visor.permissions.visorPermissions#onRouteChange onRouteChange} when a route has changed. | ||
* | ||
*/ | ||
this.$get = ["$q", "$injector", "$location", function ($q, $injector, $location) { | ||
function handlePermission(next,permissions){ | ||
if (!angular.isArray(permissions)) { | ||
permissions = [permissions]; | ||
function checkPermissions(permissions) { | ||
if (!permissions || permissions.length === 0) { | ||
return true; | ||
} | ||
if (!angular.isArray(permissions)) { | ||
permissions = [permissions]; | ||
} | ||
var isAllowed = true; | ||
permissions.forEach(function (permission) { | ||
isAllowed = isAllowed && permission.apply(null, VisorPermissions.invokeParameters); | ||
}); | ||
return isAllowed; | ||
} | ||
var isAllowed = true; | ||
permissions.forEach(function(permission){ | ||
isAllowed = isAllowed && permission.apply(null,VisorPermissions.invokeParameters); | ||
}); | ||
if (isAllowed) { | ||
return true; | ||
} else { | ||
VisorPermissions.invokeNotAllowed(config.onNotAllowed); | ||
return false; | ||
function handlePermission(next, permissions) { | ||
var isAllowed = checkPermissions(permissions); | ||
if (isAllowed) { | ||
return true; | ||
} else { | ||
VisorPermissions.invokeNotAllowed(config.onNotAllowed); | ||
return false; | ||
} | ||
} | ||
} | ||
var VisorPermissions = { | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#onRouteChange | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be called by routing module plugins | ||
* </div> | ||
* | ||
* A function to be called when a route changes, triggers the route permission checks. | ||
* | ||
* @param {*} next route object to be sent to `permission functions`. | ||
* | ||
* @param {function} delayChange a function to be called if visorPermissions requires that the route | ||
* change be delayed. in such case the delayChange function will be called with a promise that will be | ||
* resolved or rejected depending on whether the route is allowed. | ||
* | ||
* @returns {Any} true if next is allowed, false if not allowed. a string containing "delayed" if | ||
* the check is delayed. | ||
*/ | ||
onRouteChange: function(next,delayChange){ | ||
var permissions = VisorPermissions.getPermissionsFromNext(next); | ||
if (!permissions || permissions.length == 0) { | ||
return true; // don't do beforeChecks without permissions | ||
} | ||
if (!finishedBeforeCheck) { | ||
var waitForMe = $q.defer(); | ||
delayChange(waitForMe.promise); | ||
$q.all(config.doBeforeFirstCheck.map(function(cb){ | ||
return $injector.invoke(cb) | ||
})) | ||
['finally'](function(){ | ||
finishedBeforeCheck = true; | ||
if (handlePermission(next,permissions)) { | ||
waitForMe.resolve(true); | ||
} else { | ||
waitForMe.reject(false); | ||
var onCacheClearListeners = []; | ||
var cachedRoutes = {}; | ||
var VisorPermissions = { | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#onRouteChange | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* <div class="alert alert-info"> | ||
* NOTE: should only be called by routing module plugins | ||
* </div> | ||
* | ||
* A function to be called when a route changes, triggers the route permission checks. | ||
* | ||
* @param {*} next route object to be sent to `permission functions`. | ||
* | ||
* @param {function} delayChange a function to be called if visorPermissions requires that the route | ||
* change be delayed. in such case the delayChange function will be called with a promise that will be | ||
* resolved or rejected depending on whether the route is allowed. | ||
* | ||
* @returns {Any} true if next is allowed, false if not allowed. a string containing "delayed" if | ||
* the check is delayed. | ||
*/ | ||
onRouteChange: function (next, delayChange) { | ||
var permissions = VisorPermissions.getPermissionsFromNext(next); | ||
if (!permissions || permissions.length == 0) { | ||
return true; // don't do beforeChecks without permissions | ||
} | ||
if (!finishedBeforeCheck) { | ||
var waitForMe = $q.defer(); | ||
delayChange(waitForMe.promise); | ||
$q.all(config.doBeforeFirstCheck.map(function (cb) { | ||
return $injector.invoke(cb) | ||
})) | ||
['finally'](function () { | ||
finishedBeforeCheck = true; | ||
if (handlePermission(next, permissions)) { | ||
waitForMe.resolve(true); | ||
} else { | ||
waitForMe.reject(false); | ||
} | ||
}); | ||
return "delayed"; | ||
} else { | ||
return handlePermission(next, permissions) | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
getPermissionsFromNext: config.getPermissionsFromNext, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#checkPermissionsForRoute | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* A function to check if a route is currently allowed or restricted | ||
* | ||
* Heavily uses caching | ||
* | ||
* @param {*} routeId an identifier for a route (depending on the routing module, could be a string, | ||
* regex or some kind of object) | ||
* | ||
* @returns {Boolean|undefined} true if route is allowed | ||
*/ | ||
checkPermissionsForRoute: function (routeId) { | ||
var result = cachedRoutes[routeId]; | ||
if (result !== undefined) { | ||
return result; | ||
} | ||
var route = VisorPermissions.getRoute(routeId); | ||
if (!route) { | ||
return undefined; | ||
} | ||
var permissions = VisorPermissions.getPermissionsFromNext(route); | ||
result = checkPermissions(permissions); | ||
cachedRoutes[routeId] = result; | ||
return result; | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#clearPermissionCache | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* Clears the cache used by checkPermissionsForRoute - should be called when | ||
* the permission context changes (I.E. after authentication) | ||
*/ | ||
clearPermissionCache: function () { | ||
cachedRoutes = {} | ||
onCacheClearListeners.forEach(function (handler) { | ||
handler && handler(); | ||
}); | ||
}, | ||
/** | ||
* @ngdoc function | ||
* @name visor.permissions.visorPermissions#notifyOnCacheClear | ||
* @methodOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* Notify handler when the permission cache used by checkPermissionsForRoute is cleared | ||
* | ||
* @param {function} handler the handler function to call | ||
* | ||
* @returns {function} a dereigster function | ||
*/ | ||
notifyOnCacheClear: function (handler) { | ||
onCacheClearListeners.push(handler); | ||
return function () { | ||
var i = onCacheClearListeners.indexOf(handler); | ||
if (i != -1) { | ||
onCacheClearListeners.splice(i, 1); | ||
} | ||
}); | ||
return "delayed"; | ||
} else { | ||
return handlePermission(next,permissions) | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getRoute | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getRoute getRoute}. | ||
*/ | ||
getRoute: config.getRoute, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.invokeParameters#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
invokeParameters: config.invokeParameters, | ||
invokeNotAllowed: function (notAllowedFn) { | ||
$injector.invoke(notAllowedFn, null, {restrictedUrl: $location.url()}) | ||
} | ||
}, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#getPermissionsFromNext | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.visorPermissionsProvider#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
getPermissionsFromNext: config.getPermissionsFromNext, | ||
/** | ||
* @ngdoc property | ||
* @name visor.permissions.visorPermissions#invokeParameters | ||
* @propertyOf visor.permissions.visorPermissions | ||
* | ||
* @description | ||
* | ||
* runtime configuration for {@link visor.permissions.invokeParameters#getPermissionsFromNext getPermissionsFromNext}. | ||
*/ | ||
invokeParameters:config.invokeParameters, | ||
invokeNotAllowed: function(notAllowedFn){$injector.invoke(notAllowedFn,null,{restrictedUrl:$location.url()})} | ||
}; | ||
return VisorPermissions; | ||
}] | ||
}]) | ||
}; | ||
return VisorPermissions; | ||
}] | ||
}]) | ||
})(); |
@@ -1,40 +0,44 @@ | ||
(function(){ | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ui-router | ||
* @description | ||
* | ||
* # Visor.ui-router | ||
* | ||
* `Visor.ui-router` automatically add supports for permissions in ui-router, if ui-router exists. | ||
* | ||
*/ | ||
angular.module('visor.ui-router',['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions','$injector','$timeout','$location',function($rootScope, visorPermissions,$injector,$timeout,$location){ | ||
var uiModuleExists = false; | ||
try { | ||
$injector.get("$state"); | ||
uiModuleExists = true; | ||
}catch (e){} | ||
if (uiModuleExists) { | ||
$injector.invoke(["$state",function($state){ | ||
// we need to check parent states for permissions as well | ||
visorPermissions.getPermissionsFromNext = function(next){ | ||
var perms = []; | ||
while(next) { | ||
if (next.restrict) perms.unshift(next.restrict); | ||
if (next.parent) { | ||
next = $state.get(next.parent) | ||
} else if(next.name.indexOf(".") >0) { | ||
next = $state.get(next.name.replace(/(.*\.)?([^.]+)\.[^.]*$/,"$2")) | ||
} else { | ||
next = null; | ||
} | ||
} | ||
return perms; | ||
} | ||
var $urlRouter = $injector.get("$urlRouter"); | ||
(function () { | ||
/** | ||
* @ngdoc overview | ||
* @name visor.ui-router | ||
* @description | ||
* | ||
* # Visor.ui-router | ||
* | ||
* `Visor.ui-router` automatically add supports for permissions in ui-router, if ui-router exists. | ||
* | ||
*/ | ||
angular.module('visor.ui-router', ['visor.permissions']) | ||
.run(['$rootScope', 'visorPermissions', '$injector', '$timeout', '$location', function ($rootScope, visorPermissions, $injector, $timeout, $location) { | ||
var uiModuleExists = false; | ||
try { | ||
$injector.get('$state'); | ||
uiModuleExists = true; | ||
} catch (e) { | ||
} | ||
if (uiModuleExists) { | ||
$injector.invoke(['$state', function ($state) { | ||
// we need to check parent states for permissions as well | ||
visorPermissions.getPermissionsFromNext = function (next) { | ||
var perms = []; | ||
while (next) { | ||
if (next.restrict) perms.unshift(next.restrict); | ||
if (next.parent) { | ||
next = $state.get(next.parent) | ||
} else if (next.name.indexOf('.') > 0) { | ||
var chain = next.name.split('.'); | ||
chain.pop(); //remove the leftmost | ||
var parent = chain.join('.'); | ||
next = $state.get(parent); | ||
} else { | ||
next = null; | ||
} | ||
} | ||
return perms; | ||
}; | ||
var $urlRouter = $injector.get('$urlRouter'); | ||
var toUrl = null; | ||
var bypass = false; | ||
$rootScope.$on('$stateChangeStart', function(e,toState,toParams){ | ||
$rootScope.$on('$stateChangeStart', function (e, toState, toParams) { | ||
if (bypass) { | ||
@@ -44,25 +48,28 @@ bypass = false; | ||
} | ||
toUrl = $state.href(toState,toParams).replace(/^#/,''); | ||
var shouldContinue = visorPermissions.onRouteChange(toState,function delayChange(promise){ | ||
promise.then(function(){ | ||
bypass= true; | ||
$state.go(toState,toParams); | ||
toUrl = $state.href(toState, toParams).replace(/^#/, ''); | ||
var shouldContinue = visorPermissions.onRouteChange(toState, function delayChange(promise) { | ||
promise.then(function () { | ||
bypass = true; | ||
$state.go(toState, toParams); | ||
}) | ||
}); | ||
if (!shouldContinue || shouldContinue === "delayed") { | ||
if (!shouldContinue || shouldContinue === 'delayed') { | ||
e.preventDefault(); | ||
} | ||
}); | ||
visorPermissions.invokeNotAllowed = function(notAllowed){ | ||
visorPermissions.invokeNotAllowed = function (notAllowed) { | ||
//timeout is required because when using preventDefault on $stateChangeStart, the url is | ||
//reverted to it's original location, and no change at this time will override this. | ||
$timeout(function(){ | ||
$injector.invoke(notAllowed,null,{restrictedUrl:toUrl}) | ||
},0); | ||
$timeout(function () { | ||
$injector.invoke(notAllowed, null, {restrictedUrl: toUrl}) | ||
}, 0); | ||
} | ||
}]); | ||
visorPermissions.getRoute = function (routeId) { | ||
return $state.get(routeId); | ||
}; | ||
}]); | ||
} | ||
}]) | ||
} | ||
}]) | ||
})(); |
@@ -1,29 +0,29 @@ | ||
module.exports = function(config){ | ||
config.set({ | ||
module.exports = function (config) { | ||
config.set({ | ||
files : [ | ||
'bower_components/angular/angular.js', | ||
'bower_components/angular-mocks/angular-mocks.js', | ||
'bower_components/angular-ui-router/release/angular-ui-router.js', | ||
'bower_components/angular-route/angular-route.js', | ||
'src/**/*.js', | ||
'test/unit/**/*.js' | ||
], | ||
files: [ | ||
'bower_components/angular/angular.js', | ||
'bower_components/angular-mocks/angular-mocks.js', | ||
'bower_components/angular-ui-router/release/angular-ui-router.js', | ||
'bower_components/angular-route/angular-route.js', | ||
'src/**/*.js', | ||
'test/unit/**/*.js' | ||
], | ||
autoWatch : false, | ||
singleRun: true, | ||
basePath:"../", | ||
autoWatch: false, | ||
singleRun: true, | ||
basePath: "../", | ||
reporters: ['spec'], | ||
frameworks: ['jasmine'], | ||
reporters: ['spec'], | ||
frameworks: ['jasmine'], | ||
browsers : ['PhantomJS'], | ||
browsers: ['PhantomJS'], | ||
plugins : [ | ||
plugins: [ | ||
'karma-phantomjs-launcher', | ||
'karma-jasmine', | ||
'karma-spec-reporter' | ||
] | ||
] | ||
}); | ||
}); | ||
}; |
@@ -1,26 +0,28 @@ | ||
describe("delayLocationChange",function(){ | ||
var firstCalled,secondCalled; | ||
var firstDefer,secondDefer; | ||
angular.module("delayLocationChange.test",["delayLocationChange"]) | ||
.run(function(delayLocationChange,$q){ | ||
firstCalled = secondCalled = 0; | ||
firstDefer = $q.defer(); | ||
secondDefer = $q.defer(); | ||
delayLocationChange(firstDefer.promise); | ||
delayLocationChange(secondDefer.promise); | ||
}) | ||
beforeEach(module("delayLocationChange.test")); | ||
describe("delayLocationChange", function () { | ||
var firstCalled, secondCalled; | ||
var firstDefer, secondDefer; | ||
angular.module("delayLocationChange.test", ["delayLocationChange"]) | ||
.run(function (delayLocationChange, $q) { | ||
firstCalled = secondCalled = 0; | ||
firstDefer = $q.defer(); | ||
secondDefer = $q.defer(); | ||
delayLocationChange(firstDefer.promise); | ||
delayLocationChange(secondDefer.promise); | ||
}) | ||
beforeEach(module("delayLocationChange.test")); | ||
it("should stop location change until promises is resolved",inject(function($location,$rootScope){ | ||
var successes = 0; | ||
$rootScope.$on("$locationChangeSuccess",function(){successes++; }); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(0); | ||
firstDefer.resolve(); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(0); | ||
secondDefer.resolve(); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(1); | ||
})); | ||
it("should stop location change until promises is resolved", inject(function ($location, $rootScope) { | ||
var successes = 0; | ||
$rootScope.$on("$locationChangeSuccess", function () { | ||
successes++; | ||
}); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(0); | ||
firstDefer.resolve(); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(0); | ||
secondDefer.resolve(); | ||
$rootScope.$apply(); | ||
expect(successes).toEqual(1); | ||
})); | ||
}); |
@@ -1,50 +0,77 @@ | ||
describe("visor.ngRoute",function(){ | ||
describe("route change",function(){ | ||
var defer = null; | ||
angular.module("test.routes",['visor.ngRoute','ngRoute']).config(function($routeProvider,visorPermissionsProvider){ | ||
defer = null; | ||
$routeProvider.when("/first",{ | ||
restrict: function (){return true;} | ||
}).when("/deny",{ | ||
restrict: function(){return false;} | ||
}); | ||
visorPermissionsProvider.doBeforeFirstCheck.push(function($q){defer = $q.defer(); return defer.promise;}); | ||
}); | ||
beforeEach(module("test.routes")); | ||
describe("visor.ngRoute", function () { | ||
describe("route change", function () { | ||
var defer = null; | ||
angular.module("test.routes", ['visor.ngRoute', 'ngRoute']).config(function ($routeProvider, visorPermissionsProvider) { | ||
defer = null; | ||
$routeProvider.when("/first", { | ||
restrict: function () { | ||
return true; | ||
} | ||
}).when("/deny", { | ||
restrict: function () { | ||
return false; | ||
} | ||
}); | ||
visorPermissionsProvider.doBeforeFirstCheck.push(function ($q) { | ||
defer = $q.defer(); | ||
return defer.promise; | ||
}); | ||
}); | ||
beforeEach(module("test.routes")); | ||
it("should wait to change route until delay called",inject(function($location,$route,$rootScope){ | ||
var successCounter = 0; | ||
$rootScope.$on("$routeChangeSuccess",function(){successCounter++;}); | ||
$location.url("/first"); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(0); | ||
defer.resolve(""); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(1); | ||
expect($location.url()).toEqual("/first"); | ||
expect($route.current.originalPath).toEqual("/first"); | ||
})); | ||
it("should wait to change route until delay called", inject(function ($location, $route, $rootScope) { | ||
var successCounter = 0; | ||
$rootScope.$on("$routeChangeSuccess", function () { | ||
successCounter++; | ||
}); | ||
$location.url("/first"); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(0); | ||
defer.resolve(""); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(1); | ||
expect($location.url()).toEqual("/first"); | ||
expect($route.current.originalPath).toEqual("/first"); | ||
})); | ||
it("should stop route change when permission rejected",inject(function($location,$route,$rootScope){ | ||
var errorCounter = 0; | ||
$rootScope.$on("$routeChangeError",function(){errorCounter++;}); | ||
expect(errorCounter).toEqual(0); | ||
$location.url("/deny"); | ||
$rootScope.$apply(); | ||
defer.resolve(false); | ||
$rootScope.$apply(); | ||
expect(errorCounter).toEqual(1); | ||
})); | ||
it("should stop route change when permission rejected", inject(function ($location, $route, $rootScope) { | ||
var errorCounter = 0; | ||
$rootScope.$on("$routeChangeError", function () { | ||
errorCounter++; | ||
}); | ||
expect(errorCounter).toEqual(0); | ||
$location.url("/deny"); | ||
$rootScope.$apply(); | ||
defer.resolve(false); | ||
$rootScope.$apply(); | ||
expect(errorCounter).toEqual(1); | ||
})); | ||
it('should check the correct route with getPermissionsForRoute', inject(function ($rootScope, visorPermissions) { | ||
var permission = visorPermissions.checkPermissionsForRoute('/deny'); | ||
expect(permission).toEqual(false); | ||
permission = visorPermissions.checkPermissionsForRoute('/first'); | ||
expect(permission).toEqual(true); | ||
})); | ||
it('should return undefined when route does not exist', inject(function ($rootScope, visorPermissions) { | ||
var result = visorPermissions.checkPermissionsForRoute('zzz'); | ||
$rootScope.$apply(); | ||
expect(result).toEqual(undefined); | ||
})); | ||
}); | ||
it("should not change anything if ng-route is not depended on",function(){ | ||
module("visor.permissions","visor.ngRoute"); | ||
inject(function($location,$rootScope,visorPermissions){ | ||
$location.url("/something"); | ||
$rootScope.$apply(); | ||
visorPermissions.onRouteChange({restrict:function(){return false;}},function(){}); | ||
$rootScope.$apply(); | ||
//nothing crushed! | ||
}) | ||
}); | ||
it("should not change anything if ng-route is not depended on", function () { | ||
module("visor.permissions", "visor.ngRoute"); | ||
inject(function ($location, $rootScope, visorPermissions) { | ||
$location.url("/something"); | ||
$rootScope.$apply(); | ||
visorPermissions.onRouteChange({ | ||
restrict: function () { | ||
return false; | ||
} | ||
}, function () { | ||
}); | ||
$rootScope.$apply(); | ||
//nothing crushed! | ||
}) | ||
}); | ||
}); |
@@ -1,22 +0,29 @@ | ||
var VOID = function(){return true}; | ||
var NEXT = {restrict:VOID}; | ||
var VOID = function () { | ||
return true | ||
}; | ||
var NEXT = {restrict: VOID}; | ||
describe("visor.permissions",function(){ | ||
describe("doBeforeFirstCheck",function(){ | ||
describe("visor.permissions", function () { | ||
describe("doBeforeFirstCheck", function () { | ||
var doBeforeFunctions = []; | ||
angular.module("config.do-before",["visor.permissions"]).config(function(visorPermissionsProvider){ | ||
angular.module("config.do-before", ["visor.permissions"]).config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.doBeforeFirstCheck = | ||
visorPermissionsProvider.doBeforeFirstCheck.concat(doBeforeFunctions) | ||
}) | ||
beforeEach(function(){doBeforeFunctions = [];}); | ||
beforeEach(function () { | ||
doBeforeFunctions = []; | ||
}); | ||
it("should call doBeforeFirstCheck on first change",function(){ | ||
var called = false; | ||
doBeforeFunctions.push(function($q){called = true;return $q.when("");}); | ||
it("should call doBeforeFirstCheck on first change", function () { | ||
var called = false; | ||
doBeforeFunctions.push(function ($q) { | ||
called = true; | ||
return $q.when(""); | ||
}); | ||
module("config.do-before"); | ||
inject(function($rootScope,visorPermissions){ | ||
inject(function ($rootScope, visorPermissions) { | ||
$rootScope.$apply(); | ||
expect(called).toEqual(false); | ||
visorPermissions.onRouteChange(NEXT,VOID) | ||
visorPermissions.onRouteChange(NEXT, VOID) | ||
$rootScope.$apply(); | ||
@@ -26,12 +33,18 @@ expect(called).toEqual(true); | ||
}); | ||
it("should call multiple doBeforeFirstCheck",function(){ | ||
it("should call multiple doBeforeFirstCheck", function () { | ||
var calledOne = false, calledTwo = false; | ||
doBeforeFunctions.push(function($q){calledOne = true;return $q.when("");}); | ||
doBeforeFunctions.push(function($q){calledTwo = true;return $q.when("");}); | ||
doBeforeFunctions.push(function ($q) { | ||
calledOne = true; | ||
return $q.when(""); | ||
}); | ||
doBeforeFunctions.push(function ($q) { | ||
calledTwo = true; | ||
return $q.when(""); | ||
}); | ||
module("config.do-before"); | ||
inject(function($rootScope,visorPermissions){ | ||
inject(function ($rootScope, visorPermissions) { | ||
$rootScope.$apply(); | ||
expect(calledOne).toEqual(false); | ||
expect(calledTwo).toEqual(false); | ||
visorPermissions.onRouteChange(NEXT,VOID); | ||
visorPermissions.onRouteChange(NEXT, VOID); | ||
$rootScope.$apply(); | ||
@@ -42,10 +55,18 @@ expect(calledOne).toEqual(true); | ||
}); | ||
it("should wait until doBeforeFirstCheck finishes before checking permissions",function(){ | ||
it("should wait until doBeforeFirstCheck finishes before checking permissions", function () { | ||
var defer = null, called = false; | ||
doBeforeFunctions.push(function($q){defer = $q.defer(); return defer.promise;}); | ||
doBeforeFunctions.push(function ($q) { | ||
defer = $q.defer(); | ||
return defer.promise; | ||
}); | ||
module("config.do-before"); | ||
inject(function($rootScope,visorPermissions,$q){ | ||
var next = {restrict:function(){called = true;return true;}}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
inject(function ($rootScope, visorPermissions, $q) { | ||
var next = { | ||
restrict: function () { | ||
called = true; | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
@@ -58,12 +79,15 @@ expect(called).toEqual(false); | ||
}) | ||
it("should not call doBeforeFirstCheck again",function(){ | ||
it("should not call doBeforeFirstCheck again", function () { | ||
var calledCount = 0; | ||
doBeforeFunctions.push(function($q){calledCount++;return $q.when("");}); | ||
doBeforeFunctions.push(function ($q) { | ||
calledCount++; | ||
return $q.when(""); | ||
}); | ||
module("config.do-before"); | ||
inject(function($rootScope,visorPermissions){ | ||
visorPermissions.onRouteChange(NEXT,VOID) | ||
inject(function ($rootScope, visorPermissions) { | ||
visorPermissions.onRouteChange(NEXT, VOID) | ||
$rootScope.$apply(); | ||
expect(calledCount).toEqual(1); | ||
visorPermissions.onRouteChange(NEXT,VOID) | ||
visorPermissions.onRouteChange(NEXT, VOID) | ||
$rootScope.$apply(); | ||
@@ -73,9 +97,14 @@ expect(calledCount).toEqual(1); | ||
}); | ||
it("should allow having no doBeforeFirstCheck",function(){ | ||
it("should allow having no doBeforeFirstCheck", function () { | ||
var called = false; | ||
module("visor.permissions"); | ||
inject(function($rootScope,visorPermissions){ | ||
var next = {restrict:function(){called = true;return true;}}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
restrict: function () { | ||
called = true; | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
@@ -86,11 +115,16 @@ expect(called).toEqual(true); | ||
it("should call doBeforeFirstCheck only when first accessing route with permission",function(){ | ||
it("should call doBeforeFirstCheck only when first accessing route with permission", function () { | ||
var called = false; | ||
module("visor.permissions"); | ||
inject(function($rootScope,visorPermissions){ | ||
var next = {restrict:function(){called = true;return true;}}; | ||
visorPermissions.onRouteChange({},VOID); | ||
inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
restrict: function () { | ||
called = true; | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange({}, VOID); | ||
$rootScope.$apply(); | ||
expect(called).toEqual(false); | ||
visorPermissions.onRouteChange(next,VOID); | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
@@ -102,24 +136,40 @@ expect(called).toEqual(true); | ||
describe("allowed/notallowed",function(){ | ||
beforeEach(function(){ | ||
angular.module("test.allowed-notallowed",["visor.permissions"]) | ||
.config(function(visorPermissionsProvider){ | ||
visorPermissionsProvider.getPermissionsFromNext = function(next){ | ||
return next.permissions || [next.permission]; | ||
} | ||
}); | ||
module("test.allowed-notallowed") | ||
}); | ||
describe("allowed/notallowed", function () { | ||
beforeEach(function () { | ||
angular.module("test.allowed-notallowed", ["visor.permissions"]) | ||
.config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.getPermissionsFromNext = function (next) { | ||
return next.permissions || [next.permission]; | ||
} | ||
}); | ||
module("test.allowed-notallowed") | ||
}); | ||
it("should allow access if permission returns true",inject(function($rootScope,visorPermissions){ | ||
var success =false; | ||
var next = {permission:function(){return true;}}; | ||
visorPermissions.onRouteChange(next,function(promise){promise.then(function(){success=true;})}); | ||
it("should allow access if permission returns true", inject(function ($rootScope, visorPermissions) { | ||
var success = false; | ||
var next = { | ||
permission: function () { | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, function (promise) { | ||
promise.then(function () { | ||
success = true; | ||
}) | ||
}); | ||
$rootScope.$apply(); | ||
expect(success).toEqual(true); | ||
})); | ||
it("should deny access if permission returns false",inject(function($rootScope,visorPermissions){ | ||
var rejected =false; | ||
var next = {permission:function(){return false;}}; | ||
visorPermissions.onRouteChange(next,function(promise){promise.catch(function(){rejected=true;})}); | ||
it("should deny access if permission returns false", inject(function ($rootScope, visorPermissions) { | ||
var rejected = false; | ||
var next = { | ||
permission: function () { | ||
return false; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, function (promise) { | ||
promise.catch(function () { | ||
rejected = true; | ||
}) | ||
}); | ||
$rootScope.$apply(); | ||
@@ -129,31 +179,63 @@ expect(rejected).toEqual(true); | ||
it("should deny access if first permission is false and second true",inject(function($rootScope,visorPermissions){ | ||
var rejected =false; | ||
var next = {permissions:[function(){return false;},function(){return true;}]}; | ||
visorPermissions.onRouteChange(next,function(promise){promise.catch(function(){rejected=true;})}); | ||
$rootScope.$apply(); | ||
expect(rejected).toEqual(true); | ||
})); | ||
it("should deny access if first permission is true and second false",inject(function($rootScope,visorPermissions){ | ||
var rejected =false; | ||
var next = {permissions:[function(){return true;},function(){return false;}]}; | ||
visorPermissions.onRouteChange(next,function(promise){promise.catch(function(){rejected=true;})}); | ||
$rootScope.$apply(); | ||
expect(rejected).toEqual(true); | ||
})); | ||
it("should allow access if all permissions return true",inject(function($rootScope,visorPermissions){ | ||
var success =false; | ||
var next = {permissions:[function(){return true;},function(){return true;}]}; | ||
visorPermissions.onRouteChange(next,function(promise){promise.then(function(){success=true;})}); | ||
$rootScope.$apply(); | ||
expect(success).toEqual(true); | ||
})); | ||
it("should deny access if first permission is false and second true", inject(function ($rootScope, visorPermissions) { | ||
var rejected = false; | ||
var next = { | ||
permissions: [function () { | ||
return false; | ||
}, function () { | ||
return true; | ||
}] | ||
}; | ||
visorPermissions.onRouteChange(next, function (promise) { | ||
promise.catch(function () { | ||
rejected = true; | ||
}) | ||
}); | ||
$rootScope.$apply(); | ||
expect(rejected).toEqual(true); | ||
})); | ||
it("should deny access if first permission is true and second false", inject(function ($rootScope, visorPermissions) { | ||
var rejected = false; | ||
var next = { | ||
permissions: [function () { | ||
return true; | ||
}, function () { | ||
return false; | ||
}] | ||
}; | ||
visorPermissions.onRouteChange(next, function (promise) { | ||
promise.catch(function () { | ||
rejected = true; | ||
}) | ||
}); | ||
$rootScope.$apply(); | ||
expect(rejected).toEqual(true); | ||
})); | ||
it("should allow access if all permissions return true", inject(function ($rootScope, visorPermissions) { | ||
var success = false; | ||
var next = { | ||
permissions: [function () { | ||
return true; | ||
}, function () { | ||
return true; | ||
}] | ||
}; | ||
visorPermissions.onRouteChange(next, function (promise) { | ||
promise.then(function () { | ||
success = true; | ||
}) | ||
}); | ||
$rootScope.$apply(); | ||
expect(success).toEqual(true); | ||
})); | ||
}); | ||
describe("doOnNotAllowed",function(){ | ||
describe("doOnNotAllowed", function () { | ||
var notAllowedCalled = false; | ||
angular.module("config.notAllowed",["visor.permissions"]).config(function(visorPermissionsProvider){ | ||
visorPermissionsProvider.onNotAllowed = function(){notAllowedCalled = true;}; | ||
angular.module("config.notAllowed", ["visor.permissions"]).config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.onNotAllowed = function () { | ||
notAllowedCalled = true; | ||
}; | ||
}); | ||
beforeEach(function(){ | ||
beforeEach(function () { | ||
module("config.notAllowed"); | ||
@@ -163,5 +245,9 @@ notAllowedCalled = false; | ||
it("should call doOnNotAllowed if permission returns false",inject(function($rootScope,visorPermissions){ | ||
var next = {restrict:function(){return false;}}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
it("should call doOnNotAllowed if permission returns false", inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
restrict: function () { | ||
return false; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
@@ -171,5 +257,9 @@ expect(notAllowedCalled).toEqual(true); | ||
it("should notcall doOnNotAllowed if permission returns true",inject(function($rootScope,visorPermissions){ | ||
var next = {restrict:function(){return true;}}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
it("should notcall doOnNotAllowed if permission returns true", inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
restrict: function () { | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
@@ -179,37 +269,49 @@ expect(notAllowedCalled).toEqual(false); | ||
}); | ||
describe("getPermissionsFromNext",function(){ | ||
it("should use next.permission getPermissionsFromNext if not overriden",function(){ | ||
var called = false; | ||
describe("getPermissionsFromNext", function () { | ||
it("should use next.permission getPermissionsFromNext if not overriden", function () { | ||
var called = false; | ||
module("visor.permissions"); | ||
inject(function($rootScope,visorPermissions){ | ||
var next = {restrict:function(){called = true;return true;}}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
$rootScope.$apply(); | ||
expect(called).toEqual(true); | ||
}); | ||
}); | ||
it("should allow replacing getPermissionsFormNext",function(){ | ||
angular.module("config.getPerm",["visor.permissions"]).config(function(visorPermissionsProvider){ | ||
visorPermissionsProvider.getPermissionsFromNext = function(next){return [next.something];}; | ||
}); | ||
var called = false; | ||
var shouldNotBeCalled = false; | ||
module("visor.permissions"); | ||
inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
restrict: function () { | ||
called = true; | ||
return true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
expect(called).toEqual(true); | ||
}); | ||
}); | ||
it("should allow replacing getPermissionsFormNext", function () { | ||
angular.module("config.getPerm", ["visor.permissions"]).config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.getPermissionsFromNext = function (next) { | ||
return [next.something]; | ||
}; | ||
}); | ||
var called = false; | ||
var shouldNotBeCalled = false; | ||
module("config.getPerm"); | ||
inject(function($rootScope,visorPermissions){ | ||
var next = { | ||
something:function(){called = true;return true;}, | ||
restrict:function(){shouldNotBeCalled = true;} | ||
}; | ||
visorPermissions.onRouteChange(next,VOID); | ||
$rootScope.$apply(); | ||
expect(shouldNotBeCalled).toEqual(false); | ||
expect(called).toEqual(true); | ||
}); | ||
}); | ||
module("config.getPerm"); | ||
inject(function ($rootScope, visorPermissions) { | ||
var next = { | ||
something: function () { | ||
called = true; | ||
return true; | ||
}, | ||
restrict: function () { | ||
shouldNotBeCalled = true; | ||
} | ||
}; | ||
visorPermissions.onRouteChange(next, VOID); | ||
$rootScope.$apply(); | ||
expect(shouldNotBeCalled).toEqual(false); | ||
expect(called).toEqual(true); | ||
}); | ||
}); | ||
}); | ||
describe("invokeParameters",function(){ | ||
it("should send invoke parameters to permission checks",function(){ | ||
angular.module("config.invoke",["visor.permissions"]).config(function(visorPermissionsProvider){ | ||
describe("invokeParameters", function () { | ||
it("should send invoke parameters to permission checks", function () { | ||
angular.module("config.invoke", ["visor.permissions"]).config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.invokeParameters = ["param to invoke"]; | ||
@@ -219,6 +321,9 @@ }); | ||
module("config.invoke"); | ||
inject(function($rootScope,visorPermissions){ | ||
inject(function ($rootScope, visorPermissions) { | ||
visorPermissions.onRouteChange({ | ||
restrict:function(param){invoked=param;return true;} | ||
},VOID); | ||
restrict: function (param) { | ||
invoked = param; | ||
return true; | ||
} | ||
}, VOID); | ||
$rootScope.$apply(); | ||
@@ -228,10 +333,13 @@ expect(invoked).toEqual("param to invoke"); | ||
}); | ||
it("should allow overriding invoke parameters in runtime",function(){ | ||
it("should allow overriding invoke parameters in runtime", function () { | ||
var invoked = null; | ||
module("visor.permissions"); | ||
inject(function($rootScope,visorPermissions){ | ||
inject(function ($rootScope, visorPermissions) { | ||
visorPermissions.invokeParameters = ["param to invoke"]; | ||
visorPermissions.onRouteChange({ | ||
restrict:function(param){invoked=param;return true;} | ||
},VOID); | ||
restrict: function (param) { | ||
invoked = param; | ||
return true; | ||
} | ||
}, VOID); | ||
$rootScope.$apply(); | ||
@@ -242,4 +350,63 @@ expect(invoked).toEqual("param to invoke"); | ||
}); | ||
describe('checkPermissionsForRoute', function () { | ||
var routes = { | ||
first: { | ||
restrict: function () { | ||
return true; | ||
} | ||
}, | ||
deny: { | ||
restrict: function () { | ||
return false; | ||
} | ||
} | ||
} | ||
var calls = []; | ||
beforeEach(function () { | ||
angular.module("test.getPermissionsForRoute", ["visor.permissions"]) | ||
.config(function (visorPermissionsProvider) { | ||
visorPermissionsProvider.getRoute = function (routeId) { | ||
calls.push(routeId); | ||
return routes[routeId]; | ||
} | ||
}); | ||
calls = []; | ||
module("test.getPermissionsForRoute"); | ||
}) | ||
it('should return true when route is allowed', inject(function ($rootScope, visorPermissions) { | ||
var result = visorPermissions.checkPermissionsForRoute('first'); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['first']); | ||
expect(result).toEqual(true); | ||
})); | ||
it('should return false when route is not allowed', inject(function ($rootScope, visorPermissions) { | ||
var result = visorPermissions.checkPermissionsForRoute('deny'); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['deny']); | ||
expect(result).toEqual(false); | ||
})); | ||
it('should cache requests', inject(function ($rootScope, visorPermissions) { | ||
visorPermissions.checkPermissionsForRoute('first'); | ||
$rootScope.$apply(); | ||
var result = visorPermissions.checkPermissionsForRoute('first'); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['first']); //make sure it's not called twice | ||
expect(result).toEqual(true); | ||
})); | ||
it('should clear cache when called', inject(function ($rootScope, visorPermissions) { | ||
visorPermissions.checkPermissionsForRoute('first'); | ||
$rootScope.$apply(); | ||
visorPermissions.clearPermissionCache(); | ||
visorPermissions.checkPermissionsForRoute('first'); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['first', 'first']); //make sure it's not called twice | ||
})); | ||
it('should return undefined when route does not exist', inject(function ($rootScope, visorPermissions) { | ||
var result = visorPermissions.checkPermissionsForRoute('zzz'); | ||
$rootScope.$apply(); | ||
expect(result).toEqual(undefined); | ||
})); | ||
}); | ||
}); |
'use strict'; | ||
describe('visor', function() { | ||
describe('visor', function () { | ||
describe("authentication",function(){ | ||
var defer,authCallCounter; | ||
describe("authentication", function () { | ||
var defer, authCallCounter; | ||
beforeEach(function(){ | ||
defer = null; | ||
authCallCounter = 0; | ||
angular.module("test.visor.authentication",['visor']) | ||
.config(function(visorProvider){ | ||
visorProvider.authenticate = function($q){ | ||
defer = defer || $q.defer(); | ||
authCallCounter++; | ||
return defer.promise; | ||
}; | ||
beforeEach(function () { | ||
defer = null; | ||
authCallCounter = 0; | ||
angular.module("test.visor.authentication", ['visor']) | ||
.config(function (visorProvider) { | ||
visorProvider.authenticate = function ($q) { | ||
defer = defer || $q.defer(); | ||
authCallCounter++; | ||
return defer.promise; | ||
}; | ||
}); | ||
module("test.visor.authentication"); | ||
}); | ||
module("test.visor.authentication"); | ||
}); | ||
it("should send authInfo to permission checks",inject(function($rootScope,visorPermissions){ | ||
var argumentsInNext = null; | ||
visorPermissions.onRouteChange({restrict:function(){argumentsInNext=arguments;}},function(){}); | ||
defer.resolve("authValue"); | ||
$rootScope.$apply(); | ||
expect(Array.prototype.slice.call(argumentsInNext,0)).toEqual(["authValue"]); | ||
})); | ||
it("should send authInfo to permission checks", inject(function ($rootScope, visorPermissions) { | ||
var argumentsInNext = null; | ||
visorPermissions.onRouteChange({ | ||
restrict: function () { | ||
argumentsInNext = arguments; | ||
} | ||
}, function () { | ||
}); | ||
defer.resolve("authValue"); | ||
$rootScope.$apply(); | ||
expect(Array.prototype.slice.call(argumentsInNext, 0)).toEqual(["authValue"]); | ||
})); | ||
it("should call authenticate on startup by default",inject(function($rootScope,visor){ | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
})); | ||
it("should call authenticate on startup by default", inject(function ($rootScope, visor) { | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
})); | ||
it("should not call authenticate twice if route starts before authentication done",inject(function($rootScope,visorPermissions,visor){ | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
visorPermissions.onRouteChange({restrict:function(){return true;}},function(){}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
})); | ||
it("should not call authenticate twice if route starts before authentication done", inject(function ($rootScope, visorPermissions, visor) { | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
visorPermissions.onRouteChange({ | ||
restrict: function () { | ||
return true; | ||
} | ||
}, function () { | ||
}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
})); | ||
it("should not change route until autentication on startup finishes",inject(function($rootScope,$location,visor){ | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual(""); | ||
defer.resolve(null); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual("/thingy"); | ||
})); | ||
it("should not change route until autentication on startup finishes", inject(function ($rootScope, $location, visor) { | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual(""); | ||
defer.resolve(null); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual("/thingy"); | ||
})); | ||
it("should not call authenticate on startup if flag disabled, and call it only on first permission check",function(){ | ||
angular.module("test.visor.authentication.nostartup",["test.visor.authentication"]).config(function(visorProvider){ | ||
visorProvider.authenticateOnStartup = false; | ||
module("test.visor.authentication.nostartup"); | ||
inject(function($rootScope,$location,visorPermissions){ | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual("/thingy"); | ||
expect(authCallCounter).toEqual(0); | ||
visorPermissions.onRouteChange({},function(){}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(0); | ||
visorPermissions.onRouteChange({restrict:function(){}},function(){}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
it("should not call authenticate on startup if flag disabled, and call it only on first permission check", function () { | ||
angular.module("test.visor.authentication.nostartup", ["test.visor.authentication"]).config(function (visorProvider) { | ||
visorProvider.authenticateOnStartup = false; | ||
module("test.visor.authentication.nostartup"); | ||
inject(function ($rootScope, $location, visorPermissions) { | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect($location.url()).toEqual("/thingy"); | ||
expect(authCallCounter).toEqual(0); | ||
visorPermissions.onRouteChange({}, function () { | ||
}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(0); | ||
visorPermissions.onRouteChange({ | ||
restrict: function () { | ||
} | ||
}, function () { | ||
}); | ||
$rootScope.$apply(); | ||
expect(authCallCounter).toEqual(1); | ||
}); | ||
}) | ||
}); | ||
}) | ||
it("should allow using dependent services in auth", function () { | ||
var authCalled = false; | ||
angular.module("test.visor.authentication.with.service", ['visor']) | ||
.service("authService", function ($q) { | ||
return function () { | ||
authCalled = true; | ||
return $q.when("auth!"); | ||
} | ||
}) | ||
.config(function (visorProvider) { | ||
visorProvider.authenticate = function (authService) { | ||
return authService() | ||
}; | ||
}); | ||
module("test.visor.authentication.with.service"); | ||
inject(function (visor, $location, $rootScope) { | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect(authCalled).toEqual(true); | ||
}); | ||
}) | ||
}); | ||
it("should allow using dependent services in auth",function(){ | ||
var authCalled = false; | ||
angular.module("test.visor.authentication.with.service",['visor']) | ||
.service("authService",function($q){ | ||
return function(){ | ||
authCalled = true; | ||
return $q.when("auth!"); | ||
} | ||
}) | ||
.config(function(visorProvider){ | ||
visorProvider.authenticate = function(authService){ | ||
return authService() | ||
}; | ||
}); | ||
module("test.visor.authentication.with.service"); | ||
inject(function(visor,$location,$rootScope){ | ||
$location.url("/thingy"); | ||
$rootScope.$apply(); | ||
expect(authCalled).toEqual(true); | ||
}); | ||
}) | ||
}); | ||
describe("ngRoute",function(){ | ||
describe("ngRoute", function () { | ||
var authenticate = null; | ||
var authenticate = null; | ||
beforeEach(function(){ | ||
authenticate = null; | ||
angular.module("test.config.ngRoute",['ngRoute','visor']) | ||
.config(function($routeProvider,visorProvider,authenticatedOnly,notForAuthenticated){ | ||
beforeEach(function () { | ||
authenticate = null; | ||
angular.module("test.config.ngRoute", ['ngRoute', 'visor']) | ||
.config(function ($routeProvider, visorProvider, authenticatedOnly, notForAuthenticated) { | ||
$routeProvider.when("/private_url",{ | ||
restrict:authenticatedOnly | ||
}) | ||
.when("/public",{}) | ||
.when("/hidden",{ | ||
restrict:notForAuthenticated | ||
}) | ||
.when("/login",{}) | ||
.when("/access_denied",{}); | ||
visorProvider.authenticate = function($q){ | ||
return authenticate($q); | ||
}; | ||
$routeProvider.when("/private_url", { | ||
restrict: authenticatedOnly | ||
}) | ||
.when("/public", {}) | ||
.when("/hidden", { | ||
restrict: notForAuthenticated | ||
}) | ||
.when("/login", {}) | ||
.when("/access_denied", {}); | ||
visorProvider.authenticate = function ($q) { | ||
return authenticate($q); | ||
}; | ||
}); | ||
}); | ||
}); | ||
it('should allow already loggedin user into authenticatedOnly route', function(){ | ||
authenticate = function($q){ | ||
return $q.when({username:"myName"}); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function($rootScope,$location,$route,visor,$timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/private_url") | ||
}); | ||
}); | ||
it('should allow already loggedin user into authenticatedOnly route', function () { | ||
authenticate = function ($q) { | ||
return $q.when({username: "myName"}); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function ($rootScope, $location, $route, visor, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/private_url") | ||
}); | ||
}); | ||
it('should redirect anonymous users to login if accessing private route', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function($rootScope,$q,$location,$route,visor,$timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/login"); | ||
expect($location.search().next).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should redirect anonymous users to login if accessing private route', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function ($rootScope, $q, $location, $route, visor, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/login"); | ||
expect($location.search().next).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should not redirect anonymous users to login if accessing public route', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function($rootScope,$location,$route,$q,visor,$timeout) { | ||
$location.url("/public"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/public"); | ||
}); | ||
}); | ||
it('should allow access to private states after authentication', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function($rootScope,$route,$q,visor,$location,$timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/login"); | ||
visor.setAuthenticated({username:"some_name"}); | ||
$rootScope.$apply(); | ||
//should redirect back to original route automatically | ||
expect($location.url()).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should not redirect anonymous users to login if accessing public route', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function ($rootScope, $location, $route, $q, visor, $timeout) { | ||
$location.url("/public"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/public"); | ||
}); | ||
}); | ||
it('should allow access to private states after authentication', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function ($rootScope, $route, $q, visor, $location, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/login"); | ||
visor.setAuthenticated({username: "some_name"}); | ||
$rootScope.$apply(); | ||
//should redirect back to original route automatically | ||
expect($location.url()).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should not allow access if user is not authorized',function(){ | ||
authenticate = function($q){ | ||
return $q.when(true); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function($rootScope,$route,$q,visor,$location,$timeout) { | ||
$location.url("/hidden"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/access_denied"); | ||
expect($location.url()).toEqual("/access_denied"); | ||
}); | ||
it('should not allow access if user is not authorized', function () { | ||
authenticate = function ($q) { | ||
return $q.when(true); | ||
}; | ||
module("test.config.ngRoute"); | ||
inject(function ($rootScope, $route, $q, visor, $location, $timeout) { | ||
$location.url("/hidden"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($route.current.originalPath).toEqual("/access_denied"); | ||
expect($location.url()).toEqual("/access_denied"); | ||
}); | ||
}); | ||
}); | ||
}); | ||
describe('ui-router',function(){ | ||
describe('ui-router', function () { | ||
var authenticate = null; | ||
var authenticate = null; | ||
beforeEach(function(){ | ||
authenticate = null; | ||
angular.module("test.config",['ui.router','visor']) | ||
.config(function($stateProvider,visorProvider,authenticatedOnly,notForAuthenticated){ | ||
beforeEach(function () { | ||
authenticate = null; | ||
angular.module("test.config", ['ui.router', 'visor']) | ||
.config(function ($stateProvider, visorProvider, authenticatedOnly, notForAuthenticated) { | ||
$stateProvider.state("private",{ | ||
url:"/private_url", | ||
restrict:authenticatedOnly | ||
}) | ||
.state("public",{ | ||
url:"/public" | ||
}) | ||
.state("hidden",{ | ||
url:"/hidden", | ||
restrict:notForAuthenticated | ||
}) | ||
.state("private.nestedpublic",{ | ||
url:"/public" | ||
}) | ||
.state("public.nestedprivate",{ | ||
url:"/public/private", | ||
restrict:authenticatedOnly | ||
}) | ||
.state("login",{ | ||
url:"/login" | ||
}) | ||
.state("access_denied",{ | ||
url:"/access_denied" | ||
}); | ||
visorProvider.authenticate = function($q){ | ||
return authenticate($q); | ||
}; | ||
}); | ||
}); | ||
$stateProvider.state("private", { | ||
url: "/private_url", | ||
restrict: authenticatedOnly | ||
}) | ||
.state("public", { | ||
url: "/public" | ||
}) | ||
.state("hidden", { | ||
url: "/hidden", | ||
restrict: notForAuthenticated | ||
}) | ||
.state("private.nestedpublic", { | ||
url: "/public" | ||
}) | ||
.state("public.nestedprivate", { | ||
url: "/public/private", | ||
restrict: authenticatedOnly | ||
}) | ||
.state("login", { | ||
url: "/login" | ||
}) | ||
.state("access_denied", { | ||
url: "/access_denied" | ||
}); | ||
visorProvider.authenticate = function ($q) { | ||
return authenticate($q); | ||
}; | ||
}); | ||
}); | ||
it('should allow already loggedin user into authenticatedOnly route', function(){ | ||
authenticate = function($q){ | ||
return $q.when({username:"myName"}); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$location,$state,$q,visor,$timeout) { | ||
$location.url("/private_url"); | ||
it('should allow already loggedin user into authenticatedOnly route', function () { | ||
authenticate = function ($q) { | ||
return $q.when({username: "myName"}); | ||
}; | ||
module("test.config"); | ||
inject(function ($rootScope, $location, $state, $q, visor, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/private_url") | ||
}); | ||
}); | ||
expect($location.url()).toEqual("/private_url") | ||
}); | ||
}); | ||
it('should redirect anonymous users to login if accessing private route', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$state,$q,$location,visor,$timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("login"); | ||
expect($location.search().next).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should redirect anonymous users to login if accessing private route after visitng public url', function(){ | ||
authenticate = function($q){ | ||
it('should redirect anonymous users to login if accessing private route', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$state,$q,$location,visor,$timeout) { | ||
inject(function ($rootScope, $state, $q, $location, visor, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("login"); | ||
expect($location.search().next).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should redirect anonymous users to login if accessing private route after visitng public url', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function ($rootScope, $state, $q, $location, visor, $timeout) { | ||
$location.url("/public"); | ||
@@ -269,46 +284,46 @@ $rootScope.$apply(); | ||
}); | ||
it('should not redirect anonymous users to login if accessing public route', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$location,$state,$q,visor,$timeout) { | ||
$location.url("/public"); | ||
$rootScope.$apply(); | ||
it('should not redirect anonymous users to login if accessing public route', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function ($rootScope, $location, $state, $q, visor, $timeout) { | ||
$location.url("/public"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($location.url()).toEqual("/public"); | ||
}); | ||
}); | ||
it('should allow access to private states after authentication', function(){ | ||
authenticate = function($q){ | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$state,$q,visor,$location,$timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("login"); | ||
visor.setAuthenticated({username:"some_name"}); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
//should redirect back to original route automatically | ||
expect($location.url()).toEqual("/private_url"); | ||
}); | ||
}); | ||
expect($location.url()).toEqual("/public"); | ||
}); | ||
}); | ||
it('should allow access to private states after authentication', function () { | ||
authenticate = function ($q) { | ||
return $q.reject("not authenticated"); | ||
}; | ||
module("test.config"); | ||
inject(function ($rootScope, $state, $q, visor, $location, $timeout) { | ||
$location.url("/private_url"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("login"); | ||
visor.setAuthenticated({username: "some_name"}); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
//should redirect back to original route automatically | ||
expect($location.url()).toEqual("/private_url"); | ||
}); | ||
}); | ||
it('should not allow access if user is not authorized',function(){ | ||
authenticate = function($q){ | ||
return $q.when(true); | ||
}; | ||
module("test.config"); | ||
inject(function($rootScope,$state,$q,visor,$location,$timeout) { | ||
$location.url("/hidden"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("access_denied"); | ||
expect($location.url()).toEqual("/access_denied"); | ||
}); | ||
}); | ||
}); | ||
it('should not allow access if user is not authorized', function () { | ||
authenticate = function ($q) { | ||
return $q.when(true); | ||
}; | ||
module("test.config"); | ||
inject(function ($rootScope, $state, $q, visor, $location, $timeout) { | ||
$location.url("/hidden"); | ||
$rootScope.$apply(); | ||
$timeout.flush(); | ||
expect($state.current.name).toEqual("access_denied"); | ||
expect($location.url()).toEqual("/access_denied"); | ||
}); | ||
}); | ||
}); | ||
}); |
@@ -1,75 +0,159 @@ | ||
describe("visor.ui-router",function(){ | ||
describe("state change",function(){ | ||
var defer = null; | ||
angular.module("test.states",['visor.ui-router','ui.router']).config(function($stateProvider,visorPermissionsProvider){ | ||
$stateProvider.state("first",{ | ||
url:"/first", | ||
restrict: function (){return true;} | ||
}).state("deny",{ | ||
url:"/deny", | ||
restrict: function(){return false;} | ||
}); | ||
visorPermissionsProvider.doBeforeFirstCheck.push(function($q){defer = $q.defer(); return defer.promise;}); | ||
describe("visor.ui-router", function () { | ||
describe("state change", function () { | ||
var defer = null; | ||
angular.module("test.states", ['visor.ui-router', 'ui.router']).config(function ($stateProvider, visorPermissionsProvider) { | ||
$stateProvider.state("first", { | ||
url: "/first", | ||
restrict: function () { | ||
return true; | ||
} | ||
}).state("deny", { | ||
url: "/deny", | ||
restrict: function () { | ||
return false; | ||
} | ||
}); | ||
visorPermissionsProvider.doBeforeFirstCheck.push(function ($q) { | ||
defer = $q.defer(); | ||
return defer.promise; | ||
}); | ||
}) | ||
beforeEach(module("test.states")); | ||
}) | ||
beforeEach(module("test.states")); | ||
it("should wait to change state until delay called",inject(function($location,$state,$rootScope){ | ||
var successCounter = 0; | ||
$rootScope.$on("$stateChangeSuccess",function(){successCounter++;}); | ||
$location.url("/first"); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(0); | ||
defer.resolve(""); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(1); | ||
expect($location.url()).toEqual("/first"); | ||
expect($state.current.name).toEqual("first"); | ||
})); | ||
it("should wait to change state until delay called", inject(function ($location, $state, $rootScope) { | ||
var successCounter = 0; | ||
$rootScope.$on("$stateChangeSuccess", function () { | ||
successCounter++; | ||
}); | ||
$location.url("/first"); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(0); | ||
defer.resolve(""); | ||
$rootScope.$apply(); | ||
expect(successCounter).toEqual(1); | ||
expect($location.url()).toEqual("/first"); | ||
expect($state.current.name).toEqual("first"); | ||
})); | ||
it("should stop state change when permission rejected",inject(function($location,$state,$rootScope){ | ||
$location.url("/deny"); | ||
$rootScope.$apply(); | ||
defer.reject(""); | ||
$rootScope.$apply(); | ||
expect($state.current.name).toEqual(""); | ||
})); | ||
it("should stop state change when permission rejected", inject(function ($location, $state, $rootScope) { | ||
$location.url("/deny"); | ||
$rootScope.$apply(); | ||
defer.reject(""); | ||
$rootScope.$apply(); | ||
expect($state.current.name).toEqual(""); | ||
})); | ||
}); | ||
describe("permissions",function(){ | ||
describe("permissions", function () { | ||
var calls = []; | ||
angular.module("test.states.permissions",['visor.ui-router','ui.router']).config(function($stateProvider,visorPermissionsProvider){ | ||
$stateProvider.state("parent",{ | ||
restrict: function(){calls.push("parent");return true;} | ||
}).state("child",{ | ||
parent:"parent", | ||
url:"/child", | ||
restrict: function (){calls.push("child");return true;} | ||
}).state("deny",{ | ||
url:"/deny", | ||
restrict: function(){calls.push("deny");return false;} | ||
describe('property based inheritance', function () { | ||
angular.module("test.states.permissions", ['visor.ui-router', 'ui.router']).config(function ($stateProvider, visorPermissionsProvider) { | ||
$stateProvider.state("parent", { | ||
restrict: function () { | ||
calls.push("parent"); | ||
return true; | ||
} | ||
}).state("child", { | ||
parent: "parent", | ||
url: "/child", | ||
restrict: function () { | ||
calls.push("child"); | ||
return true; | ||
} | ||
}).state('grandchild', { | ||
url: '/grandchild', | ||
parent: "child", | ||
restrict: function () { | ||
calls.push('grandchild'); | ||
return true; | ||
} | ||
}).state("deny", { | ||
url: "/deny", | ||
restrict: function () { | ||
calls.push("deny"); | ||
return false; | ||
} | ||
}); | ||
}); | ||
beforeEach(function () { | ||
calls = []; | ||
module("test.states.permissions") | ||
}); | ||
it("should check permissions in next", inject(function ($state, $rootScope, $location) { | ||
$state.go("deny"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(["deny"]) | ||
})); | ||
it("should check permission for parent route", inject(function ($state, $rootScope, $location) { | ||
$state.go("child"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(["parent", "child"]) | ||
})); | ||
it('should check permission for grandparent route', inject(function ($state, $rootScope, $location) { | ||
$state.go("grandchild"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['parent', 'child', 'grandchild']) | ||
})); | ||
it('should check the correct route with getPermissionsForRoute', inject(function ($rootScope, visorPermissions) { | ||
var permission = visorPermissions.checkPermissionsForRoute('deny'); | ||
expect(permission).toEqual(false); | ||
permission = visorPermissions.checkPermissionsForRoute('child'); | ||
expect(permission).toEqual(true); | ||
expect(calls).toEqual(["deny", "parent", "child"]) | ||
})); | ||
it('should return undefined when route does not exist', inject(function ($rootScope, visorPermissions) { | ||
var result = visorPermissions.checkPermissionsForRoute('zzz'); | ||
$rootScope.$apply(); | ||
expect(result).toEqual(undefined); | ||
})); | ||
}); | ||
}); | ||
beforeEach(function(){calls = [];module("test.states.permissions")}); | ||
it("should check permissions in next",inject(function($state,$rootScope,$location){ | ||
$state.go("deny"); | ||
describe('dot based inheritance', function () { | ||
angular.module("test.states.permissions.dot", ['visor.ui-router', 'ui.router']).config(function ($stateProvider, visorPermissionsProvider) { | ||
$stateProvider.state("parent", { | ||
restrict: function () { | ||
calls.push("parent"); | ||
return true; | ||
} | ||
}).state("parent.child", { | ||
url: "/child", | ||
restrict: function () { | ||
calls.push("child"); | ||
return true; | ||
} | ||
}).state('parent.child.grandchild', { | ||
url: '/grandchild', | ||
restrict: function () { | ||
calls.push('grandchild'); | ||
return true; | ||
} | ||
}); | ||
}); | ||
beforeEach(function () { | ||
calls = []; | ||
module("test.states.permissions.dot") | ||
}); | ||
it('should check permission for grandparent route', inject(function ($state, $rootScope, $location) { | ||
$state.go("parent.child.grandchild"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(['parent', 'child', 'grandchild']) | ||
})); | ||
}) | ||
}); | ||
it("should not change anything if ui-router is not depended on", function () { | ||
module("visor.permissions", "visor.ui-router") | ||
inject(function ($location, $rootScope, visorPermissions) { | ||
$location.url("/something"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(["deny"]) | ||
})); | ||
it("should check permission for parent route",inject(function($state,$rootScope,$location){ | ||
$state.go("child"); | ||
$rootScope.$apply(); | ||
expect(calls).toEqual(["parent","child"]) | ||
})); | ||
visorPermissions.onRouteChange({ | ||
restrict: function () { | ||
return false; | ||
} | ||
}, function () { | ||
}); | ||
$rootScope.$apply(); | ||
//nothing crushed! | ||
}) | ||
}); | ||
it("should not change anything if ui-router is not depended on",function(){ | ||
module("visor.permissions","visor.ui-router") | ||
inject(function($location,$rootScope,visorPermissions){ | ||
$location.url("/something"); | ||
$rootScope.$apply(); | ||
visorPermissions.onRouteChange({restrict:function(){return false;}},function(){}); | ||
$rootScope.$apply(); | ||
//nothing crushed! | ||
}) | ||
}); | ||
}); | ||
}); |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
1666094
44
38193
1
0