├── .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 |
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$2>")+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;q
K.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+""+a+">";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))?0 b||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,""],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 |
--------------------------------------------------------------------------------
/public/views/login.html:
--------------------------------------------------------------------------------
1 | Login
2 |
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 |
--------------------------------------------------------------------------------