├── .gitignore ├── README.md ├── app.js ├── package.json ├── public ├── javascripts │ ├── angular-resource.js │ ├── angular-resource.min.js │ ├── angular-resource.min.js.map │ ├── angular-route.js │ ├── angular-route.min.js │ ├── angular-route.min.js.map │ ├── angular.js │ ├── angular.min.js │ ├── angular.min.js.map │ └── app.js ├── stylesheets │ └── style.css └── views │ ├── admin.html │ ├── login.html │ └── main.html └── views └── index.ejs /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | AuthenticationAngularJS 2 | ======================= 3 | 4 | This code is an example to create secured routes in AngularJS. To have more details, please go to [the tutorial](https://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs). 5 | 6 | To run this example: 7 | 1. `npm install` 8 | 2. `node app.js` 9 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var http = require('http'); 3 | var path = require('path'); 4 | var passport = require('passport'); 5 | var LocalStrategy = require('passport-local').Strategy; 6 | 7 | //================================================================== 8 | // Define the strategy to be used by PassportJS 9 | passport.use(new LocalStrategy( 10 | function(username, password, done) { 11 | if (username === "admin" && password === "admin") // stupid example 12 | return done(null, {name: "admin"}); 13 | 14 | return done(null, false, { message: 'Incorrect username.' }); 15 | } 16 | )); 17 | 18 | // Serialized and deserialized methods when got from session 19 | passport.serializeUser(function(user, done) { 20 | done(null, user); 21 | }); 22 | 23 | passport.deserializeUser(function(user, done) { 24 | done(null, user); 25 | }); 26 | 27 | // Define a middleware function to be used for every secured routes 28 | var auth = function(req, res, next){ 29 | if (!req.isAuthenticated()) 30 | res.send(401); 31 | else 32 | next(); 33 | }; 34 | //================================================================== 35 | 36 | // Start express application 37 | var app = express(); 38 | 39 | // all environments 40 | app.set('port', process.env.PORT || 3000); 41 | app.set('views', __dirname + '/views'); 42 | app.set('view engine', 'ejs'); 43 | app.use(express.favicon()); 44 | app.use(express.logger('dev')); 45 | app.use(express.cookieParser()); 46 | app.use(express.bodyParser()); 47 | app.use(express.methodOverride()); 48 | app.use(express.session({ secret: 'securedsession' })); 49 | app.use(passport.initialize()); // Add passport initialization 50 | app.use(passport.session()); // Add passport initialization 51 | app.use(app.router); 52 | app.use(express.static(path.join(__dirname, 'public'))); 53 | 54 | // development only 55 | if ('development' == app.get('env')) { 56 | app.use(express.errorHandler()); 57 | } 58 | 59 | //================================================================== 60 | // routes 61 | app.get('/', function(req, res){ 62 | res.render('index', { title: 'Express' }); 63 | }); 64 | 65 | app.get('/users', auth, function(req, res){ 66 | res.send([{name: "user1"}, {name: "user2"}]); 67 | }); 68 | //================================================================== 69 | 70 | //================================================================== 71 | // route to test if the user is logged in or not 72 | app.get('/loggedin', function(req, res) { 73 | res.send(req.isAuthenticated() ? req.user : '0'); 74 | }); 75 | 76 | // route to log in 77 | app.post('/login', passport.authenticate('local'), function(req, res) { 78 | res.send(req.user); 79 | }); 80 | 81 | // route to log out 82 | app.post('/logout', function(req, res){ 83 | req.logOut(); 84 | res.send(200); 85 | }); 86 | //================================================================== 87 | 88 | http.createServer(app).listen(app.get('port'), function(){ 89 | console.log('Express server listening on port ' + app.get('port')); 90 | }); 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AuthenticationInAngularJS", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app.js" 7 | }, 8 | "dependencies": { 9 | "express": "3.3.5", 10 | "ejs": "*", 11 | "passport": "*", 12 | "passport-local": "*" 13 | } 14 | } -------------------------------------------------------------------------------- /public/javascripts/angular-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.3.11 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | var $resourceMinErr = angular.$$minErr('$resource'); 9 | 10 | // Helper functions and regex to lookup a dotted path on an object 11 | // stopping at undefined/null. The path must be composed of ASCII 12 | // identifiers (just like $parse) 13 | var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; 14 | 15 | function isValidDottedPath(path) { 16 | return (path != null && path !== '' && path !== 'hasOwnProperty' && 17 | MEMBER_NAME_REGEX.test('.' + path)); 18 | } 19 | 20 | function lookupDottedPath(obj, path) { 21 | if (!isValidDottedPath(path)) { 22 | throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); 23 | } 24 | var keys = path.split('.'); 25 | for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { 26 | var key = keys[i]; 27 | obj = (obj !== null) ? obj[key] : undefined; 28 | } 29 | return obj; 30 | } 31 | 32 | /** 33 | * Create a shallow copy of an object and clear other fields from the destination 34 | */ 35 | function shallowClearAndCopy(src, dst) { 36 | dst = dst || {}; 37 | 38 | angular.forEach(dst, function(value, key) { 39 | delete dst[key]; 40 | }); 41 | 42 | for (var key in src) { 43 | if (src.hasOwnProperty(key) && !(key.charAt(0) === '$' && key.charAt(1) === '$')) { 44 | dst[key] = src[key]; 45 | } 46 | } 47 | 48 | return dst; 49 | } 50 | 51 | /** 52 | * @ngdoc module 53 | * @name ngResource 54 | * @description 55 | * 56 | * # ngResource 57 | * 58 | * The `ngResource` module provides interaction support with RESTful services 59 | * via the $resource service. 60 | * 61 | * 62 | *
63 | * 64 | * See {@link ngResource.$resource `$resource`} for usage. 65 | */ 66 | 67 | /** 68 | * @ngdoc service 69 | * @name $resource 70 | * @requires $http 71 | * 72 | * @description 73 | * A factory which creates a resource object that lets you interact with 74 | * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources. 75 | * 76 | * The returned resource object has action methods which provide high-level behaviors without 77 | * the need to interact with the low level {@link ng.$http $http} service. 78 | * 79 | * Requires the {@link ngResource `ngResource`} module to be installed. 80 | * 81 | * By default, trailing slashes will be stripped from the calculated URLs, 82 | * which can pose problems with server backends that do not expect that 83 | * behavior. This can be disabled by configuring the `$resourceProvider` like 84 | * this: 85 | * 86 | * ```js 87 | app.config(['$resourceProvider', function($resourceProvider) { 88 | // Don't strip trailing slashes from calculated URLs 89 | $resourceProvider.defaults.stripTrailingSlashes = false; 90 | }]); 91 | * ``` 92 | * 93 | * @param {string} url A parametrized URL template with parameters prefixed by `:` as in 94 | * `/user/:username`. If you are using a URL with a port number (e.g. 95 | * `http://example.com:8080/api`), it will be respected. 96 | * 97 | * If you are using a url with a suffix, just add the suffix, like this: 98 | * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` 99 | * or even `$resource('http://example.com/resource/:resource_id.:format')` 100 | * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be 101 | * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you 102 | * can escape it with `/\.`. 103 | * 104 | * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in 105 | * `actions` methods. If any of the parameter value is a function, it will be executed every time 106 | * when a param value needs to be obtained for a request (unless the param was overridden). 107 | * 108 | * Each key value in the parameter object is first bound to url template if present and then any 109 | * excess keys are appended to the url search query after the `?`. 110 | * 111 | * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in 112 | * URL `/path/greet?salutation=Hello`. 113 | * 114 | * If the parameter value is prefixed with `@` then the value for that parameter will be extracted 115 | * from the corresponding property on the `data` object (provided when calling an action method). For 116 | * example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of `someParam` 117 | * will be `data.someProp`. 118 | * 119 | * @param {Object.=} actions Hash with declaration of custom actions that should extend 120 | * the default set of resource actions. The declaration should be created in the format of {@link 121 | * ng.$http#usage $http.config}: 122 | * 123 | * {action1: {method:?, params:?, isArray:?, headers:?, ...}, 124 | * action2: {method:?, params:?, isArray:?, headers:?, ...}, 125 | * ...} 126 | * 127 | * Where: 128 | * 129 | * - **`action`** – {string} – The name of action. This name becomes the name of the method on 130 | * your resource object. 131 | * - **`method`** – {string} – Case insensitive HTTP method (e.g. `GET`, `POST`, `PUT`, 132 | * `DELETE`, `JSONP`, etc). 133 | * - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of 134 | * the parameter value is a function, it will be executed every time when a param value needs to 135 | * be obtained for a request (unless the param was overridden). 136 | * - **`url`** – {string} – action specific `url` override. The url templating is supported just 137 | * like for the resource-level urls. 138 | * - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, 139 | * see `returns` section. 140 | * - **`transformRequest`** – 141 | * `{function(data, headersGetter)|Array.}` – 142 | * transform function or an array of such functions. The transform function takes the http 143 | * request body and headers and returns its transformed (typically serialized) version. 144 | * By default, transformRequest will contain one function that checks if the request data is 145 | * an object and serializes to using `angular.toJson`. To prevent this behavior, set 146 | * `transformRequest` to an empty array: `transformRequest: []` 147 | * - **`transformResponse`** – 148 | * `{function(data, headersGetter)|Array.}` – 149 | * transform function or an array of such functions. The transform function takes the http 150 | * response body and headers and returns its transformed (typically deserialized) version. 151 | * By default, transformResponse will contain one function that checks if the response looks like 152 | * a JSON string and deserializes it using `angular.fromJson`. To prevent this behavior, set 153 | * `transformResponse` to an empty array: `transformResponse: []` 154 | * - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the 155 | * GET request, otherwise if a cache instance built with 156 | * {@link ng.$cacheFactory $cacheFactory}, this cache will be used for 157 | * caching. 158 | * - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that 159 | * should abort the request when resolved. 160 | * - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the 161 | * XHR object. See 162 | * [requests with credentials](https://developer.mozilla.org/en/http_access_control#section_5) 163 | * for more information. 164 | * - **`responseType`** - `{string}` - see 165 | * [requestType](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType). 166 | * - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - 167 | * `response` and `responseError`. Both `response` and `responseError` interceptors get called 168 | * with `http response` object. See {@link ng.$http $http interceptors}. 169 | * 170 | * @param {Object} options Hash with custom settings that should extend the 171 | * default `$resourceProvider` behavior. The only supported option is 172 | * 173 | * Where: 174 | * 175 | * - **`stripTrailingSlashes`** – {boolean} – If true then the trailing 176 | * slashes from any calculated URL will be stripped. (Defaults to true.) 177 | * 178 | * @returns {Object} A resource "class" object with methods for the default set of resource actions 179 | * optionally extended with custom `actions`. The default set contains these actions: 180 | * ```js 181 | * { 'get': {method:'GET'}, 182 | * 'save': {method:'POST'}, 183 | * 'query': {method:'GET', isArray:true}, 184 | * 'remove': {method:'DELETE'}, 185 | * 'delete': {method:'DELETE'} }; 186 | * ``` 187 | * 188 | * Calling these methods invoke an {@link ng.$http} with the specified http method, 189 | * destination and parameters. When the data is returned from the server then the object is an 190 | * instance of the resource class. The actions `save`, `remove` and `delete` are available on it 191 | * as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, 192 | * read, update, delete) on server-side data like this: 193 | * ```js 194 | * var User = $resource('/user/:userId', {userId:'@id'}); 195 | * var user = User.get({userId:123}, function() { 196 | * user.abc = true; 197 | * user.$save(); 198 | * }); 199 | * ``` 200 | * 201 | * It is important to realize that invoking a $resource object method immediately returns an 202 | * empty reference (object or array depending on `isArray`). Once the data is returned from the 203 | * server the existing reference is populated with the actual data. This is a useful trick since 204 | * usually the resource is assigned to a model which is then rendered by the view. Having an empty 205 | * object results in no rendering, once the data arrives from the server then the object is 206 | * populated with the data and the view automatically re-renders itself showing the new data. This 207 | * means that in most cases one never has to write a callback function for the action methods. 208 | * 209 | * The action methods on the class object or instance object can be invoked with the following 210 | * parameters: 211 | * 212 | * - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` 213 | * - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` 214 | * - non-GET instance actions: `instance.$action([parameters], [success], [error])` 215 | * 216 | * Success callback is called with (value, responseHeaders) arguments. Error callback is called 217 | * with (httpResponse) argument. 218 | * 219 | * Class actions return empty instance (with additional properties below). 220 | * Instance actions return promise of the action. 221 | * 222 | * The Resource instances and collection have these additional properties: 223 | * 224 | * - `$promise`: the {@link ng.$q promise} of the original server interaction that created this 225 | * instance or collection. 226 | * 227 | * On success, the promise is resolved with the same resource instance or collection object, 228 | * updated with data from server. This makes it easy to use in 229 | * {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view 230 | * rendering until the resource(s) are loaded. 231 | * 232 | * On failure, the promise is resolved with the {@link ng.$http http response} object, without 233 | * the `resource` property. 234 | * 235 | * If an interceptor object was provided, the promise will instead be resolved with the value 236 | * returned by the interceptor. 237 | * 238 | * - `$resolved`: `true` after first server interaction is completed (either with success or 239 | * rejection), `false` before that. Knowing if the Resource has been resolved is useful in 240 | * data-binding. 241 | * 242 | * @example 243 | * 244 | * # Credit card resource 245 | * 246 | * ```js 247 | // Define CreditCard class 248 | var CreditCard = $resource('/user/:userId/card/:cardId', 249 | {userId:123, cardId:'@id'}, { 250 | charge: {method:'POST', params:{charge:true}} 251 | }); 252 | 253 | // We can retrieve a collection from the server 254 | var cards = CreditCard.query(function() { 255 | // GET: /user/123/card 256 | // server returns: [ {id:456, number:'1234', name:'Smith'} ]; 257 | 258 | var card = cards[0]; 259 | // each item is an instance of CreditCard 260 | expect(card instanceof CreditCard).toEqual(true); 261 | card.name = "J. Smith"; 262 | // non GET methods are mapped onto the instances 263 | card.$save(); 264 | // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'} 265 | // server returns: {id:456, number:'1234', name: 'J. Smith'}; 266 | 267 | // our custom method is mapped as well. 268 | card.$charge({amount:9.99}); 269 | // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'} 270 | }); 271 | 272 | // we can create an instance as well 273 | var newCard = new CreditCard({number:'0123'}); 274 | newCard.name = "Mike Smith"; 275 | newCard.$save(); 276 | // POST: /user/123/card {number:'0123', name:'Mike Smith'} 277 | // server returns: {id:789, number:'0123', name: 'Mike Smith'}; 278 | expect(newCard.id).toEqual(789); 279 | * ``` 280 | * 281 | * The object returned from this function execution is a resource "class" which has "static" method 282 | * for each action in the definition. 283 | * 284 | * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and 285 | * `headers`. 286 | * When the data is returned from the server then the object is an instance of the resource type and 287 | * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD 288 | * operations (create, read, update, delete) on server-side data. 289 | 290 | ```js 291 | var User = $resource('/user/:userId', {userId:'@id'}); 292 | User.get({userId:123}, function(user) { 293 | user.abc = true; 294 | user.$save(); 295 | }); 296 | ``` 297 | * 298 | * It's worth noting that the success callback for `get`, `query` and other methods gets passed 299 | * in the response that came from the server as well as $http header getter function, so one 300 | * could rewrite the above example and get access to http headers as: 301 | * 302 | ```js 303 | var User = $resource('/user/:userId', {userId:'@id'}); 304 | User.get({userId:123}, function(u, getResponseHeaders){ 305 | u.abc = true; 306 | u.$save(function(u, putResponseHeaders) { 307 | //u => saved user object 308 | //putResponseHeaders => $http header getter 309 | }); 310 | }); 311 | ``` 312 | * 313 | * You can also access the raw `$http` promise via the `$promise` property on the object returned 314 | * 315 | ``` 316 | var User = $resource('/user/:userId', {userId:'@id'}); 317 | User.get({userId:123}) 318 | .$promise.then(function(user) { 319 | $scope.user = user; 320 | }); 321 | ``` 322 | 323 | * # Creating a custom 'PUT' request 324 | * In this example we create a custom method on our resource to make a PUT request 325 | * ```js 326 | * var app = angular.module('app', ['ngResource', 'ngRoute']); 327 | * 328 | * // Some APIs expect a PUT request in the format URL/object/ID 329 | * // Here we are creating an 'update' method 330 | * app.factory('Notes', ['$resource', function($resource) { 331 | * return $resource('/notes/:id', null, 332 | * { 333 | * 'update': { method:'PUT' } 334 | * }); 335 | * }]); 336 | * 337 | * // In our controller we get the ID from the URL using ngRoute and $routeParams 338 | * // We pass in $routeParams and our Notes factory along with $scope 339 | * app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes', 340 | function($scope, $routeParams, Notes) { 341 | * // First get a note object from the factory 342 | * var note = Notes.get({ id:$routeParams.id }); 343 | * $id = note.id; 344 | * 345 | * // Now call update passing in the ID first then the object you are updating 346 | * Notes.update({ id:$id }, note); 347 | * 348 | * // This will PUT /notes/ID with the note object in the request payload 349 | * }]); 350 | * ``` 351 | */ 352 | angular.module('ngResource', ['ng']). 353 | provider('$resource', function() { 354 | var provider = this; 355 | 356 | this.defaults = { 357 | // Strip slashes by default 358 | stripTrailingSlashes: true, 359 | 360 | // Default actions configuration 361 | actions: { 362 | 'get': {method: 'GET'}, 363 | 'save': {method: 'POST'}, 364 | 'query': {method: 'GET', isArray: true}, 365 | 'remove': {method: 'DELETE'}, 366 | 'delete': {method: 'DELETE'} 367 | } 368 | }; 369 | 370 | this.$get = ['$http', '$q', function($http, $q) { 371 | 372 | var noop = angular.noop, 373 | forEach = angular.forEach, 374 | extend = angular.extend, 375 | copy = angular.copy, 376 | isFunction = angular.isFunction; 377 | 378 | /** 379 | * We need our custom method because encodeURIComponent is too aggressive and doesn't follow 380 | * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set 381 | * (pchar) allowed in path segments: 382 | * segment = *pchar 383 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 384 | * pct-encoded = "%" HEXDIG HEXDIG 385 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 386 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 387 | * / "*" / "+" / "," / ";" / "=" 388 | */ 389 | function encodeUriSegment(val) { 390 | return encodeUriQuery(val, true). 391 | replace(/%26/gi, '&'). 392 | replace(/%3D/gi, '='). 393 | replace(/%2B/gi, '+'); 394 | } 395 | 396 | 397 | /** 398 | * This method is intended for encoding *key* or *value* parts of query component. We need a 399 | * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't 400 | * have to be encoded per http://tools.ietf.org/html/rfc3986: 401 | * query = *( pchar / "/" / "?" ) 402 | * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 403 | * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 404 | * pct-encoded = "%" HEXDIG HEXDIG 405 | * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" 406 | * / "*" / "+" / "," / ";" / "=" 407 | */ 408 | function encodeUriQuery(val, pctEncodeSpaces) { 409 | return encodeURIComponent(val). 410 | replace(/%40/gi, '@'). 411 | replace(/%3A/gi, ':'). 412 | replace(/%24/g, '$'). 413 | replace(/%2C/gi, ','). 414 | replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); 415 | } 416 | 417 | function Route(template, defaults) { 418 | this.template = template; 419 | this.defaults = extend({}, provider.defaults, defaults); 420 | this.urlParams = {}; 421 | } 422 | 423 | Route.prototype = { 424 | setUrlParams: function(config, params, actionUrl) { 425 | var self = this, 426 | url = actionUrl || self.template, 427 | val, 428 | encodedVal; 429 | 430 | var urlParams = self.urlParams = {}; 431 | forEach(url.split(/\W/), function(param) { 432 | if (param === 'hasOwnProperty') { 433 | throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); 434 | } 435 | if (!(new RegExp("^\\d+$").test(param)) && param && 436 | (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { 437 | urlParams[param] = true; 438 | } 439 | }); 440 | url = url.replace(/\\:/g, ':'); 441 | 442 | params = params || {}; 443 | forEach(self.urlParams, function(_, urlParam) { 444 | val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; 445 | if (angular.isDefined(val) && val !== null) { 446 | encodedVal = encodeUriSegment(val); 447 | url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), function(match, p1) { 448 | return encodedVal + p1; 449 | }); 450 | } else { 451 | url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, 452 | leadingSlashes, tail) { 453 | if (tail.charAt(0) == '/') { 454 | return tail; 455 | } else { 456 | return leadingSlashes + tail; 457 | } 458 | }); 459 | } 460 | }); 461 | 462 | // strip trailing slashes and set the url (unless this behavior is specifically disabled) 463 | if (self.defaults.stripTrailingSlashes) { 464 | url = url.replace(/\/+$/, '') || '/'; 465 | } 466 | 467 | // then replace collapse `/.` if found in the last URL path segment before the query 468 | // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x` 469 | url = url.replace(/\/\.(?=\w+($|\?))/, '.'); 470 | // replace escaped `/\.` with `/.` 471 | config.url = url.replace(/\/\\\./, '/.'); 472 | 473 | 474 | // set params - delegate param encoding to $http 475 | forEach(params, function(value, key) { 476 | if (!self.urlParams[key]) { 477 | config.params = config.params || {}; 478 | config.params[key] = value; 479 | } 480 | }); 481 | } 482 | }; 483 | 484 | 485 | function resourceFactory(url, paramDefaults, actions, options) { 486 | var route = new Route(url, options); 487 | 488 | actions = extend({}, provider.defaults.actions, actions); 489 | 490 | function extractParams(data, actionParams) { 491 | var ids = {}; 492 | actionParams = extend({}, paramDefaults, actionParams); 493 | forEach(actionParams, function(value, key) { 494 | if (isFunction(value)) { value = value(); } 495 | ids[key] = value && value.charAt && value.charAt(0) == '@' ? 496 | lookupDottedPath(data, value.substr(1)) : value; 497 | }); 498 | return ids; 499 | } 500 | 501 | function defaultResponseInterceptor(response) { 502 | return response.resource; 503 | } 504 | 505 | function Resource(value) { 506 | shallowClearAndCopy(value || {}, this); 507 | } 508 | 509 | Resource.prototype.toJSON = function() { 510 | var data = extend({}, this); 511 | delete data.$promise; 512 | delete data.$resolved; 513 | return data; 514 | }; 515 | 516 | forEach(actions, function(action, name) { 517 | var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); 518 | 519 | Resource[name] = function(a1, a2, a3, a4) { 520 | var params = {}, data, success, error; 521 | 522 | /* jshint -W086 */ /* (purposefully fall through case statements) */ 523 | switch (arguments.length) { 524 | case 4: 525 | error = a4; 526 | success = a3; 527 | //fallthrough 528 | case 3: 529 | case 2: 530 | if (isFunction(a2)) { 531 | if (isFunction(a1)) { 532 | success = a1; 533 | error = a2; 534 | break; 535 | } 536 | 537 | success = a2; 538 | error = a3; 539 | //fallthrough 540 | } else { 541 | params = a1; 542 | data = a2; 543 | success = a3; 544 | break; 545 | } 546 | case 1: 547 | if (isFunction(a1)) success = a1; 548 | else if (hasBody) data = a1; 549 | else params = a1; 550 | break; 551 | case 0: break; 552 | default: 553 | throw $resourceMinErr('badargs', 554 | "Expected up to 4 arguments [params, data, success, error], got {0} arguments", 555 | arguments.length); 556 | } 557 | /* jshint +W086 */ /* (purposefully fall through case statements) */ 558 | 559 | var isInstanceCall = this instanceof Resource; 560 | var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); 561 | var httpConfig = {}; 562 | var responseInterceptor = action.interceptor && action.interceptor.response || 563 | defaultResponseInterceptor; 564 | var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || 565 | undefined; 566 | 567 | forEach(action, function(value, key) { 568 | if (key != 'params' && key != 'isArray' && key != 'interceptor') { 569 | httpConfig[key] = copy(value); 570 | } 571 | }); 572 | 573 | if (hasBody) httpConfig.data = data; 574 | route.setUrlParams(httpConfig, 575 | extend({}, extractParams(data, action.params || {}), params), 576 | action.url); 577 | 578 | var promise = $http(httpConfig).then(function(response) { 579 | var data = response.data, 580 | promise = value.$promise; 581 | 582 | if (data) { 583 | // Need to convert action.isArray to boolean in case it is undefined 584 | // jshint -W018 585 | if (angular.isArray(data) !== (!!action.isArray)) { 586 | throw $resourceMinErr('badcfg', 587 | 'Error in resource configuration for action `{0}`. Expected response to ' + 588 | 'contain an {1} but got an {2}', name, action.isArray ? 'array' : 'object', 589 | angular.isArray(data) ? 'array' : 'object'); 590 | } 591 | // jshint +W018 592 | if (action.isArray) { 593 | value.length = 0; 594 | forEach(data, function(item) { 595 | if (typeof item === "object") { 596 | value.push(new Resource(item)); 597 | } else { 598 | // Valid JSON values may be string literals, and these should not be converted 599 | // into objects. These items will not have access to the Resource prototype 600 | // methods, but unfortunately there 601 | value.push(item); 602 | } 603 | }); 604 | } else { 605 | shallowClearAndCopy(data, value); 606 | value.$promise = promise; 607 | } 608 | } 609 | 610 | value.$resolved = true; 611 | 612 | response.resource = value; 613 | 614 | return response; 615 | }, function(response) { 616 | value.$resolved = true; 617 | 618 | (error || noop)(response); 619 | 620 | return $q.reject(response); 621 | }); 622 | 623 | promise = promise.then( 624 | function(response) { 625 | var value = responseInterceptor(response); 626 | (success || noop)(value, response.headers); 627 | return value; 628 | }, 629 | responseErrorInterceptor); 630 | 631 | if (!isInstanceCall) { 632 | // we are creating instance / collection 633 | // - set the initial promise 634 | // - return the instance / collection 635 | value.$promise = promise; 636 | value.$resolved = false; 637 | 638 | return value; 639 | } 640 | 641 | // instance call 642 | return promise; 643 | }; 644 | 645 | 646 | Resource.prototype['$' + name] = function(params, success, error) { 647 | if (isFunction(params)) { 648 | error = success; success = params; params = {}; 649 | } 650 | var result = Resource[name].call(this, params, this, success, error); 651 | return result.$promise || result; 652 | }; 653 | }); 654 | 655 | Resource.bind = function(additionalParamDefaults) { 656 | return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); 657 | }; 658 | 659 | return Resource; 660 | } 661 | 662 | return resourceFactory; 663 | }]; 664 | }); 665 | 666 | 667 | })(window, window.angular); 668 | -------------------------------------------------------------------------------- /public/javascripts/angular-resource.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.11 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(I,d,B){'use strict';function D(f,q){q=q||{};d.forEach(q,function(d,h){delete q[h]});for(var h in f)!f.hasOwnProperty(h)||"$"===h.charAt(0)&&"$"===h.charAt(1)||(q[h]=f[h]);return q}var w=d.$$minErr("$resource"),C=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;d.module("ngResource",["ng"]).provider("$resource",function(){var f=this;this.defaults={stripTrailingSlashes:!0,actions:{get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}}; 7 | this.$get=["$http","$q",function(q,h){function t(d,g){this.template=d;this.defaults=s({},f.defaults,g);this.urlParams={}}function v(x,g,l,m){function c(b,k){var c={};k=s({},g,k);r(k,function(a,k){u(a)&&(a=a());var d;if(a&&a.charAt&&"@"==a.charAt(0)){d=b;var e=a.substr(1);if(null==e||""===e||"hasOwnProperty"===e||!C.test("."+e))throw w("badmember",e);for(var e=e.split("."),n=0,g=e.length;n 22 | */ 23 | /* global -ngRouteModule */ 24 | var ngRouteModule = angular.module('ngRoute', ['ng']). 25 | provider('$route', $RouteProvider), 26 | $routeMinErr = angular.$$minErr('ngRoute'); 27 | 28 | /** 29 | * @ngdoc provider 30 | * @name $routeProvider 31 | * 32 | * @description 33 | * 34 | * Used for configuring routes. 35 | * 36 | * ## Example 37 | * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. 38 | * 39 | * ## Dependencies 40 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 41 | */ 42 | function $RouteProvider() { 43 | function inherit(parent, extra) { 44 | return angular.extend(Object.create(parent), extra); 45 | } 46 | 47 | var routes = {}; 48 | 49 | /** 50 | * @ngdoc method 51 | * @name $routeProvider#when 52 | * 53 | * @param {string} path Route path (matched against `$location.path`). If `$location.path` 54 | * contains redundant trailing slash or is missing one, the route will still match and the 55 | * `$location.path` will be updated to add or drop the trailing slash to exactly match the 56 | * route definition. 57 | * 58 | * * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up 59 | * to the next slash are matched and stored in `$routeParams` under the given `name` 60 | * when the route matches. 61 | * * `path` can contain named groups starting with a colon and ending with a star: 62 | * e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name` 63 | * when the route matches. 64 | * * `path` can contain optional named groups with a question mark: e.g.`:name?`. 65 | * 66 | * For example, routes like `/color/:color/largecode/:largecode*\/edit` will match 67 | * `/color/brown/largecode/code/with/slashes/edit` and extract: 68 | * 69 | * * `color: brown` 70 | * * `largecode: code/with/slashes`. 71 | * 72 | * 73 | * @param {Object} route Mapping information to be assigned to `$route.current` on route 74 | * match. 75 | * 76 | * Object properties: 77 | * 78 | * - `controller` – `{(string|function()=}` – Controller fn that should be associated with 79 | * newly created scope or the name of a {@link angular.Module#controller registered 80 | * controller} if passed as a string. 81 | * - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be 82 | * published to scope under the `controllerAs` name. 83 | * - `template` – `{string=|function()=}` – html template as a string or a function that 84 | * returns an html template as a string which should be used by {@link 85 | * ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. 86 | * This property takes precedence over `templateUrl`. 87 | * 88 | * If `template` is a function, it will be called with the following parameters: 89 | * 90 | * - `{Array.}` - route parameters extracted from the current 91 | * `$location.path()` by applying the current route 92 | * 93 | * - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html 94 | * template that should be used by {@link ngRoute.directive:ngView ngView}. 95 | * 96 | * If `templateUrl` is a function, it will be called with the following parameters: 97 | * 98 | * - `{Array.}` - route parameters extracted from the current 99 | * `$location.path()` by applying the current route 100 | * 101 | * - `resolve` - `{Object.=}` - An optional map of dependencies which should 102 | * be injected into the controller. If any of these dependencies are promises, the router 103 | * will wait for them all to be resolved or one to be rejected before the controller is 104 | * instantiated. 105 | * If all the promises are resolved successfully, the values of the resolved promises are 106 | * injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is 107 | * fired. If any of the promises are rejected the 108 | * {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object 109 | * is: 110 | * 111 | * - `key` – `{string}`: a name of a dependency to be injected into the controller. 112 | * - `factory` - `{string|function}`: If `string` then it is an alias for a service. 113 | * Otherwise if function, then it is {@link auto.$injector#invoke injected} 114 | * and the return value is treated as the dependency. If the result is a promise, it is 115 | * resolved before its value is injected into the controller. Be aware that 116 | * `ngRoute.$routeParams` will still refer to the previous route within these resolve 117 | * functions. Use `$route.current.params` to access the new route parameters, instead. 118 | * 119 | * - `redirectTo` – {(string|function())=} – value to update 120 | * {@link ng.$location $location} path with and trigger route redirection. 121 | * 122 | * If `redirectTo` is a function, it will be called with the following parameters: 123 | * 124 | * - `{Object.}` - route parameters extracted from the current 125 | * `$location.path()` by applying the current route templateUrl. 126 | * - `{string}` - current `$location.path()` 127 | * - `{Object}` - current `$location.search()` 128 | * 129 | * The custom `redirectTo` function is expected to return a string which will be used 130 | * to update `$location.path()` and `$location.search()`. 131 | * 132 | * - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` 133 | * or `$location.hash()` changes. 134 | * 135 | * If the option is set to `false` and url in the browser changes, then 136 | * `$routeUpdate` event is broadcasted on the root scope. 137 | * 138 | * - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive 139 | * 140 | * If the option is set to `true`, then the particular route can be matched without being 141 | * case sensitive 142 | * 143 | * @returns {Object} self 144 | * 145 | * @description 146 | * Adds a new route definition to the `$route` service. 147 | */ 148 | this.when = function(path, route) { 149 | //copy original route object to preserve params inherited from proto chain 150 | var routeCopy = angular.copy(route); 151 | if (angular.isUndefined(routeCopy.reloadOnSearch)) { 152 | routeCopy.reloadOnSearch = true; 153 | } 154 | if (angular.isUndefined(routeCopy.caseInsensitiveMatch)) { 155 | routeCopy.caseInsensitiveMatch = this.caseInsensitiveMatch; 156 | } 157 | routes[path] = angular.extend( 158 | routeCopy, 159 | path && pathRegExp(path, routeCopy) 160 | ); 161 | 162 | // create redirection for trailing slashes 163 | if (path) { 164 | var redirectPath = (path[path.length - 1] == '/') 165 | ? path.substr(0, path.length - 1) 166 | : path + '/'; 167 | 168 | routes[redirectPath] = angular.extend( 169 | {redirectTo: path}, 170 | pathRegExp(redirectPath, routeCopy) 171 | ); 172 | } 173 | 174 | return this; 175 | }; 176 | 177 | /** 178 | * @ngdoc property 179 | * @name $routeProvider#caseInsensitiveMatch 180 | * @description 181 | * 182 | * A boolean property indicating if routes defined 183 | * using this provider should be matched using a case insensitive 184 | * algorithm. Defaults to `false`. 185 | */ 186 | this.caseInsensitiveMatch = false; 187 | 188 | /** 189 | * @param path {string} path 190 | * @param opts {Object} options 191 | * @return {?Object} 192 | * 193 | * @description 194 | * Normalizes the given path, returning a regular expression 195 | * and the original path. 196 | * 197 | * Inspired by pathRexp in visionmedia/express/lib/utils.js. 198 | */ 199 | function pathRegExp(path, opts) { 200 | var insensitive = opts.caseInsensitiveMatch, 201 | ret = { 202 | originalPath: path, 203 | regexp: path 204 | }, 205 | keys = ret.keys = []; 206 | 207 | path = path 208 | .replace(/([().])/g, '\\$1') 209 | .replace(/(\/)?:(\w+)([\?\*])?/g, function(_, slash, key, option) { 210 | var optional = option === '?' ? option : null; 211 | var star = option === '*' ? option : null; 212 | keys.push({ name: key, optional: !!optional }); 213 | slash = slash || ''; 214 | return '' 215 | + (optional ? '' : slash) 216 | + '(?:' 217 | + (optional ? slash : '') 218 | + (star && '(.+?)' || '([^/]+)') 219 | + (optional || '') 220 | + ')' 221 | + (optional || ''); 222 | }) 223 | .replace(/([\/$\*])/g, '\\$1'); 224 | 225 | ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); 226 | return ret; 227 | } 228 | 229 | /** 230 | * @ngdoc method 231 | * @name $routeProvider#otherwise 232 | * 233 | * @description 234 | * Sets route definition that will be used on route change when no other route definition 235 | * is matched. 236 | * 237 | * @param {Object|string} params Mapping information to be assigned to `$route.current`. 238 | * If called with a string, the value maps to `redirectTo`. 239 | * @returns {Object} self 240 | */ 241 | this.otherwise = function(params) { 242 | if (typeof params === 'string') { 243 | params = {redirectTo: params}; 244 | } 245 | this.when(null, params); 246 | return this; 247 | }; 248 | 249 | 250 | this.$get = ['$rootScope', 251 | '$location', 252 | '$routeParams', 253 | '$q', 254 | '$injector', 255 | '$templateRequest', 256 | '$sce', 257 | function($rootScope, $location, $routeParams, $q, $injector, $templateRequest, $sce) { 258 | 259 | /** 260 | * @ngdoc service 261 | * @name $route 262 | * @requires $location 263 | * @requires $routeParams 264 | * 265 | * @property {Object} current Reference to the current route definition. 266 | * The route definition contains: 267 | * 268 | * - `controller`: The controller constructor as define in route definition. 269 | * - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for 270 | * controller instantiation. The `locals` contain 271 | * the resolved values of the `resolve` map. Additionally the `locals` also contain: 272 | * 273 | * - `$scope` - The current route scope. 274 | * - `$template` - The current route template HTML. 275 | * 276 | * @property {Object} routes Object with all route configuration Objects as its properties. 277 | * 278 | * @description 279 | * `$route` is used for deep-linking URLs to controllers and views (HTML partials). 280 | * It watches `$location.url()` and tries to map the path to an existing route definition. 281 | * 282 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 283 | * 284 | * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. 285 | * 286 | * The `$route` service is typically used in conjunction with the 287 | * {@link ngRoute.directive:ngView `ngView`} directive and the 288 | * {@link ngRoute.$routeParams `$routeParams`} service. 289 | * 290 | * @example 291 | * This example shows how changing the URL hash causes the `$route` to match a route against the 292 | * URL, and the `ngView` pulls in the partial. 293 | * 294 | * 296 | * 297 | *
298 | * Choose: 299 | * Moby | 300 | * Moby: Ch1 | 301 | * Gatsby | 302 | * Gatsby: Ch4 | 303 | * Scarlet Letter
304 | * 305 | *
306 | * 307 | *
308 | * 309 | *
$location.path() = {{$location.path()}}
310 | *
$route.current.templateUrl = {{$route.current.templateUrl}}
311 | *
$route.current.params = {{$route.current.params}}
312 | *
$route.current.scope.name = {{$route.current.scope.name}}
313 | *
$routeParams = {{$routeParams}}
314 | *
315 | *
316 | * 317 | * 318 | * controller: {{name}}
319 | * Book Id: {{params.bookId}}
320 | *
321 | * 322 | * 323 | * controller: {{name}}
324 | * Book Id: {{params.bookId}}
325 | * Chapter Id: {{params.chapterId}} 326 | *
327 | * 328 | * 329 | * angular.module('ngRouteExample', ['ngRoute']) 330 | * 331 | * .controller('MainController', function($scope, $route, $routeParams, $location) { 332 | * $scope.$route = $route; 333 | * $scope.$location = $location; 334 | * $scope.$routeParams = $routeParams; 335 | * }) 336 | * 337 | * .controller('BookController', function($scope, $routeParams) { 338 | * $scope.name = "BookController"; 339 | * $scope.params = $routeParams; 340 | * }) 341 | * 342 | * .controller('ChapterController', function($scope, $routeParams) { 343 | * $scope.name = "ChapterController"; 344 | * $scope.params = $routeParams; 345 | * }) 346 | * 347 | * .config(function($routeProvider, $locationProvider) { 348 | * $routeProvider 349 | * .when('/Book/:bookId', { 350 | * templateUrl: 'book.html', 351 | * controller: 'BookController', 352 | * resolve: { 353 | * // I will cause a 1 second delay 354 | * delay: function($q, $timeout) { 355 | * var delay = $q.defer(); 356 | * $timeout(delay.resolve, 1000); 357 | * return delay.promise; 358 | * } 359 | * } 360 | * }) 361 | * .when('/Book/:bookId/ch/:chapterId', { 362 | * templateUrl: 'chapter.html', 363 | * controller: 'ChapterController' 364 | * }); 365 | * 366 | * // configure html5 to get links working on jsfiddle 367 | * $locationProvider.html5Mode(true); 368 | * }); 369 | * 370 | * 371 | * 372 | * 373 | * it('should load and compile correct template', function() { 374 | * element(by.linkText('Moby: Ch1')).click(); 375 | * var content = element(by.css('[ng-view]')).getText(); 376 | * expect(content).toMatch(/controller\: ChapterController/); 377 | * expect(content).toMatch(/Book Id\: Moby/); 378 | * expect(content).toMatch(/Chapter Id\: 1/); 379 | * 380 | * element(by.partialLinkText('Scarlet')).click(); 381 | * 382 | * content = element(by.css('[ng-view]')).getText(); 383 | * expect(content).toMatch(/controller\: BookController/); 384 | * expect(content).toMatch(/Book Id\: Scarlet/); 385 | * }); 386 | * 387 | *
388 | */ 389 | 390 | /** 391 | * @ngdoc event 392 | * @name $route#$routeChangeStart 393 | * @eventType broadcast on root scope 394 | * @description 395 | * Broadcasted before a route change. At this point the route services starts 396 | * resolving all of the dependencies needed for the route change to occur. 397 | * Typically this involves fetching the view template as well as any dependencies 398 | * defined in `resolve` route property. Once all of the dependencies are resolved 399 | * `$routeChangeSuccess` is fired. 400 | * 401 | * The route change (and the `$location` change that triggered it) can be prevented 402 | * by calling `preventDefault` method of the event. See {@link ng.$rootScope.Scope#$on} 403 | * for more details about event object. 404 | * 405 | * @param {Object} angularEvent Synthetic event object. 406 | * @param {Route} next Future route information. 407 | * @param {Route} current Current route information. 408 | */ 409 | 410 | /** 411 | * @ngdoc event 412 | * @name $route#$routeChangeSuccess 413 | * @eventType broadcast on root scope 414 | * @description 415 | * Broadcasted after a route dependencies are resolved. 416 | * {@link ngRoute.directive:ngView ngView} listens for the directive 417 | * to instantiate the controller and render the view. 418 | * 419 | * @param {Object} angularEvent Synthetic event object. 420 | * @param {Route} current Current route information. 421 | * @param {Route|Undefined} previous Previous route information, or undefined if current is 422 | * first route entered. 423 | */ 424 | 425 | /** 426 | * @ngdoc event 427 | * @name $route#$routeChangeError 428 | * @eventType broadcast on root scope 429 | * @description 430 | * Broadcasted if any of the resolve promises are rejected. 431 | * 432 | * @param {Object} angularEvent Synthetic event object 433 | * @param {Route} current Current route information. 434 | * @param {Route} previous Previous route information. 435 | * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. 436 | */ 437 | 438 | /** 439 | * @ngdoc event 440 | * @name $route#$routeUpdate 441 | * @eventType broadcast on root scope 442 | * @description 443 | * 444 | * The `reloadOnSearch` property has been set to false, and we are reusing the same 445 | * instance of the Controller. 446 | */ 447 | 448 | var forceReload = false, 449 | preparedRoute, 450 | preparedRouteIsUpdateOnly, 451 | $route = { 452 | routes: routes, 453 | 454 | /** 455 | * @ngdoc method 456 | * @name $route#reload 457 | * 458 | * @description 459 | * Causes `$route` service to reload the current route even if 460 | * {@link ng.$location $location} hasn't changed. 461 | * 462 | * As a result of that, {@link ngRoute.directive:ngView ngView} 463 | * creates new scope and reinstantiates the controller. 464 | */ 465 | reload: function() { 466 | forceReload = true; 467 | $rootScope.$evalAsync(function() { 468 | // Don't support cancellation of a reload for now... 469 | prepareRoute(); 470 | commitRoute(); 471 | }); 472 | }, 473 | 474 | /** 475 | * @ngdoc method 476 | * @name $route#updateParams 477 | * 478 | * @description 479 | * Causes `$route` service to update the current URL, replacing 480 | * current route parameters with those specified in `newParams`. 481 | * Provided property names that match the route's path segment 482 | * definitions will be interpolated into the location's path, while 483 | * remaining properties will be treated as query params. 484 | * 485 | * @param {Object} newParams mapping of URL parameter names to values 486 | */ 487 | updateParams: function(newParams) { 488 | if (this.current && this.current.$$route) { 489 | var searchParams = {}, self=this; 490 | 491 | angular.forEach(Object.keys(newParams), function(key) { 492 | if (!self.current.pathParams[key]) searchParams[key] = newParams[key]; 493 | }); 494 | 495 | newParams = angular.extend({}, this.current.params, newParams); 496 | $location.path(interpolate(this.current.$$route.originalPath, newParams)); 497 | $location.search(angular.extend({}, $location.search(), searchParams)); 498 | } 499 | else { 500 | throw $routeMinErr('norout', 'Tried updating route when with no current route'); 501 | } 502 | } 503 | }; 504 | 505 | $rootScope.$on('$locationChangeStart', prepareRoute); 506 | $rootScope.$on('$locationChangeSuccess', commitRoute); 507 | 508 | return $route; 509 | 510 | ///////////////////////////////////////////////////// 511 | 512 | /** 513 | * @param on {string} current url 514 | * @param route {Object} route regexp to match the url against 515 | * @return {?Object} 516 | * 517 | * @description 518 | * Check if the route matches the current url. 519 | * 520 | * Inspired by match in 521 | * visionmedia/express/lib/router/router.js. 522 | */ 523 | function switchRouteMatcher(on, route) { 524 | var keys = route.keys, 525 | params = {}; 526 | 527 | if (!route.regexp) return null; 528 | 529 | var m = route.regexp.exec(on); 530 | if (!m) return null; 531 | 532 | for (var i = 1, len = m.length; i < len; ++i) { 533 | var key = keys[i - 1]; 534 | 535 | var val = m[i]; 536 | 537 | if (key && val) { 538 | params[key.name] = val; 539 | } 540 | } 541 | return params; 542 | } 543 | 544 | function prepareRoute($locationEvent) { 545 | var lastRoute = $route.current; 546 | 547 | preparedRoute = parseRoute(); 548 | preparedRouteIsUpdateOnly = preparedRoute && lastRoute && preparedRoute.$$route === lastRoute.$$route 549 | && angular.equals(preparedRoute.pathParams, lastRoute.pathParams) 550 | && !preparedRoute.reloadOnSearch && !forceReload; 551 | 552 | if (!preparedRouteIsUpdateOnly && (lastRoute || preparedRoute)) { 553 | if ($rootScope.$broadcast('$routeChangeStart', preparedRoute, lastRoute).defaultPrevented) { 554 | if ($locationEvent) { 555 | $locationEvent.preventDefault(); 556 | } 557 | } 558 | } 559 | } 560 | 561 | function commitRoute() { 562 | var lastRoute = $route.current; 563 | var nextRoute = preparedRoute; 564 | 565 | if (preparedRouteIsUpdateOnly) { 566 | lastRoute.params = nextRoute.params; 567 | angular.copy(lastRoute.params, $routeParams); 568 | $rootScope.$broadcast('$routeUpdate', lastRoute); 569 | } else if (nextRoute || lastRoute) { 570 | forceReload = false; 571 | $route.current = nextRoute; 572 | if (nextRoute) { 573 | if (nextRoute.redirectTo) { 574 | if (angular.isString(nextRoute.redirectTo)) { 575 | $location.path(interpolate(nextRoute.redirectTo, nextRoute.params)).search(nextRoute.params) 576 | .replace(); 577 | } else { 578 | $location.url(nextRoute.redirectTo(nextRoute.pathParams, $location.path(), $location.search())) 579 | .replace(); 580 | } 581 | } 582 | } 583 | 584 | $q.when(nextRoute). 585 | then(function() { 586 | if (nextRoute) { 587 | var locals = angular.extend({}, nextRoute.resolve), 588 | template, templateUrl; 589 | 590 | angular.forEach(locals, function(value, key) { 591 | locals[key] = angular.isString(value) ? 592 | $injector.get(value) : $injector.invoke(value, null, null, key); 593 | }); 594 | 595 | if (angular.isDefined(template = nextRoute.template)) { 596 | if (angular.isFunction(template)) { 597 | template = template(nextRoute.params); 598 | } 599 | } else if (angular.isDefined(templateUrl = nextRoute.templateUrl)) { 600 | if (angular.isFunction(templateUrl)) { 601 | templateUrl = templateUrl(nextRoute.params); 602 | } 603 | templateUrl = $sce.getTrustedResourceUrl(templateUrl); 604 | if (angular.isDefined(templateUrl)) { 605 | nextRoute.loadedTemplateUrl = templateUrl; 606 | template = $templateRequest(templateUrl); 607 | } 608 | } 609 | if (angular.isDefined(template)) { 610 | locals['$template'] = template; 611 | } 612 | return $q.all(locals); 613 | } 614 | }). 615 | // after route change 616 | then(function(locals) { 617 | if (nextRoute == $route.current) { 618 | if (nextRoute) { 619 | nextRoute.locals = locals; 620 | angular.copy(nextRoute.params, $routeParams); 621 | } 622 | $rootScope.$broadcast('$routeChangeSuccess', nextRoute, lastRoute); 623 | } 624 | }, function(error) { 625 | if (nextRoute == $route.current) { 626 | $rootScope.$broadcast('$routeChangeError', nextRoute, lastRoute, error); 627 | } 628 | }); 629 | } 630 | } 631 | 632 | 633 | /** 634 | * @returns {Object} the current active route, by matching it against the URL 635 | */ 636 | function parseRoute() { 637 | // Match a route 638 | var params, match; 639 | angular.forEach(routes, function(route, path) { 640 | if (!match && (params = switchRouteMatcher($location.path(), route))) { 641 | match = inherit(route, { 642 | params: angular.extend({}, $location.search(), params), 643 | pathParams: params}); 644 | match.$$route = route; 645 | } 646 | }); 647 | // No route matched; fallback to "otherwise" route 648 | return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); 649 | } 650 | 651 | /** 652 | * @returns {string} interpolation of the redirect path with the parameters 653 | */ 654 | function interpolate(string, params) { 655 | var result = []; 656 | angular.forEach((string || '').split(':'), function(segment, i) { 657 | if (i === 0) { 658 | result.push(segment); 659 | } else { 660 | var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/); 661 | var key = segmentMatch[1]; 662 | result.push(params[key]); 663 | result.push(segmentMatch[2] || ''); 664 | delete params[key]; 665 | } 666 | }); 667 | return result.join(''); 668 | } 669 | }]; 670 | } 671 | 672 | ngRouteModule.provider('$routeParams', $RouteParamsProvider); 673 | 674 | 675 | /** 676 | * @ngdoc service 677 | * @name $routeParams 678 | * @requires $route 679 | * 680 | * @description 681 | * The `$routeParams` service allows you to retrieve the current set of route parameters. 682 | * 683 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 684 | * 685 | * The route parameters are a combination of {@link ng.$location `$location`}'s 686 | * {@link ng.$location#search `search()`} and {@link ng.$location#path `path()`}. 687 | * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. 688 | * 689 | * In case of parameter name collision, `path` params take precedence over `search` params. 690 | * 691 | * The service guarantees that the identity of the `$routeParams` object will remain unchanged 692 | * (but its properties will likely change) even when a route change occurs. 693 | * 694 | * Note that the `$routeParams` are only updated *after* a route change completes successfully. 695 | * This means that you cannot rely on `$routeParams` being correct in route resolve functions. 696 | * Instead you can use `$route.current.params` to access the new route's parameters. 697 | * 698 | * @example 699 | * ```js 700 | * // Given: 701 | * // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby 702 | * // Route: /Chapter/:chapterId/Section/:sectionId 703 | * // 704 | * // Then 705 | * $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'} 706 | * ``` 707 | */ 708 | function $RouteParamsProvider() { 709 | this.$get = function() { return {}; }; 710 | } 711 | 712 | ngRouteModule.directive('ngView', ngViewFactory); 713 | ngRouteModule.directive('ngView', ngViewFillContentFactory); 714 | 715 | 716 | /** 717 | * @ngdoc directive 718 | * @name ngView 719 | * @restrict ECA 720 | * 721 | * @description 722 | * # Overview 723 | * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by 724 | * including the rendered template of the current route into the main layout (`index.html`) file. 725 | * Every time the current route changes, the included view changes with it according to the 726 | * configuration of the `$route` service. 727 | * 728 | * Requires the {@link ngRoute `ngRoute`} module to be installed. 729 | * 730 | * @animations 731 | * enter - animation is used to bring new content into the browser. 732 | * leave - animation is used to animate existing content away. 733 | * 734 | * The enter and leave animation occur concurrently. 735 | * 736 | * @scope 737 | * @priority 400 738 | * @param {string=} onload Expression to evaluate whenever the view updates. 739 | * 740 | * @param {string=} autoscroll Whether `ngView` should call {@link ng.$anchorScroll 741 | * $anchorScroll} to scroll the viewport after the view is updated. 742 | * 743 | * - If the attribute is not set, disable scrolling. 744 | * - If the attribute is set without value, enable scrolling. 745 | * - Otherwise enable scrolling only if the `autoscroll` attribute value evaluated 746 | * as an expression yields a truthy value. 747 | * @example 748 | 751 | 752 |
753 | Choose: 754 | Moby | 755 | Moby: Ch1 | 756 | Gatsby | 757 | Gatsby: Ch4 | 758 | Scarlet Letter
759 | 760 |
761 |
762 |
763 |
764 | 765 |
$location.path() = {{main.$location.path()}}
766 |
$route.current.templateUrl = {{main.$route.current.templateUrl}}
767 |
$route.current.params = {{main.$route.current.params}}
768 |
$routeParams = {{main.$routeParams}}
769 |
770 |
771 | 772 | 773 |
774 | controller: {{book.name}}
775 | Book Id: {{book.params.bookId}}
776 |
777 |
778 | 779 | 780 |
781 | controller: {{chapter.name}}
782 | Book Id: {{chapter.params.bookId}}
783 | Chapter Id: {{chapter.params.chapterId}} 784 |
785 |
786 | 787 | 788 | .view-animate-container { 789 | position:relative; 790 | height:100px!important; 791 | background:white; 792 | border:1px solid black; 793 | height:40px; 794 | overflow:hidden; 795 | } 796 | 797 | .view-animate { 798 | padding:10px; 799 | } 800 | 801 | .view-animate.ng-enter, .view-animate.ng-leave { 802 | -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 803 | transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; 804 | 805 | display:block; 806 | width:100%; 807 | border-left:1px solid black; 808 | 809 | position:absolute; 810 | top:0; 811 | left:0; 812 | right:0; 813 | bottom:0; 814 | padding:10px; 815 | } 816 | 817 | .view-animate.ng-enter { 818 | left:100%; 819 | } 820 | .view-animate.ng-enter.ng-enter-active { 821 | left:0; 822 | } 823 | .view-animate.ng-leave.ng-leave-active { 824 | left:-100%; 825 | } 826 | 827 | 828 | 829 | angular.module('ngViewExample', ['ngRoute', 'ngAnimate']) 830 | .config(['$routeProvider', '$locationProvider', 831 | function($routeProvider, $locationProvider) { 832 | $routeProvider 833 | .when('/Book/:bookId', { 834 | templateUrl: 'book.html', 835 | controller: 'BookCtrl', 836 | controllerAs: 'book' 837 | }) 838 | .when('/Book/:bookId/ch/:chapterId', { 839 | templateUrl: 'chapter.html', 840 | controller: 'ChapterCtrl', 841 | controllerAs: 'chapter' 842 | }); 843 | 844 | $locationProvider.html5Mode(true); 845 | }]) 846 | .controller('MainCtrl', ['$route', '$routeParams', '$location', 847 | function($route, $routeParams, $location) { 848 | this.$route = $route; 849 | this.$location = $location; 850 | this.$routeParams = $routeParams; 851 | }]) 852 | .controller('BookCtrl', ['$routeParams', function($routeParams) { 853 | this.name = "BookCtrl"; 854 | this.params = $routeParams; 855 | }]) 856 | .controller('ChapterCtrl', ['$routeParams', function($routeParams) { 857 | this.name = "ChapterCtrl"; 858 | this.params = $routeParams; 859 | }]); 860 | 861 | 862 | 863 | 864 | it('should load and compile correct template', function() { 865 | element(by.linkText('Moby: Ch1')).click(); 866 | var content = element(by.css('[ng-view]')).getText(); 867 | expect(content).toMatch(/controller\: ChapterCtrl/); 868 | expect(content).toMatch(/Book Id\: Moby/); 869 | expect(content).toMatch(/Chapter Id\: 1/); 870 | 871 | element(by.partialLinkText('Scarlet')).click(); 872 | 873 | content = element(by.css('[ng-view]')).getText(); 874 | expect(content).toMatch(/controller\: BookCtrl/); 875 | expect(content).toMatch(/Book Id\: Scarlet/); 876 | }); 877 | 878 |
879 | */ 880 | 881 | 882 | /** 883 | * @ngdoc event 884 | * @name ngView#$viewContentLoaded 885 | * @eventType emit on the current ngView scope 886 | * @description 887 | * Emitted every time the ngView content is reloaded. 888 | */ 889 | ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate']; 890 | function ngViewFactory($route, $anchorScroll, $animate) { 891 | return { 892 | restrict: 'ECA', 893 | terminal: true, 894 | priority: 400, 895 | transclude: 'element', 896 | link: function(scope, $element, attr, ctrl, $transclude) { 897 | var currentScope, 898 | currentElement, 899 | previousLeaveAnimation, 900 | autoScrollExp = attr.autoscroll, 901 | onloadExp = attr.onload || ''; 902 | 903 | scope.$on('$routeChangeSuccess', update); 904 | update(); 905 | 906 | function cleanupLastView() { 907 | if (previousLeaveAnimation) { 908 | $animate.cancel(previousLeaveAnimation); 909 | previousLeaveAnimation = null; 910 | } 911 | 912 | if (currentScope) { 913 | currentScope.$destroy(); 914 | currentScope = null; 915 | } 916 | if (currentElement) { 917 | previousLeaveAnimation = $animate.leave(currentElement); 918 | previousLeaveAnimation.then(function() { 919 | previousLeaveAnimation = null; 920 | }); 921 | currentElement = null; 922 | } 923 | } 924 | 925 | function update() { 926 | var locals = $route.current && $route.current.locals, 927 | template = locals && locals.$template; 928 | 929 | if (angular.isDefined(template)) { 930 | var newScope = scope.$new(); 931 | var current = $route.current; 932 | 933 | // Note: This will also link all children of ng-view that were contained in the original 934 | // html. If that content contains controllers, ... they could pollute/change the scope. 935 | // However, using ng-view on an element with additional content does not make sense... 936 | // Note: We can't remove them in the cloneAttchFn of $transclude as that 937 | // function is called before linking the content, which would apply child 938 | // directives to non existing elements. 939 | var clone = $transclude(newScope, function(clone) { 940 | $animate.enter(clone, null, currentElement || $element).then(function onNgViewEnter() { 941 | if (angular.isDefined(autoScrollExp) 942 | && (!autoScrollExp || scope.$eval(autoScrollExp))) { 943 | $anchorScroll(); 944 | } 945 | }); 946 | cleanupLastView(); 947 | }); 948 | 949 | currentElement = clone; 950 | currentScope = current.scope = newScope; 951 | currentScope.$emit('$viewContentLoaded'); 952 | currentScope.$eval(onloadExp); 953 | } else { 954 | cleanupLastView(); 955 | } 956 | } 957 | } 958 | }; 959 | } 960 | 961 | // This directive is called during the $transclude call of the first `ngView` directive. 962 | // It will replace and compile the content of the element with the loaded template. 963 | // We need this directive so that the element content is already filled when 964 | // the link function of another directive on the same element as ngView 965 | // is called. 966 | ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route']; 967 | function ngViewFillContentFactory($compile, $controller, $route) { 968 | return { 969 | restrict: 'ECA', 970 | priority: -400, 971 | link: function(scope, $element) { 972 | var current = $route.current, 973 | locals = current.locals; 974 | 975 | $element.html(locals.$template); 976 | 977 | var link = $compile($element.contents()); 978 | 979 | if (current.controller) { 980 | locals.$scope = scope; 981 | var controller = $controller(current.controller, locals); 982 | if (current.controllerAs) { 983 | scope[current.controllerAs] = controller; 984 | } 985 | $element.data('$ngControllerController', controller); 986 | $element.children().data('$ngControllerController', controller); 987 | } 988 | 989 | link(scope); 990 | } 991 | }; 992 | } 993 | 994 | 995 | })(window, window.angular); 996 | -------------------------------------------------------------------------------- /public/javascripts/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.3.11 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(p,d,C){'use strict';function v(r,h,g){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,y){function z(){k&&(g.cancel(k),k=null);l&&(l.$destroy(),l=null);m&&(k=g.leave(m),k.then(function(){k=null}),m=null)}function x(){var b=r.current&&r.current.locals;if(d.isDefined(b&&b.$template)){var b=a.$new(),f=r.current;m=y(b,function(b){g.enter(b,null,m||c).then(function(){!d.isDefined(t)||t&&!a.$eval(t)||h()});z()});l=f.scope=b;l.$emit("$viewContentLoaded"); 7 | l.$eval(w)}else z()}var l,m,k,t=b.autoscroll,w=b.onload||"";a.$on("$routeChangeSuccess",x);x()}}}function A(d,h,g){return{restrict:"ECA",priority:-400,link:function(a,c){var b=g.current,f=b.locals;c.html(f.$template);var y=d(c.contents());b.controller&&(f.$scope=a,f=h(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));y(a)}}}p=d.module("ngRoute",["ng"]).provider("$route",function(){function r(a,c){return d.extend(Object.create(a), 8 | c)}function h(a,d){var b=d.caseInsensitiveMatch,f={originalPath:a,regexp:a},g=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,d,b,c){a="?"===c?c:null;c="*"===c?c:null;g.push({name:b,optional:!!a});d=d||"";return""+(a?"":d)+"(?:"+(a?d:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=new RegExp("^"+a+"$",b?"i":"");return f}var g={};this.when=function(a,c){var b=d.copy(c);d.isUndefined(b.reloadOnSearch)&&(b.reloadOnSearch=!0); 9 | d.isUndefined(b.caseInsensitiveMatch)&&(b.caseInsensitiveMatch=this.caseInsensitiveMatch);g[a]=d.extend(b,a&&h(a,b));if(a){var f="/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";g[f]=d.extend({redirectTo:a},h(f,b))}return this};this.caseInsensitiveMatch=!1;this.otherwise=function(a){"string"===typeof a&&(a={redirectTo:a});this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$templateRequest","$sce",function(a,c,b,f,h,p,x){function l(b){var e=s.current; 10 | (v=(n=k())&&e&&n.$$route===e.$$route&&d.equals(n.pathParams,e.pathParams)&&!n.reloadOnSearch&&!w)||!e&&!n||a.$broadcast("$routeChangeStart",n,e).defaultPrevented&&b&&b.preventDefault()}function m(){var u=s.current,e=n;if(v)u.params=e.params,d.copy(u.params,b),a.$broadcast("$routeUpdate",u);else if(e||u)w=!1,(s.current=e)&&e.redirectTo&&(d.isString(e.redirectTo)?c.path(t(e.redirectTo,e.params)).search(e.params).replace():c.url(e.redirectTo(e.pathParams,c.path(),c.search())).replace()),f.when(e).then(function(){if(e){var a= 11 | d.extend({},e.resolve),b,c;d.forEach(a,function(b,e){a[e]=d.isString(b)?h.get(b):h.invoke(b,null,null,e)});d.isDefined(b=e.template)?d.isFunction(b)&&(b=b(e.params)):d.isDefined(c=e.templateUrl)&&(d.isFunction(c)&&(c=c(e.params)),c=x.getTrustedResourceUrl(c),d.isDefined(c)&&(e.loadedTemplateUrl=c,b=p(c)));d.isDefined(b)&&(a.$template=b);return f.all(a)}}).then(function(c){e==s.current&&(e&&(e.locals=c,d.copy(e.params,b)),a.$broadcast("$routeChangeSuccess",e,u))},function(b){e==s.current&&a.$broadcast("$routeChangeError", 12 | e,u,b)})}function k(){var a,b;d.forEach(g,function(f,g){var q;if(q=!b){var h=c.path();q=f.keys;var l={};if(f.regexp)if(h=f.regexp.exec(h)){for(var k=1,m=h.length;k").append(b).html();try{return b[0].nodeType===pb?Q(c):c.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+Q(b)})}catch(d){return Q(c)}}function pc(b){try{return decodeURIComponent(b)}catch(a){}}function qc(b){var a={},c,d;s((b||"").split("&"),function(b){b&& 15 | (c=b.replace(/\+/g,"%20").split("="),d=pc(c[0]),y(d)&&(b=y(c[1])?pc(c[1]):!0,rc.call(a,d)?D(a[d])?a[d].push(b):a[d]=[a[d],b]:a[d]=b))});return a}function Nb(b){var a=[];s(b,function(b,d){D(b)?s(b,function(b){a.push(Fa(d,!0)+(!0===b?"":"="+Fa(b,!0)))}):a.push(Fa(d,!0)+(!0===b?"":"="+Fa(b,!0)))});return a.length?a.join("&"):""}function qb(b){return Fa(b,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function Fa(b,a){return encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi, 16 | ":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,a?"%20":"+")}function Id(b,a){var c,d,e=rb.length;b=B(b);for(d=0;d/,">"));}a=a||[];a.unshift(["$provide",function(a){a.value("$rootElement",b)}]);c.debugInfoEnabled&&a.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);a.unshift("ng");d=Ob(a,c.strictDi);d.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return d}, 18 | e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;M&&e.test(M.name)&&(c.debugInfoEnabled=!0,M.name=M.name.replace(e,""));if(M&&!f.test(M.name))return d();M.name=M.name.replace(f,"");ga.resumeBootstrap=function(b){s(b,function(b){a.push(b)});d()}}function Kd(){M.name="NG_ENABLE_DEBUG_INFO!"+M.name;M.location.reload()}function Ld(b){b=ga.element(b).injector();if(!b)throw Ka("test");return b.get("$$testability")}function tc(b,a){a=a||"_";return b.replace(Md,function(b,d){return(d?a:"")+b.toLowerCase()})} 19 | function Nd(){var b;uc||((sa=M.jQuery)&&sa.fn.on?(B=sa,z(sa.fn,{scope:La.scope,isolateScope:La.isolateScope,controller:La.controller,injector:La.injector,inheritedData:La.inheritedData}),b=sa.cleanData,sa.cleanData=function(a){var c;if(Pb)Pb=!1;else for(var d=0,e;null!=(e=a[d]);d++)(c=sa._data(e,"events"))&&c.$destroy&&sa(e).triggerHandler("$destroy");b(a)}):B=R,ga.element=B,uc=!0)}function Qb(b,a,c){if(!b)throw Ka("areq",a||"?",c||"required");return b}function sb(b,a,c){c&&D(b)&&(b=b[b.length-1]); 20 | Qb(G(b),a,"not a function, got "+(b&&"object"===typeof b?b.constructor.name||"Object":typeof b));return b}function Ma(b,a){if("hasOwnProperty"===b)throw Ka("badname",a);}function vc(b,a,c){if(!a)return b;a=a.split(".");for(var d,e=b,f=a.length,g=0;g")+d[2];for(d=d[0];d--;)c=c.lastChild;f=Ya(f,c.childNodes);c=e.firstChild;c.textContent=""}else f.push(a.createTextNode(b));e.textContent="";e.innerHTML="";s(f,function(a){e.appendChild(a)});return e}function R(b){if(b instanceof 27 | R)return b;var a;F(b)&&(b=U(b),a=!0);if(!(this instanceof R)){if(a&&"<"!=b.charAt(0))throw Sb("nosel");return new R(b)}if(a){a=Y;var c;b=(c=gf.exec(b))?[a.createElement(c[1])]:(c=Fc(b,a))?c.childNodes:[]}Gc(this,b)}function Tb(b){return b.cloneNode(!0)}function wb(b,a){a||xb(b);if(b.querySelectorAll)for(var c=b.querySelectorAll("*"),d=0,e=c.length;d 4096 bytes)!"));else{if(n.cookie!==y)for(y=n.cookie,d=y.split("; "),ea={},f=0;fk&&this.remove(q.key), 46 | b},get:function(a){if(k").parent()[0])});var f=S(a,b,a,c,d,e);E.$$addScopeClass(a);var g=null;return function(b,c,d){Qb(b,"scope");d=d||{};var e=d.parentBoundTranscludeFn,h=d.transcludeControllers;d=d.futureParentElement;e&&e.$$boundTransclude&&(e=e.$$boundTransclude);g||(g=(d=d&&d[0])?"foreignobject"!==ua(d)&&d.toString().match(/SVG/)?"svg":"html":"html");d="html"!==g?B(Wb(g,B("
").append(a).html())): 51 | c?La.clone.call(a):a;if(h)for(var l in h)d.data("$"+l+"Controller",h[l].instance);E.$$addScopeInfo(d,b);c&&c(d,b);f&&f(b,d,d,e);return d}}function S(a,b,c,d,e,f){function g(a,c,d,e){var f,l,k,q,n,p,w;if(r)for(w=Array(c.length),q=0;qK.priority)break;if(N=K.scope)K.templateUrl||(I(N)?(Oa("new/isolated scope",S||P,K,aa),S=K):Oa("new/isolated scope",S,K,aa)),P=P||K;z=K.name;!K.templateUrl&&K.controller&&(N=K.controller, 61 | C=C||{},Oa("'"+z+"' controller",C[z],K,aa),C[z]=K);if(N=K.transclude)ca=!0,K.$$tlb||(Oa("transclusion",ea,K,aa),ea=K),"element"==N?(H=!0,x=K.priority,N=aa,aa=e.$$element=B(Y.createComment(" "+z+": "+e[z]+" ")),d=aa[0],V(g,Za.call(N,0),d),Aa=E(N,f,x,l&&l.name,{nonTlbTranscludeDirective:ea})):(N=B(Tb(d)).contents(),aa.empty(),Aa=E(N,f));if(K.template)if(A=!0,Oa("template",ka,K,aa),ka=K,N=G(K.template)?K.template(aa,e):K.template,N=Sc(N),K.replace){l=K;N=Rb.test(N)?Tc(Wb(K.templateNamespace,U(N))):[]; 62 | d=N[0];if(1!=N.length||d.nodeType!==oa)throw ja("tplrt",z,"");V(g,aa,d);R={$attr:{}};N=W(d,[],R);var ba=a.splice(M+1,a.length-(M+1));S&&y(N);a=a.concat(N).concat(ba);Qc(e,R);R=a.length}else aa.html(N);if(K.templateUrl)A=!0,Oa("template",ka,K,aa),ka=K,K.replace&&(l=K),v=T(a.splice(M,a.length-M),aa,e,g,ca&&Aa,k,n,{controllerDirectives:C,newIsolateScopeDirective:S,templateDirective:ka,nonTlbTranscludeDirective:ea}),R=a.length;else if(K.compile)try{Q=K.compile(aa,e,Aa),G(Q)?w(null,Q,Pa,fb):Q&&w(Q.pre, 63 | Q.post,Pa,fb)}catch(qf){c(qf,va(aa))}K.terminal&&(v.terminal=!0,x=Math.max(x,K.priority))}v.scope=P&&!0===P.scope;v.transcludeOnThisElement=ca;v.elementTranscludeOnThisElement=H;v.templateOnThisElement=A;v.transclude=Aa;r.hasElementTranscludeDirective=H;return v}function y(a){for(var b=0,c=a.length;bq.priority)&&-1!=q.restrict.indexOf(f)){if(l){var w={$$start:l,$$end:k};q=z(Object.create(q),w)}b.push(q);h=q}}catch(O){c(O)}}return h}function A(b){if(d.hasOwnProperty(b))for(var c=a.get(b+"Directive"),e=0,f=c.length;e"+b+"";return c.childNodes[0].childNodes;default:return b}}function R(a,b){if("srcdoc"==b)return L.HTML;var c=ua(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return L.RESOURCE_URL}function Pa(a,c,d,e,f){var h=R(a,e);f=g[e]||f;var k=b(d,!0, 69 | h,f);if(k){if("multiple"===e&&"select"===ua(a))throw ja("selmulti",va(a));c.push({priority:100,compile:function(){return{pre:function(a,c,g){c=g.$$observers||(g.$$observers={});if(l.test(e))throw ja("nodomevents");var n=g[e];n!==d&&(k=n&&b(n,!0,h,f),d=n);k&&(g[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(g.$$observers&&g.$$observers[e].$$scope||a).$watch(k,function(a,b){"class"===e&&a!=b?g.$updateClass(a,b):g.$set(e,a)}))}}}})}}function V(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g< 70 | h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var l=a.length;g=a)return b;for(;a--;)8===b[a].nodeType&&rf.call(b,a,1);return b}function Fe(){var b={},a=!1,c=/^(\S+)(\s+as\s+(\w+))?$/;this.register=function(a,c){Ma(a,"controller");I(a)?z(b,a):b[a]=c};this.allowGlobals=function(){a=!0};this.$get=["$injector","$window",function(d,e){function f(a,b,c,d){if(!a||!I(a.$scope))throw T("$controller")("noscp",d,b);a.$scope[b]=c}return function(g,h, 76 | l,k){var m,n,q;l=!0===l;k&&F(k)&&(q=k);F(g)&&(k=g.match(c),n=k[1],q=q||k[3],g=b.hasOwnProperty(n)?b[n]:vc(h.$scope,n,!0)||(a?vc(e,n,!0):t),sb(g,n,!0));if(l)return l=(D(g)?g[g.length-1]:g).prototype,m=Object.create(l||null),q&&f(h,q,m,n||g.name),z(function(){d.invoke(g,m,h,n);return m},{instance:m,identifier:q});m=d.instantiate(g,h,n);q&&f(h,q,m,n||g.name);return m}}]}function Ge(){this.$get=["$window",function(b){return B(b.document)}]}function He(){this.$get=["$log",function(b){return function(a, 77 | c){b.error.apply(b,arguments)}}]}function Yb(b,a){if(F(b)){var c=b.replace(sf,"").trim();if(c){var d=a("Content-Type");(d=d&&0===d.indexOf(Vc))||(d=(d=c.match(tf))&&uf[d[0]].test(c));d&&(b=oc(c))}}return b}function Wc(b){var a=ha(),c,d,e;if(!b)return a;s(b.split("\n"),function(b){e=b.indexOf(":");c=Q(U(b.substr(0,e)));d=U(b.substr(e+1));c&&(a[c]=a[c]?a[c]+", "+d:d)});return a}function Xc(b){var a=I(b)?b:t;return function(c){a||(a=Wc(b));return c?(c=a[Q(c)],void 0===c&&(c=null),c):a}}function Yc(b, 78 | a,c,d){if(G(d))return d(b,a,c);s(d,function(d){b=d(b,a,c)});return b}function Ke(){var b=this.defaults={transformResponse:[Yb],transformRequest:[function(a){return I(a)&&"[object File]"!==Da.call(a)&&"[object Blob]"!==Da.call(a)&&"[object FormData]"!==Da.call(a)?$a(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ra(Zb),put:ra(Zb),patch:ra(Zb)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN"},a=!1;this.useApplyAsync=function(b){return y(b)?(a=!!b,this):a};var c=this.interceptors= 79 | [];this.$get=["$httpBackend","$browser","$cacheFactory","$rootScope","$q","$injector",function(d,e,f,g,h,l){function k(a){function c(a){var b=z({},a);b.data=a.data?Yc(a.data,a.headers,a.status,e.transformResponse):a.data;a=a.status;return 200<=a&&300>a?b:h.reject(b)}function d(a){var b,c={};s(a,function(a,d){G(a)?(b=a(),null!=b&&(c[d]=b)):c[d]=a});return c}if(!ga.isObject(a))throw T("$http")("badreq",a);var e=z({method:"get",transformRequest:b.transformRequest,transformResponse:b.transformResponse}, 80 | a);e.headers=function(a){var c=b.headers,e=z({},a.headers),f,g,c=z({},c.common,c[Q(a.method)]);a:for(f in c){a=Q(f);for(g in e)if(Q(g)===a)continue a;e[f]=c[f]}return d(e)}(a);e.method=ub(e.method);var f=[function(a){var d=a.headers,e=Yc(a.data,Xc(d),t,a.transformRequest);A(e)&&s(d,function(a,b){"content-type"===Q(b)&&delete d[b]});A(a.withCredentials)&&!A(b.withCredentials)&&(a.withCredentials=b.withCredentials);return m(a,e).then(c,c)},t],g=h.when(e);for(s(u,function(a){(a.request||a.requestError)&& 81 | f.unshift(a.request,a.requestError);(a.response||a.responseError)&&f.push(a.response,a.responseError)});f.length;){a=f.shift();var l=f.shift(),g=g.then(a,l)}g.success=function(a){g.then(function(b){a(b.data,b.status,b.headers,e)});return g};g.error=function(a){g.then(null,function(b){a(b.data,b.status,b.headers,e)});return g};return g}function m(c,f){function l(b,c,d,e){function f(){m(c,b,d,e)}P&&(200<=b&&300>b?P.put(X,[b,c,Wc(d),e]):P.remove(X));a?g.$applyAsync(f):(f(),g.$$phase||g.$apply())}function m(a, 82 | b,d,e){b=Math.max(b,0);(200<=b&&300>b?C.resolve:C.reject)({data:a,status:b,headers:Xc(d),config:c,statusText:e})}function w(a){m(a.data,a.status,ra(a.headers()),a.statusText)}function u(){var a=k.pendingRequests.indexOf(c);-1!==a&&k.pendingRequests.splice(a,1)}var C=h.defer(),x=C.promise,P,E,s=c.headers,X=n(c.url,c.params);k.pendingRequests.push(c);x.then(u,u);!c.cache&&!b.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(P=I(c.cache)?c.cache:I(b.cache)?b.cache:q);P&&(E=P.get(X),y(E)?E&& 83 | G(E.then)?E.then(w,w):D(E)?m(E[1],E[0],ra(E[2]),E[3]):m(E,200,{},"OK"):P.put(X,x));A(E)&&((E=Zc(c.url)?e.cookies()[c.xsrfCookieName||b.xsrfCookieName]:t)&&(s[c.xsrfHeaderName||b.xsrfHeaderName]=E),d(c.method,X,f,l,s,c.timeout,c.withCredentials,c.responseType));return x}function n(a,b){if(!b)return a;var c=[];Ed(b,function(a,b){null===a||A(a)||(D(a)||(a=[a]),s(a,function(a){I(a)&&(a=qa(a)?a.toISOString():$a(a));c.push(Fa(b)+"="+Fa(a))}))});0=l&&(r.resolve(q),n(O.$$intervalId),delete f[O.$$intervalId]);u||b.$apply()},h);f[O.$$intervalId]=r;return O}var f={};e.cancel=function(b){return b&&b.$$intervalId in f?(f[b.$$intervalId].reject("canceled"),a.clearInterval(b.$$intervalId),delete f[b.$$intervalId],!0):!1};return e}]}function Rd(){this.$get=function(){return{id:"en-us",NUMBER_FORMATS:{DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{minInt:1,minFrac:0,maxFrac:3,posPre:"",posSuf:"",negPre:"-",negSuf:"",gSize:3, 92 | lgSize:3},{minInt:1,minFrac:2,maxFrac:2,posPre:"\u00a4",posSuf:"",negPre:"(\u00a4",negSuf:")",gSize:3,lgSize:3}],CURRENCY_SYM:"$"},DATETIME_FORMATS:{MONTH:"January February March April May June July August September October November December".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),AMPMS:["AM","PM"],medium:"MMM d, y h:mm:ss a","short":"M/d/yy h:mm a", 93 | fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",mediumDate:"MMM d, y",shortDate:"M/d/yy",mediumTime:"h:mm:ss a",shortTime:"h:mm a"},pluralCat:function(b){return 1===b?"one":"other"}}}}function ac(b){b=b.split("/");for(var a=b.length;a--;)b[a]=qb(b[a]);return b.join("/")}function $c(b,a){var c=Ba(b);a.$$protocol=c.protocol;a.$$host=c.hostname;a.$$port=ba(c.port)||xf[c.protocol]||null}function ad(b,a){var c="/"!==b.charAt(0);c&&(b="/"+b);var d=Ba(b);a.$$path=decodeURIComponent(c&&"/"===d.pathname.charAt(0)? 94 | d.pathname.substring(1):d.pathname);a.$$search=qc(d.search);a.$$hash=decodeURIComponent(d.hash);a.$$path&&"/"!=a.$$path.charAt(0)&&(a.$$path="/"+a.$$path)}function za(b,a){if(0===a.indexOf(b))return a.substr(b.length)}function Ha(b){var a=b.indexOf("#");return-1==a?b:b.substr(0,a)}function bd(b){return b.replace(/(#.+)|#$/,"$1")}function bc(b){return b.substr(0,Ha(b).lastIndexOf("/")+1)}function cc(b,a){this.$$html5=!0;a=a||"";var c=bc(b);$c(b,this);this.$$parse=function(a){var b=za(c,a);if(!F(b))throw Fb("ipthprfx", 95 | a,c);ad(b,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Nb(this.$$search),b=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=ac(this.$$path)+(a?"?"+a:"")+b;this.$$absUrl=c+this.$$url.substr(1)};this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;(f=za(b,d))!==t?(g=f,g=(f=za(a,f))!==t?c+(za("/",f)||f):b+g):(f=za(c,d))!==t?g=c+f:c==d+"/"&&(g=c);g&&this.$$parse(g);return!!g}}function dc(b,a){var c=bc(b);$c(b,this);this.$$parse= 96 | function(d){d=za(b,d)||za(c,d);var e;"#"===d.charAt(0)?(e=za(a,d),A(e)&&(e=d)):e=this.$$html5?d:"";ad(e,this);d=this.$$path;var f=/^\/[A-Z]:(\/.*)/;0===e.indexOf(b)&&(e=e.replace(b,""));f.exec(e)||(d=(e=f.exec(d))?e[1]:d);this.$$path=d;this.$$compose()};this.$$compose=function(){var c=Nb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=ac(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+(this.$$url?a+this.$$url:"")};this.$$parseLinkUrl=function(a,c){return Ha(b)==Ha(a)?(this.$$parse(a),!0): 97 | !1}}function cd(b,a){this.$$html5=!0;dc.apply(this,arguments);var c=bc(b);this.$$parseLinkUrl=function(d,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;b==Ha(d)?f=d:(g=za(c,d))?f=b+a+g:c===d+"/"&&(f=c);f&&this.$$parse(f);return!!f};this.$$compose=function(){var c=Nb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=ac(this.$$path)+(c?"?"+c:"")+e;this.$$absUrl=b+a+this.$$url}}function Gb(b){return function(){return this[b]}}function dd(b,a){return function(c){if(A(c))return this[b]; 98 | this[b]=a(c);this.$$compose();return this}}function Me(){var b="",a={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(a){return y(a)?(b=a,this):b};this.html5Mode=function(b){return Wa(b)?(a.enabled=b,this):I(b)?(Wa(b.enabled)&&(a.enabled=b.enabled),Wa(b.requireBase)&&(a.requireBase=b.requireBase),Wa(b.rewriteLinks)&&(a.rewriteLinks=b.rewriteLinks),this):a};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(c,d,e,f,g){function h(a,b,c){var e=k.url(), 99 | f=k.$$state;try{d.url(a,b,c),k.$$state=d.state()}catch(g){throw k.url(e),k.$$state=f,g;}}function l(a,b){c.$broadcast("$locationChangeSuccess",k.absUrl(),a,k.$$state,b)}var k,m;m=d.baseHref();var n=d.url(),q;if(a.enabled){if(!m&&a.requireBase)throw Fb("nobase");q=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?cc:cd}else q=Ha(n),m=dc;k=new m(q,"#"+b);k.$$parseLinkUrl(n,n);k.$$state=d.state();var u=/^\s*(javascript|mailto):/i;f.on("click",function(b){if(a.rewriteLinks&&!b.ctrlKey&& 100 | !b.metaKey&&!b.shiftKey&&2!=b.which&&2!=b.button){for(var e=B(b.target);"a"!==ua(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),l=e.attr("href")||e.attr("xlink:href");I(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Ba(h.animVal).href);u.test(h)||!h||e.attr("target")||b.isDefaultPrevented()||!k.$$parseLinkUrl(h,l)||(b.preventDefault(),k.absUrl()!=d.url()&&(c.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});k.absUrl()!=n&&d.url(k.absUrl(),!0);var r=!0;d.onUrlChange(function(a, 101 | b){c.$evalAsync(function(){var d=k.absUrl(),e=k.$$state,f;k.$$parse(a);k.$$state=b;f=c.$broadcast("$locationChangeStart",a,d,b,e).defaultPrevented;k.absUrl()===a&&(f?(k.$$parse(d),k.$$state=e,h(d,!1,e)):(r=!1,l(d,e)))});c.$$phase||c.$digest()});c.$watch(function(){var a=bd(d.url()),b=bd(k.absUrl()),f=d.state(),g=k.$$replace,q=a!==b||k.$$html5&&e.history&&f!==k.$$state;if(r||q)r=!1,c.$evalAsync(function(){var b=k.absUrl(),d=c.$broadcast("$locationChangeStart",b,a,k.$$state,f).defaultPrevented;k.absUrl()=== 102 | b&&(d?(k.$$parse(a),k.$$state=f):(q&&h(b,g,f===k.$$state?null:k.$$state),l(a,f)))});k.$$replace=!1});return k}]}function Ne(){var b=!0,a=this;this.debugEnabled=function(a){return y(a)?(b=a,this):b};this.$get=["$window",function(c){function d(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=c.console||{},e=b[a]||b.log||H;a=!1;try{a=!!e.apply}catch(l){}return a? 103 | function(){var a=[];s(arguments,function(b){a.push(d(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){b&&c.apply(a,arguments)}}()}}]}function ta(b,a){if("__defineGetter__"===b||"__defineSetter__"===b||"__lookupGetter__"===b||"__lookupSetter__"===b||"__proto__"===b)throw la("isecfld",a);return b}function ma(b,a){if(b){if(b.constructor===b)throw la("isecfn",a);if(b.window=== 104 | b)throw la("isecwindow",a);if(b.children&&(b.nodeName||b.prop&&b.attr&&b.find))throw la("isecdom",a);if(b===Object)throw la("isecobj",a);}return b}function ec(b){return b.constant}function gb(b,a,c,d,e){ma(b,e);ma(a,e);c=c.split(".");for(var f,g=0;1h?ed(g[0],g[1],g[2],g[3],g[4],c,d):function(a,b){var e=0,f;do f=ed(g[e++],g[e++],g[e++],g[e++],g[e++],c,d)(a,b),b=t,a=f;while(e=this.promise.$$state.status&&d&&d.length&&b(function(){for(var b,e,f=0,g=d.length;fa)for(b in k++,f)e.hasOwnProperty(b)||(u--,delete f[b])}else f!==e&&(f=e,k++);return k}}c.$stateful=!0;var d=this,e,f,h,l=1s&&(y=4-s,W[y]||(W[y]=[]),W[y].push({msg:G(e.exp)?"fn: "+(e.exp.name||e.exp.toString()):e.exp,newVal:g,oldVal:l}));else if(e===c){v=!1;break a}}catch(A){f(A)}if(!(m=t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(m=t.$$nextSibling);)t=t.$parent}while(t=m);if((v||O.length)&&!s--)throw r.$$phase=null,a("infdig",b,W);}while(v||O.length);for(r.$$phase=null;p.length;)try{p.shift()()}catch(ca){f(ca)}},$destroy:function(){if(!this.$$destroyed){var a= 125 | this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;if(this!==r){for(var b in this.$$listenerCount)m(this,this.$$listenerCount[b],b);a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=H;this.$on=this.$watch=this.$watchGroup= 126 | function(){return H};this.$$listeners={};this.$parent=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=this.$root=this.$$watchers=null}}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){r.$$phase||O.length||h.defer(function(){O.length&&r.$digest()});O.push({scope:this,expression:a,locals:b})},$$postDigest:function(a){p.push(a)},$apply:function(a){try{return k("$apply"),this.$eval(a)}catch(b){f(b)}finally{r.$$phase=null;try{r.$digest()}catch(c){throw f(c),c; 127 | }}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&v.push(b);u()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,m(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1}, 128 | l=Ya([h],arguments,1),k,m;do{d=e.$$listeners[a]||c;h.currentScope=e;k=0;for(m=d.length;kRa)throw Ca("iequirks");var d=ra(na);d.isEnabled=function(){return b};d.trustAs=c.trustAs;d.getTrusted=c.getTrusted;d.valueOf=c.valueOf;b||(d.trustAs=d.getTrusted=function(a,b){return b},d.valueOf=pa);d.parseAs=function(b,c){var e=a(c);return e.literal&&e.constant?e:a(c,function(a){return d.getTrusted(b,a)})};var e=d.parseAs,f=d.getTrusted,g=d.trustAs;s(na,function(a,b){var c=Q(b);d[cb("parse_as_"+c)]=function(b){return e(a,b)};d[cb("get_trusted_"+c)]=function(b){return f(a,b)};d[cb("trust_as_"+ 135 | c)]=function(b){return g(a,b)}});return d}]}function Ue(){this.$get=["$window","$document",function(b,a){var c={},d=ba((/android (\d+)/.exec(Q((b.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((b.navigator||{}).userAgent),f=a[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,l=f.body&&f.body.style,k=!1,m=!1;if(l){for(var n in l)if(k=h.exec(n)){g=k[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in l&&"webkit");k=!!("transition"in l||g+"Transition"in l);m=!!("animation"in l||g+"Animation"in 136 | l);!d||k&&m||(k=F(f.body.style.webkitTransition),m=F(f.body.style.webkitAnimation))}return{history:!(!b.history||!b.history.pushState||4>d||e),hasEvent:function(a){if("input"===a&&11>=Ra)return!1;if(A(c[a])){var b=f.createElement("div");c[a]="on"+a in b}return c[a]},csp:ab(),vendorPrefix:g,transitions:k,animations:m,android:d}}]}function We(){this.$get=["$templateCache","$http","$q",function(b,a,c){function d(e,f){d.totalPendingRequests++;var g=a.defaults&&a.defaults.transformResponse;D(g)?g=g.filter(function(a){return a!== 137 | Yb}):g===Yb&&(g=null);return a.get(e,{cache:b,transformResponse:g}).finally(function(){d.totalPendingRequests--}).then(function(a){return a.data},function(a){if(!f)throw ja("tpload",e);return c.reject(a)})}d.totalPendingRequests=0;return d}]}function Xe(){this.$get=["$rootScope","$browser","$location",function(b,a,c){return{findBindings:function(a,b,c){a=a.getElementsByClassName("ng-binding");var g=[];s(a,function(a){var d=ga.element(a).data("$binding");d&&s(d,function(d){c?(new RegExp("(^|\\s)"+ 138 | gd(b)+"(\\s|\\||$)")).test(d)&&g.push(a):-1!=d.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,c){for(var g=["ng-","data-ng-","ng\\:"],h=0;hb;b=Math.abs(b);var g=b+"",h="",l=[],k=!1;if(-1!==g.indexOf("e")){var m=g.match(/([\d\.]+)e(-?)(\d+)/);m&& 144 | "-"==m[2]&&m[3]>e+1?b=0:(h=g,k=!0)}if(k)0b&&(h=b.toFixed(e),b=parseFloat(h));else{g=(g.split(od)[1]||"").length;A(e)&&(e=Math.min(Math.max(a.minFrac,g),a.maxFrac));b=+(Math.round(+(b.toString()+"e"+e)).toString()+"e"+-e);var g=(""+b).split(od),k=g[0],g=g[1]||"",n=0,q=a.lgSize,u=a.gSize;if(k.length>=q+u)for(n=k.length-q,m=0;mb&&(d="-",b=-b);for(b=""+b;b.length-c)e+=c;0===e&&-12==c&&(e=12);return Hb(e,a,d)}}function Ib(b,a){return function(c,d){var e=c["get"+b](),f=ub(a?"SHORT"+b:b);return d[f][e]}}function pd(b){var a=(new Date(b,0,1)).getDay();return new Date(b,0,(4>=a?5:12)-a)}function qd(b){return function(a){var c= 146 | pd(a.getFullYear());a=+new Date(a.getFullYear(),a.getMonth(),a.getDate()+(4-a.getDay()))-+c;a=1+Math.round(a/6048E5);return Hb(a,b)}}function kd(b){function a(a){var b;if(b=a.match(c)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,l=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=ba(b[9]+b[10]),g=ba(b[9]+b[11]));h.call(a,ba(b[1]),ba(b[2])-1,ba(b[3]));f=ba(b[4]||0)-f;g=ba(b[5]||0)-g;h=ba(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));l.call(a,f,g,h,b)}return a}var c=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/; 147 | return function(c,e,f){var g="",h=[],l,k;e=e||"mediumDate";e=b.DATETIME_FORMATS[e]||e;F(c)&&(c=Kf.test(c)?ba(c):a(c));V(c)&&(c=new Date(c));if(!qa(c))return c;for(;e;)(k=Lf.exec(e))?(h=Ya(h,k,1),e=h.pop()):(h.push(e),e=null);f&&"UTC"===f&&(c=new Date(c.getTime()),c.setMinutes(c.getMinutes()+c.getTimezoneOffset()));s(h,function(a){l=Mf[a];g+=l?l(c,b.DATETIME_FORMATS):a.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function Ff(){return function(b,a){A(a)&&(a=2);return $a(b,a)}}function Gf(){return function(b, 148 | a){V(b)&&(b=b.toString());return D(b)||F(b)?(a=Infinity===Math.abs(Number(a))?Number(a):ba(a))?0b||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))a.on("paste cut",m)}a.on("change",l);d.$render=function(){a.val(d.$isEmpty(d.$viewValue)?"":d.$viewValue)}}function Lb(b,a){return function(c,d){var e,f;if(qa(c))return c;if(F(c)){'"'==c.charAt(0)&&'"'==c.charAt(c.length-1)&&(c=c.substring(1,c.length-1));if(Nf.test(c))return new Date(c);b.lastIndex= 155 | 0;if(e=b.exec(c))return e.shift(),f=d?{yyyy:d.getFullYear(),MM:d.getMonth()+1,dd:d.getDate(),HH:d.getHours(),mm:d.getMinutes(),ss:d.getSeconds(),sss:d.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},s(e,function(b,c){c=s}; 157 | g.$observe("min",function(a){s=q(a);h.$validate()})}if(y(g.max)||g.ngMax){var p;h.$validators.max=function(a){return!n(a)||A(p)||c(a)<=p};g.$observe("max",function(a){p=q(a);h.$validate()})}}}function td(b,a,c,d){(d.$$hasNativeValidators=I(a[0].validity))&&d.$parsers.push(function(b){var c=a.prop("validity")||{};return c.badInput&&!c.typeMismatch?t:b})}function ud(b,a,c,d,e){if(y(d)){b=b(d);if(!b.constant)throw T("ngModel")("constexpr",c,d);return b(a)}return e}function ic(b,a){b="ngClass"+b;return["$animate", 158 | function(c){function d(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Rb=/<|&#?\w+;/,ef=/<([\w:]+)/,ff=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,ia={option:[1,'"],thead:[1,"","
"],col:[2,"", 164 | "
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option;ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead;ia.th=ia.td;var La=R.prototype={ready:function(b){function a(){c||(c=!0,b())}var c=!1;"complete"===Y.readyState?setTimeout(a):(this.on("DOMContentLoaded",a),R(M).on("load",a))},toString:function(){var b=[];s(this,function(a){b.push(""+a)});return"["+b.join(", ")+"]"},eq:function(b){return 0<= 165 | b?B(this[b]):B(this[this.length+b])},length:0,push:Pf,sort:[].sort,splice:[].splice},Eb={};s("multiple selected checked disabled readOnly required open".split(" "),function(b){Eb[Q(b)]=b});var Mc={};s("input select option textarea button form details".split(" "),function(b){Mc[b]=!0});var Nc={ngMinlength:"minlength",ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};s({data:Ub,removeData:xb},function(b,a){R[a]=b});s({data:Ub,inheritedData:Db,scope:function(b){return B.data(b,"$scope")|| 166 | Db(b.parentNode||b,["$isolateScope","$scope"])},isolateScope:function(b){return B.data(b,"$isolateScope")||B.data(b,"$isolateScopeNoTemplate")},controller:Ic,injector:function(b){return Db(b,"$injector")},removeAttr:function(b,a){b.removeAttribute(a)},hasClass:Ab,css:function(b,a,c){a=cb(a);if(y(c))b.style[a]=c;else return b.style[a]},attr:function(b,a,c){var d=Q(a);if(Eb[d])if(y(c))c?(b[a]=!0,b.setAttribute(a,d)):(b[a]=!1,b.removeAttribute(d));else return b[a]||(b.attributes.getNamedItem(a)||H).specified? 167 | d:t;else if(y(c))b.setAttribute(a,c);else if(b.getAttribute)return b=b.getAttribute(a,2),null===b?t:b},prop:function(b,a,c){if(y(c))b[a]=c;else return b[a]},text:function(){function b(a,b){if(A(b)){var d=a.nodeType;return d===oa||d===pb?a.textContent:""}a.textContent=b}b.$dv="";return b}(),val:function(b,a){if(A(a)){if(b.multiple&&"select"===ua(b)){var c=[];s(b.options,function(a){a.selected&&c.push(a.value||a.text)});return 0===c.length?null:c}return b.value}b.value=a},html:function(b,a){if(A(a))return b.innerHTML; 168 | wb(b,!0);b.innerHTML=a},empty:Jc},function(b,a){R.prototype[a]=function(a,d){var e,f,g=this.length;if(b!==Jc&&(2==b.length&&b!==Ab&&b!==Ic?a:d)===t){if(I(a)){for(e=0;e":function(a,c,d,e){return d(a,c)>e(a,c)},"<=":function(a, 183 | c,d,e){return d(a,c)<=e(a,c)},">=":function(a,c,d,e){return d(a,c)>=e(a,c)},"&&":function(a,c,d,e){return d(a,c)&&e(a,c)},"||":function(a,c,d,e){return d(a,c)||e(a,c)},"!":function(a,c,d){return!d(a,c)},"=":!0,"|":!0}),Xf={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},gc=function(a){this.options=a};gc.prototype={constructor:gc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,c,d){d=d||this.index;c=y(c)?"s "+c+"-"+this.index+" ["+this.text.substring(c, 186 | d)+"]":" "+d;throw la("lexerr",a,c,this.text);},readNumber:function(){for(var a="",c=this.index;this.indexa){a=this.tokens[a];var g=a.text;if(g===c||g===d||g===e||g===f||!(c||d||e||f))return a}return!1},expect:function(a,c,d,e){return(a=this.peek(a,c,d,e))?(this.tokens.shift(),a):!1},consume:function(a){if(0===this.tokens.length)throw la("ueoe",this.text);var c=this.expect(a);c||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return c},unaryFn:function(a,c){var d=mb[a];return z(function(a,f){return d(a,f,c)},{constant:c.constant,inputs:[c]})},binaryFn:function(a, 192 | c,d,e){var f=mb[c];return z(function(c,e){return f(c,e,a,d)},{constant:a.constant&&d.constant,inputs:!e&&[a,d]})},identifier:function(){for(var a=this.consume().text;this.peek(".")&&this.peekAhead(1).identifier&&!this.peekAhead(2,"(");)a+=this.consume().text+this.consume().text;return zf(a,this.options,this.text)},constant:function(){var a=this.consume().value;return z(function(){return a},{constant:!0,literal:!0})},statements:function(){for(var a=[];;)if(0","<=",">=");)a=this.binaryFn(a,c.text,this.additive());return a},additive:function(){for(var a=this.multiplicative(),c;c=this.expect("+","-");)a=this.binaryFn(a,c.text,this.multiplicative());return a},multiplicative:function(){for(var a=this.unary(),c;c=this.expect("*","/","%");)a=this.binaryFn(a,c.text,this.unary());return a},unary:function(){var a;return this.expect("+")?this.primary():(a=this.expect("-"))?this.binaryFn(hb.ZERO, 197 | a.text,this.unary()):(a=this.expect("!"))?this.unaryFn(a.text,this.unary()):this.primary()},fieldAccess:function(a){var c=this.identifier();return z(function(d,e,f){d=f||a(d,e);return null==d?t:c(d)},{assign:function(d,e,f){var g=a(d,f);g||a.assign(d,g={},f);return c.assign(g,e)}})},objectIndex:function(a){var c=this.text,d=this.expression();this.consume("]");return z(function(e,f){var g=a(e,f),h=d(e,f);ta(h,c);return g?ma(g[h],c):t},{assign:function(e,f,g){var h=ta(d(e,g),c),l=ma(a(e,g),c);l||a.assign(e, 198 | l={},g);return l[h]=f}})},functionCall:function(a,c){var d=[];if(")"!==this.peekToken().text){do d.push(this.expression());while(this.expect(","))}this.consume(")");var e=this.text,f=d.length?[]:null;return function(g,h){var l=c?c(g,h):y(c)?t:g,k=a(g,h,l)||H;if(f)for(var m=d.length;m--;)f[m]=ma(d[m](g,h),e);ma(l,e);if(k){if(k.constructor===k)throw la("isecfn",e);if(k===Uf||k===Vf||k===Wf)throw la("isecff",e);}l=k.apply?k.apply(l,f):k(f[0],f[1],f[2],f[3],f[4]);return ma(l,e)}},arrayDeclaration:function(){var a= 199 | [];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return z(function(c,d){for(var e=[],f=0,g=a.length;fa.getHours()?c.AMPMS[0]:c.AMPMS[1]},Z:function(a){a=-1*a.getTimezoneOffset();return a=(0<=a?"+":"")+(Hb(Math[0=h};d.$observe("min",function(a){y(a)&& 209 | !V(a)&&(a=parseFloat(a,10));h=V(a)&&!isNaN(a)?a:t;e.$validate()})}if(d.max||d.ngMax){var l;e.$validators.max=function(a){return e.$isEmpty(a)||A(l)||a<=l};d.$observe("max",function(a){y(a)&&!V(a)&&(a=parseFloat(a,10));l=V(a)&&!isNaN(a)?a:t;e.$validate()})}},url:function(a,c,d,e,f,g){ib(a,c,d,e,f,g);hc(e);e.$$parserName="url";e.$validators.url=function(a,c){var d=a||c;return e.$isEmpty(d)||Yf.test(d)}},email:function(a,c,d,e,f,g){ib(a,c,d,e,f,g);hc(e);e.$$parserName="email";e.$validators.email=function(a, 210 | c){var d=a||c;return e.$isEmpty(d)||Zf.test(d)}},radio:function(a,c,d,e){A(d.name)&&c.attr("name",++nb);c.on("click",function(a){c[0].checked&&e.$setViewValue(d.value,a&&a.type)});e.$render=function(){c[0].checked=d.value==e.$viewValue};d.$observe("value",e.$render)},checkbox:function(a,c,d,e,f,g,h,l){var k=ud(l,a,"ngTrueValue",d.ngTrueValue,!0),m=ud(l,a,"ngFalseValue",d.ngFalseValue,!1);c.on("click",function(a){e.$setViewValue(c[0].checked,a&&a.type)});e.$render=function(){c[0].checked=e.$viewValue}; 211 | e.$isEmpty=function(a){return!1===a};e.$formatters.push(function(a){return fa(a,k)});e.$parsers.push(function(a){return a?k:m})},hidden:H,button:H,submit:H,reset:H,file:H},xc=["$browser","$sniffer","$filter","$parse",function(a,c,d,e){return{restrict:"E",require:["?ngModel"],link:{pre:function(f,g,h,l){l[0]&&(Dd[Q(h.type)]||Dd.text)(f,g,h,l[0],c,a,d,e)}}}}],ag=/^(true|false|\d+)$/,ye=function(){return{restrict:"A",priority:100,compile:function(a,c){return ag.test(c.ngValue)?function(a,c,f){f.$set("value", 212 | a.$eval(f.ngValue))}:function(a,c,f){a.$watch(f.ngValue,function(a){f.$set("value",a)})}}}},Zd=["$compile",function(a){return{restrict:"AC",compile:function(c){a.$$addBindingClass(c);return function(c,e,f){a.$$addBindingInfo(e,f.ngBind);e=e[0];c.$watch(f.ngBind,function(a){e.textContent=a===t?"":a})}}}}],ae=["$interpolate","$compile",function(a,c){return{compile:function(d){c.$$addBindingClass(d);return function(d,f,g){d=a(f.attr(g.$attr.ngBindTemplate));c.$$addBindingInfo(f,d.expressions);f=f[0]; 213 | g.$observe("ngBindTemplate",function(a){f.textContent=a===t?"":a})}}}}],$d=["$sce","$parse","$compile",function(a,c,d){return{restrict:"A",compile:function(e,f){var g=c(f.ngBindHtml),h=c(f.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(e);return function(c,e,f){d.$$addBindingInfo(e,f.ngBindHtml);c.$watch(h,function(){e.html(a.getTrustedHtml(g(c))||"")})}}}}],xe=da({restrict:"A",require:"ngModel",link:function(a,c,d,e){e.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}), 214 | be=ic("",!0),de=ic("Odd",0),ce=ic("Even",1),ee=Ja({compile:function(a,c){c.$set("ngCloak",t);a.removeClass("ng-cloak")}}),fe=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Cc={},bg={blur:!0,focus:!0};s("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var c=ya("ng-"+a);Cc[c]=["$parse","$rootScope",function(d,e){return{restrict:"A",compile:function(f,g){var h= 215 | d(g[c],null,!0);return function(c,d){d.on(a,function(d){var f=function(){h(c,{$event:d})};bg[a]&&e.$$phase?c.$evalAsync(f):c.$apply(f)})}}}}]});var ie=["$animate",function(a){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(c,d,e,f,g){var h,l,k;c.$watch(e.ngIf,function(c){c?l||g(function(c,f){l=f;c[c.length++]=Y.createComment(" end ngIf: "+e.ngIf+" ");h={clone:c};a.enter(c,d.parent(),d)}):(k&&(k.remove(),k=null),l&&(l.$destroy(),l=null),h&&(k= 216 | tb(h.clone),a.leave(k).then(function(){k=null}),h=null))})}}}],je=["$templateRequest","$anchorScroll","$animate","$sce",function(a,c,d,e){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ga.noop,compile:function(f,g){var h=g.ngInclude||g.src,l=g.onload||"",k=g.autoscroll;return function(f,g,q,s,r){var t=0,p,v,w,L=function(){v&&(v.remove(),v=null);p&&(p.$destroy(),p=null);w&&(d.leave(w).then(function(){v=null}),v=w,w=null)};f.$watch(e.parseAsResourceUrl(h),function(e){var h= 217 | function(){!y(k)||k&&!f.$eval(k)||c()},q=++t;e?(a(e,!0).then(function(a){if(q===t){var c=f.$new();s.template=a;a=r(c,function(a){L();d.enter(a,null,g).then(h)});p=c;w=a;p.$emit("$includeContentLoaded",e);f.$eval(l)}},function(){q===t&&(L(),f.$emit("$includeContentError",e))}),f.$emit("$includeContentRequested",e)):(L(),s.template=null)})}}}}],Ae=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(c,d,e,f){/SVG/.test(d[0].toString())?(d.empty(),a(Fc(f.template, 218 | Y).childNodes)(c,function(a){d.append(a)},{futureParentElement:d})):(d.html(f.template),a(d.contents())(c))}}}],ke=Ja({priority:450,compile:function(){return{pre:function(a,c,d){a.$eval(d.ngInit)}}}}),we=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,c,d,e){var f=c.attr(d.$attr.ngList)||", ",g="false"!==d.ngTrim,h=g?U(f):f;e.$parsers.push(function(a){if(!A(a)){var c=[];a&&s(a.split(h),function(a){a&&c.push(g?U(a):a)});return c}});e.$formatters.push(function(a){return D(a)? 219 | a.join(f):t});e.$isEmpty=function(a){return!a||!a.length}}}},kb="ng-valid",vd="ng-invalid",Sa="ng-pristine",Kb="ng-dirty",xd="ng-pending",Mb=new T("ngModel"),cg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,c,d,e,f,g,h,l,k,m){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=t;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0; 220 | this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=t;this.$name=m(d.name||"",!1)(a);var n=f(d.ngModel),q=n.assign,u=n,r=q,O=null,p=this;this.$$setOptions=function(a){if((p.$options=a)&&a.getterSetter){var c=f(d.ngModel+"()"),g=f(d.ngModel+"($$$p)");u=function(a){var d=n(a);G(d)&&(d=c(a));return d};r=function(a,c){G(n(a))?g(a,{$$$p:p.$modelValue}):q(a,p.$modelValue)}}else if(!n.assign)throw Mb("nonassign",d.ngModel,va(e)); 221 | };this.$render=H;this.$isEmpty=function(a){return A(a)||""===a||null===a||a!==a};var v=e.inheritedData("$formController")||Jb,w=0;sd({ctrl:this,$element:e,set:function(a,c){a[c]=!0},unset:function(a,c){delete a[c]},parentForm:v,$animate:g});this.$setPristine=function(){p.$dirty=!1;p.$pristine=!0;g.removeClass(e,Kb);g.addClass(e,Sa)};this.$setDirty=function(){p.$dirty=!0;p.$pristine=!1;g.removeClass(e,Sa);g.addClass(e,Kb);v.$setDirty()};this.$setUntouched=function(){p.$touched=!1;p.$untouched=!0;g.setClass(e, 222 | "ng-untouched","ng-touched")};this.$setTouched=function(){p.$touched=!0;p.$untouched=!1;g.setClass(e,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){h.cancel(O);p.$viewValue=p.$$lastCommittedViewValue;p.$render()};this.$validate=function(){if(!V(p.$modelValue)||!isNaN(p.$modelValue)){var a=p.$$rawModelValue,c=p.$valid,d=p.$modelValue,e=p.$options&&p.$options.allowInvalid;p.$$runValidators(p.$error[p.$$parserName||"parse"]?!1:t,a,p.$$lastCommittedViewValue,function(f){e||c===f||(p.$modelValue= 223 | f?a:t,p.$modelValue!==d&&p.$$writeModelToScope())})}};this.$$runValidators=function(a,c,d,e){function f(){var a=!0;s(p.$validators,function(e,f){var g=e(c,d);a=a&&g;h(f,g)});return a?!0:(s(p.$asyncValidators,function(a,c){h(c,null)}),!1)}function g(){var a=[],e=!0;s(p.$asyncValidators,function(f,g){var l=f(c,d);if(!l||!G(l.then))throw Mb("$asyncValidators",l);h(g,t);a.push(l.then(function(){h(g,!0)},function(a){e=!1;h(g,!1)}))});a.length?k.all(a).then(function(){l(e)},H):l(!0)}function h(a,c){m=== 224 | w&&p.$setValidity(a,c)}function l(a){m===w&&e(a)}w++;var m=w;(function(a){var c=p.$$parserName||"parse";if(a===t)h(c,null);else if(h(c,a),!a)return s(p.$validators,function(a,c){h(c,null)}),s(p.$asyncValidators,function(a,c){h(c,null)}),!1;return!0})(a)?f()?g():l(!1):l(!1)};this.$commitViewValue=function(){var a=p.$viewValue;h.cancel(O);if(p.$$lastCommittedViewValue!==a||""===a&&p.$$hasNativeValidators)p.$$lastCommittedViewValue=a,p.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate= 225 | function(){var c=p.$$lastCommittedViewValue,d=A(c)?t:!0;if(d)for(var e=0;eF;)d=r.pop(),m(N,d.label,!1),d.element.remove()}for(;R.length>x;){l=R.pop();for(F=1;Fa&&q.removeOption(c)})}var n;if(!(n=r.match(d)))throw eg("iexp", 245 | r,va(f));var C=c(n[2]||n[1]),x=n[4]||n[6],A=/ as /.test(n[0])&&n[1],B=A?c(A):null,G=n[5],I=c(n[3]||""),F=c(n[2]?n[1]:x),P=c(n[7]),M=n[8]?c(n[8]):null,Q={},R=[[{element:f,label:""}]],T={};z&&(a(z)(e),z.removeClass("ng-scope"),z.remove());f.empty();f.on("change",function(){e.$apply(function(){var a=P(e)||[],c;if(u)c=[],s(f.val(),function(d){d=M?Q[d]:d;c.push("?"===d?t:""===d?null:h(B?B:F,d,a[d]))});else{var d=M?Q[f.val()]:f.val();c="?"===d?t:""===d?null:h(B?B:F,d,a[d])}g.$setViewValue(c);p()})});g.$render= 246 | p;e.$watchCollection(P,l);e.$watchCollection(function(){var a=P(e),c;if(a&&D(a)){c=Array(a.length);for(var d=0,f=a.length;df||e.$isEmpty(a)||c.length<=f}}}}},Ac=function(){return{restrict:"A",require:"?ngModel",link:function(a,c, 250 | d,e){if(e){var f=0;d.$observe("minlength",function(a){f=ba(a)||0;e.$validate()});e.$validators.minlength=function(a,c){return e.$isEmpty(c)||c.length>=f}}}}};M.angular.bootstrap?console.log("WARNING: Tried to load angular more than once."):(Nd(),Pd(ga),B(Y).ready(function(){Jd(Y,sc)}))})(window,document);!window.angular.$$csp()&&window.angular.element(document).find("head").prepend(''); 251 | //# sourceMappingURL=angular.min.js.map 252 | -------------------------------------------------------------------------------- /public/javascripts/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /********************************************************************** 4 | * Angular Application 5 | **********************************************************************/ 6 | var app = angular.module('app', ['ngResource', 'ngRoute']) 7 | .config(function($routeProvider, $locationProvider, $httpProvider) { 8 | //================================================ 9 | // Check if the user is connected 10 | //================================================ 11 | var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){ 12 | // Initialize a new promise 13 | var deferred = $q.defer(); 14 | 15 | // Make an AJAX call to check if the user is logged in 16 | $http.get('/loggedin').success(function(user){ 17 | // Authenticated 18 | if (user !== '0') 19 | /*$timeout(deferred.resolve, 0);*/ 20 | deferred.resolve(); 21 | 22 | // Not Authenticated 23 | else { 24 | $rootScope.message = 'You need to log in.'; 25 | //$timeout(function(){deferred.reject();}, 0); 26 | deferred.reject(); 27 | $location.url('/login'); 28 | } 29 | }); 30 | 31 | return deferred.promise; 32 | }; 33 | //================================================ 34 | 35 | //================================================ 36 | // Add an interceptor for AJAX errors 37 | //================================================ 38 | $httpProvider.interceptors.push(function($q, $location) { 39 | return { 40 | response: function(response) { 41 | // do something on success 42 | return response; 43 | }, 44 | responseError: function(response) { 45 | if (response.status === 401) 46 | $location.url('/login'); 47 | return $q.reject(response); 48 | } 49 | }; 50 | }); 51 | //================================================ 52 | 53 | //================================================ 54 | // Define all the routes 55 | //================================================ 56 | $routeProvider 57 | .when('/', { 58 | templateUrl: '/views/main.html' 59 | }) 60 | .when('/admin', { 61 | templateUrl: 'views/admin.html', 62 | controller: 'AdminCtrl', 63 | resolve: { 64 | loggedin: checkLoggedin 65 | } 66 | }) 67 | .when('/login', { 68 | templateUrl: 'views/login.html', 69 | controller: 'LoginCtrl' 70 | }) 71 | .otherwise({ 72 | redirectTo: '/' 73 | }); 74 | //================================================ 75 | 76 | }) // end of config() 77 | .run(function($rootScope, $http){ 78 | $rootScope.message = ''; 79 | 80 | // Logout function is available in any pages 81 | $rootScope.logout = function(){ 82 | $rootScope.message = 'Logged out.'; 83 | $http.post('/logout'); 84 | }; 85 | }); 86 | 87 | 88 | /********************************************************************** 89 | * Login controller 90 | **********************************************************************/ 91 | app.controller('LoginCtrl', function($scope, $rootScope, $http, $location) { 92 | // This object will be filled by the form 93 | $scope.user = {}; 94 | 95 | // Register the login() function 96 | $scope.login = function(){ 97 | $http.post('/login', { 98 | username: $scope.user.username, 99 | password: $scope.user.password, 100 | }) 101 | .success(function(user){ 102 | // No error: authentication OK 103 | $rootScope.message = 'Authentication successful!'; 104 | $location.url('/admin'); 105 | }) 106 | .error(function(){ 107 | // Error: authentication failed 108 | $rootScope.message = 'Authentication failed.'; 109 | $location.url('/login'); 110 | }); 111 | }; 112 | }); 113 | 114 | 115 | 116 | /********************************************************************** 117 | * Admin controller 118 | **********************************************************************/ 119 | app.controller('AdminCtrl', function($scope, $http) { 120 | // List of users got from the server 121 | $scope.users = []; 122 | 123 | // Fill the array to display it in the page 124 | $http.get('/users').success(function(users){ 125 | for (var i in users) 126 | $scope.users.push(users[i]); 127 | }); 128 | }); 129 | -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } -------------------------------------------------------------------------------- /public/views/admin.html: -------------------------------------------------------------------------------- 1 |

Admin

2 |

3 | Home
4 | Logout 5 |

6 |

User List

7 |
    8 |
  • {{u.name}}
  • 9 |
-------------------------------------------------------------------------------- /public/views/login.html: -------------------------------------------------------------------------------- 1 |

Login

2 |
3 |

4 | 5 | 6 |

7 |

8 | 9 |
10 |

11 |

12 | 13 |

14 |

15 | username: admin
password: admin
16 |

17 |
18 | -------------------------------------------------------------------------------- /public/views/main.html: -------------------------------------------------------------------------------- 1 |

Home

2 |

3 | Administration 4 |

-------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= title %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | {{message}} 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------