PageSourceSearch

https://keepy.me/build/js/lib.js

js keepy.me collected 2026-10-02 06:15:28 UTC 321,968 bytes, 8,751 lines download raw bytes

vendor: 4,994 bytes, lines 1-128
1/**
2 * @license AngularJS v1.2.6
3 * (c) 2010-2014 Google, Inc. http://angularjs.org
4 * License: MIT
5 */
6(function(window, angular, undefined) {'use strict';
7
8var $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)
13var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;
14
15function isValidDottedPath(path) {
16  return (path != null && path !== '' && path !== 'hasOwnProperty' &&
17      MEMBER_NAME_REGEX.test('.' + path));
18}
19
20function 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 */
35function 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 overview
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 * {@installModule resource}
62 *
63 * <div doc-module-components="ngResource"></div>
64 *
65 * See {@link ngResource.$resource `$resource`} for usage.
66 */
67
68/**
69 * @ngdoc object
70 * @name ngResource.$resource
71 * @requires $http
72 *
73 * @description
74 * A factory which creates a resource object that lets you interact with
75 * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
76 *
77 * The returned resource object has action methods which provide high-level behaviors without
78 * the need to interact with the low level {@link ng.$http $http} service.
79 *
80 * Requires the {@link ngResource `ngResource`} module to be installed.
81 *
82 * @param {string} url A parametrized URL template with parameters prefixed by `:` as in
83 *   `/user/:username`. If you are using a URL with a port number (e.g.
84 *   `http://example.com:8080/api`), it will be respected.
85 *
86 *   If you are using a url with a suffix, just add the suffix, like this:
87 *   `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
88 *   or even `$resource('http://example.com/resource/:resource_id.:format')`
89 *   If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
90 *   collapsed down to a single `.`.  If you need this sequence to appear and not collapse then you
91 *   can escape it with `/\.`.
92 *
93 * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
94 *   `actions` methods. If any of the parameter value is a function, it will be executed every time
95 *   when a param value needs to be obtained for a request (unless the param was overridden).
96 *
97 *   Each key value in the parameter object is first bound to url template if present and then any
98 *   excess keys are appended to the url seapph query after the `?`.
99 *
100 *   Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
101 *   URL `/path/greet?salutation=Hello`.
102 *
103 *   If the parameter value is prefixed with `@` then the value of that parameter is extracted from
104 *   the data object (useful for non-GET operations).
105 *
106 * @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the
107 *   default set of resource actions. The declaration should be created in the format of {@link
108 *   ng.$http#usage_parameters $http.config}:
109 *
110 *       {action1: {method:?, params:?, isArray:?, headers:?, ...},
111 *        action2: {method:?, params:?, isArray:?, headers:?, ...},
112 *        ...}
113 *
114 *   Where:
115 *
116 *   - **`action`** – {string} – The name of action. This name becomes the name of the method on
117 *     your resource object.
118 *   - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`,
119 *     `DELETE`, and `JSONP`.
120 *   - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of
121 *     the parameter value is a function, it will be executed every time when a param value needs to
122 *     be obtained for a request (unless the param was overridden).
123 *   - **`url`** – {string} – action specific `url` override. The url templating is supported just
124 *     like for the resource-level urls.
125 *   - **`isArray`** – {boolean=} – If true then the returned object for this action is an array,
126 *     see `returns` section.
127 *   - **`transformRequest`** –
128 *     `{function(data, headersGetter)|Array.<function(data, headersGetter)>
128}` –
129 *     transform function or an array of such functions. The transform function takes the http
130 *     request body and headers and returns its transformed (typically serialized) version.
131 *   - **`transformResponse`** –
132 *     `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
133 *     transform function or an array of such functions. The transform function takes the http
134 *     response body and headers and returns its transformed (typically deserialized) version.
135 *   - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
136 *     GET request, otherwise if a cache instance built with
137 *     {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
138 *     caching.
139 *   - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that
140 *     should abort the request when resolved.
141 *   - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the
142 *     XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
143 *     requests with credentials} for more information.
144 *   - **`responseType`** - `{string}` - see {@link
145 *     https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
146 *   - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods -
147 *     `response` and `responseError`. Both `response` and `responseError` interceptors get called
148 *     with `http response` object. See {@link ng.$http $http interceptors}.
149 *
150 * @returns {Object} A resource "class" object with methods for the default set of resource actions
151 *   optionally extended with custom `actions`. The default set contains these actions:
152 *
153 *       { 'get':    {method:'GET'},
154 *         'save':   {method:'POST'},
155 *         'query':  {method:'GET', isArray:true},
156 *         'remove': {method:'DELETE'},
157 *         'delete': {method:'DELETE'} };
158 *
159 *   Calling these methods invoke an {@link ng.$http} with the specified http method,
160 *   destination and parameters. When the data is returned from the server then the object is an
161 *   instance of the resource class. The actions `save`, `remove` and `delete` are available on it
162 *   as  methods with the `$` prefix. This allows you to easily perform CRUD operations (create,
163 *   read, update, delete) on server-side data like this:
164 *   <pre>
165        var User = $resource('/user/:userId', {userId:'@id'});
166        var user = User.get({userId:123}, function() {
167          user.abc = true;
168          user.$save();
169        });
170     </pre>
171 *
172 *   It is important to realize that invoking a $resource object method immediately returns an
173 *   empty reference (object or array depending on `isArray`). Once the data is returned from the
174 *   server the existing reference is populated with the actual data. This is a useful trick since
175 *   usually the resource is assigned to a model which is then rendered by the view. Having an empty
176 *   object results in no rendering, once the data arrives from the server then the object is
177 *   populated with the data and the view automatically re-renders itself showing the new data. This
178 *   means that in most cases one never has to write a callback function for the action methods.
179 *
180 *   The action methods on the class object or instance object can be invoked with the following
181 *   parameters:
182 *
183 *   - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])`
184 *   - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
185 *   - non-GET instance actions:  `instance.$action([parameters], [success], [error])`
186 *
187 *   Success callback is called with (value, responseHeaders) arguments. Error callback is called
188 *   with (httpResponse) argument.
189 *
190 *   Class actions return empty instance (with additional properties below).
191 *   Instance actions return promise of the action.
192 *
193 *   The Resource instances and collection have these additional properties:
194 *
195 *   - `$promise`: the {@link ng.$q promise} of the original server interaction that created this
196 *     instance or collection.
197 *
198 *     On success, the promise is resolved with the same resource instance or collection object,
199 *     updated with data from server. This makes it easy to use in
200 *     {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view
201 *     rendering until the resource(s) are loaded.
202 *
203 *     On failure, the promise is resolved with the {@link ng.$http http response} object, without
204 *     the `resource` property.
205 *
206 *   - `$resolved`: `true` after first server interaction is completed (either with success or
207 *      rejection), `false` before that. Knowing if the Resource has been resolved 
207is useful in
208 *      data-binding.
209 *
210 * @example
211 *
212 * # Credit card resource
213 *
214 * <pre>
215     // Define CreditCard class
216     var CreditCard = $resource('/user/:userId/card/:cardId',
217      {userId:123, cardId:'@id'}, {
218       charge: {method:'POST', params:{charge:true}}
219      });
220
221     // We can retrieve a collection from the server
222     var cards = CreditCard.query(function() {
223       // GET: /user/123/card
224       // server returns: [ {id:456, number:'1234', name:'Smith'} ];
225
226       var card = cards[0];
227       // each item is an instance of CreditCard
228       expect(card instanceof CreditCard).toEqual(true);
229       card.name = "J. Smith";
230       // non GET methods are mapped onto the instances
231       card.$save();
232       // POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
233       // server returns: {id:456, number:'1234', name: 'J. Smith'};
234
235       // our custom method is mapped as well.
236       card.$charge({amount:9.99});
237       // POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
238     });
239
240     // we can create an instance as well
241     var newCard = new CreditCard({number:'0123'});
242     newCard.name = "Mike Smith";
243     newCard.$save();
244     // POST: /user/123/card {number:'0123', name:'Mike Smith'}
245     // server returns: {id:789, number:'0123', name: 'Mike Smith'};
246     expect(newCard.id).toEqual(789);
247 * </pre>
248 *
249 * The object returned from this function execution is a resource "class" which has "static" method
250 * for each action in the definition.
251 *
252 * Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and
253 * `headers`.
254 * When the data is returned from the server then the object is an instance of the resource type and
255 * all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD
256 * operations (create, read, update, delete) on server-side data.
257
258   <pre>
259     var User = $resource('/user/:userId', {userId:'@id'});
260     var user = User.get({userId:123}, function() {
261       user.abc = true;
262       user.$save();
263     });
264   </pre>
265 *
266 * It's worth noting that the success callback for `get`, `query` and other methods gets passed
267 * in the response that came from the server as well as $http header getter function, so one
268 * could rewrite the above example and get access to http headers as:
269 *
270   <pre>
271     var User = $resource('/user/:userId', {userId:'@id'});
272     User.get({userId:123}, function(u, getResponseHeaders){
273       u.abc = true;
274       u.$save(function(u, putResponseHeaders) {
275         //u => saved user object
276         //putResponseHeaders => $http header getter
277       });
278     });
279   </pre>
280
281 * # Creating a custom 'PUT' request
282 * In this example we create a custom method on our resource to make a PUT request
283 * <pre>
284 *		var app = angular.module('app', ['ngResource', 'ngRoute']);
285 *
286 *		// Some APIs expect a PUT request in the format URL/object/ID
287 *		// Here we are creating an 'update' method 
288 *		app.factory('Notes', ['$resource', function($resource) {
289 *    return $resource('/notes/:id', null,
290 *        {
291 *            'update': { method:'PUT' }
292 *        });
293 *		}]);
294 *
295 *		// In our controller we get the ID from the URL using ngRoute and $routeParams
296 *		// We pass in $routeParams and our Notes factory along with $scope
297 *		app.controller('NotesCtrl', ['$scope', '$routeParams', 'Notes',
298                                      function($scope, $routeParams, Notes) {
299 *    // First get a note object from the factory
300 *    var note = Notes.get({ id:$routeParams.id });
301 *    $id = note.id;
302 *
303 *    // Now call update passing in the ID first then the object you are updating
304 *    Notes.update({ id:$id }, note);
305 *
306 *    // This will PUT /notes/ID with the note object in the request payload
307 *		}]);
308 * </pre>
309 */
310angular.module('ngResource', ['ng']).
311  factory('$resource', ['$http', '$q', function($http, $q) {
312
313    var DEFAULT_ACTIONS = {
314      'get':    {method:'GET'},
315      'save':   {method:'POST'},
316      'query':  {method:'GET', isArray:true},
317      'remove': {method:'DELETE'},
318      'delete': {method:'DELETE'}
319    };
320    var noop = angular.noop,
321        forEach = angular.forEach,
322        extend = angular.extend,
323        copy = angular.copy,
324        isFunction = angular.isFunction;
325
326    /**
327     * We need our custom method because encodeURIComponent is too aggressive and doesn't follow
328     * http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
329     * segments:
330     *    segment       = *pchar
331     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
332     *    pct-encoded   = "%" HEXDIG HEXDIG
333     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
334     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
335     *                     / "*" / "+" / "," / ";" / "="
336     */
337    function encodeUriSegment(val) {
338      return encodeUriQuery(val, true).
339        replace(/%26/gi, '&').
340        replace(/%3D/gi, '=').
341        replace(/%2B/gi, '+');
342    }
343
344
345    /**
346     * This method is intended for encoding *key* or *value* parts of query component. We need a
347     * custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't
348     * have to be encoded per http://tools.ietf.org/html/rfc3986:
349     *    query       = *( pchar / "/" / "?" )
350     *    pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
351     *    unreserved    = ALPHA / DIGIT / "-" / "." / "_" / "~"
352     *    pct-encoded   = "%" HEXDIG HEXDIG
353     *    sub-delims    = "!" / "$" / "&" / "'" / "(" / ")"
354     *                     / "*" / "+" / "," / ";" / "="
355     */
356    function encodeUriQuery(val, pctEncodeSpaces) {
357      return encodeURIComponent(val).
358        replace(/%40/gi, '@').
359        replace(/%3A/gi, ':').
360        replace(/%24/g, '$').
361        replace(/%2C/gi, ',').
362        replace(/%20/g, (pctEncodeSpaces ? '%20' : '+'));
363    }
364
365    function Route(template, defaults) {
366      this.template = template;
367      this.defaults = defaults || {};
368      this.urlParams = {};
369    }
370
371    Route.prototype = {
372      setUrlParams: function(config, params, actionUrl) {
373        var self = this,
374            url = actionUrl || self.template,
375            val,
376            encodedVal;
377
378        var urlParams = self.urlParams = {};
379        forEach(url.split(/\W/), function(param){
380          if (param === 'hasOwnProperty') {
381            throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name.");
382          }
383          if (!(new RegExp("^\\d+$").test(param)) && param &&
384               (new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) {
385            urlParams[param] = true;
386          }
387        });
388        url = url.replace(/\\:/g, ':');
389
390        params = params || {};
391        forEach(self.urlParams, function(_, urlParam){
392          val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam];
393          if (angular.isDefined(val) && val !== null) {
394            encodedVal = encodeUriSegment(val);
395            url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1");
396          } else {
397            url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match,
398                leadingSlashes, tail) {
399              if (tail.charAt(0) == '/') {
400                return tail;
401              } else {
402                return leadingSlashes + tail;
403              }
404            });
405          }
406        });
407
408        // strip trailing slashes and set the url
409        url = url.replace(/\/+$/, '');
410        // then replace collapse `/.` if found in the last URL path segment before the query
411        // E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
412        url = url.replace(/\/\.(?=\w+($|\?))/, '.');
413        // replace escaped `/\.` with `/.`
414        config.url = url.replace(/\/\\\./, '/.');
415
416
417        // set params - delegate param encoding to $http
418        forEach(params, function(value, key){
419          if (!self.urlParams[key]) {
420            config.params = config.params || {};
421            config.params[key] = value;
422          }
423        });
424      }
425    };
426
427
428    function resourceFactory(url, paramDefaults, actions) {
429      var route = new Route(url);
430
431      actions = extend({}, DEFAULT_ACTIONS, actions);
432
433      function extractParams(data, actionParams){
434        var ids = {};
435        actionParams = extend({}, paramDefaults, actionParams);
436        forEach(actionParams, function(value, key){
437          if (isFunction(value)) { value = value(); }
438          ids[key] = value && value.charAt && value.charAt(0) == '@' ?
439            lookupDottedPath(data, value.substr(1)) : value;
440        });
441        return ids;
442      }
443
444      function defaultResponseInterceptor(response) {
445        return response.resource;
446      }
447
448      function Resource(value){
449        shallowClearAndCopy(value || {}, this);
450      }
451
452      forEach(actions, function(action, name) {
453        var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method);
454
455        Resource[name] = function(a1, a2, a3, a4) {
456          var params = {}, data, success, error;
457
458          /* jshint -W086 */ /* (purposefully fall through case statements) */
459          switch(arguments.length) {
460          case 4:
461            error = a4;
462            success = a3;
463            //fallthrough
464          case 3:
465          case 2:
466            if (isFunction(a2)) {
467              if (isFunction(a1)) {
468                success = a1;
469                error = a2;
470                break;
471              }
472
473              success = a2;
474              error = a3;
475              //fallthrough
476            } else {
477              params = a1;
478              data = a2;
479              success = a3;
480              break;
481            }
482          case 1:
483            if (isFunction(a1)) success = a1;
484            else if (hasBody) data = a1;
485            else params = a1;
486            break;
487          case 0: break;
488          default:
489            throw $resourceMinErr('badargs',
490              "Expected up to 4 arguments [params, data, success, error], got {0} arguments",
491              arguments.length);
492          }
493          /* jshint +W086 */ /* (purposefully fall through case statements) */
494
495          var isInstanceCall = this instanceof Resource;
496          var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data));
497          var httpConfig = {};
498          var responseInterceptor = action.interceptor && action.interceptor.response ||
499                                    defaultResponseInterceptor;
500          var responseErrorInterceptor = action.interceptor && action.interceptor.responseError ||
501                                    undefined;
502
503          forEach(action, function(value, key) {
504            if (key != 'params' && key != 'isArray' && key != 'interceptor') {
505              httpConfig[key] = copy(value);
506            }
507          });
508
509          if (hasBody) httpConfig.data = data;
510          route.setUrlParams(httpConfig,
511                             extend({}, extractParams(data, action.params || {}), params),
512                             action.url);
513
514          var promise = $http(httpConfig).then(function(response) {
515            var data = response.data,
516                promise = value.$promise;
517
518            if (data) {
519              // Need to convert action.isArray to boolean in case it is undefined
520              // jshint -W018
521              if (angular.isArray(data) !== (!!action.isArray)) {
522                throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' +
523                  'response to contain an {0} but got an {1}',
524                  action.isArray?'array':'object', angular.isArray(data)?'array':'object');
525              }
526              // jshint +W018
527              if (action.isArray) {
528                value.length = 0;
529                forEach(data, function(item) {
530                  value.push(new Resource(item));
531                });
532              } else {
533                shallowClearAndCopy(data, value);
534                value.$promise = promise;
535              }
536            }
537
538            value.$resolved = true;
539
540            response.resource = value;
541
542            return response;
543          }, function(response) {
544            value.$resolved = true;
545
546            (error||noop)(response);
547
548            return $q.reject(response);
549          });
550
551          promise = promise.then(
552              function(response) {
553                var value = responseInterceptor(response);
554                (success||noop)(value, response.headers);
555                return value;
556              },
557              responseErrorInterceptor);
558
559          if (!isInstanceCall) {
560            // we are creating instance / collection
561            // - set the initial promise
562            // - return the instance / collection
563            value.$promise = promise;
564            value.$resolved = false;
565
566            return value;
567          }
568
569          // instance call
570          return promise;
571        };
572
573
574        Resource.prototype['$' + name] = function(params, success, error) {
575          if (isFunction(params)) {
576            error = success; success = params; params = {};
577          }
578          var result = Resource[name].call(this, params, this, success, error);
579          return result.$promise || result;
580        };
581      });
582
583      Resource.bind = function(additionalParamDefaults){
584        return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions);
585      };
586
587      return Resource;
588    }
589
590    return resourceFactory;
591  }]);
592
593
594})(window, window.angular);
595
596/**
597 * @license AngularJS v1.2.6
598 * (c) 2010-2014 Google, Inc. http://angularjs.org
599 * License: MIT
600 */
601(function(window, angular, undefined) {'use strict';
602
603var $sanitizeMinErr = angular.$$minErr('$sanitize');
604
605/**
606 * @ngdoc overview
607 * @name ngSanitize
608 * @description
609 *
610 * # ngSanitize
611 *
612 * The `ngSanitize` module provides functionality to sanitize HTML.
613 *
614 * {@installModule sanitize}
615 *
616 * <div doc-module-components="ngSanitize"></div>
617 *
618 * See {@link ngSanitize.$sanitize `$sanitize`} for usage.
619 */
620
621/*
622 * HTML Parser By Misko Hevery ([email protected])
623 * based on:  HTML Parser By John Resig (ejohn.org)
624 * Original code by Erik Arvidsson, Mozilla Public License
625 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
626 *
627 * // Use like so:
628 * htmlParser(htmlString, {
629 *     start: function(tag, attrs, unary) {},
630 *     end: function(tag) {},
631 *     chars: function(text) {},
632 *     comment: function(text) {}
633 * });
634 *
635 */
636
637
638/**
639 * @ngdoc service
640 * @name ngSanitize.$sanitize
641 * @function
642 *
643 * @description
644 *   The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
645 *   then serialized back to properly escaped html string. This means that no unsafe input can make
646 *   it into the returned string, however, since our parser is more strict than a typical browser
647 *   parser, it's possible that some obscure input, which would be recognized as valid HTML by a
648 *   browser, won't make it through the sanitizer.
649 *   The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
650 *   `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
651 *
652 * @param {string} html Html input.
653 * @returns {string} Sanitized html.
654 *
655 * @example
656   <doc:example module="ngSanitize">
657   <doc:source>
658     <script>
659       function Ctrl($scope, $sce) {
660         $scope.snippet =
661           '<p style="color:blue">an html\n' +
662           '<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
663           'snippet</p>';
664         $scope.deliberatelyTrustDangerousSnippet = function() {
665           return $sce.trustAsHtml($scope.snippet);
666         };
667       }
668     </script>
669     <div ng-controller="Ctrl">
670        Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
671       <table>
672         <tr>
673           <td>Directive</td>
674           <td>How</td>
675           <td>Source</td>
676           <td>Rendered</td>
677         </tr>
678         <tr id="bind-html-with-sanitize">
679           <td>ng-bind-html</td>
680           <td>Automatically uses $sanitize</td>
681           <td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
682           <td><div ng-bind-html="snippet"></div></td>
683         </tr>
684         <tr id="bind-html-with-trust">
685           <td>ng-bind-html</td>
686           <td>Bypass $sanitize by explicitly trusting the dangerous value</td>
687           <td>
688           <pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
689&lt;/div&gt;</pre>
690           </td>
691           <td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
692         </tr>
693         <tr id="bind-default">
694           <td>ng-bind</td>
695           <td>Automatically escapes</td>
696           <td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
697           <td><div ng-bind="snippet"></div></td>
698         </tr>
699       </table>
700       </div>
701   </doc:source>
702   <doc:scenario>
703     it('should sanitize the html snippet by default', function() {
704       expect(using('#bind-html-with-sanitize').element('div').html()).
705         toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
706     });
707
708     it('should inline raw snippet if bound to a trusted value', function() {
709       expect(using('#bind-html-with-trust').element("div").html()).
710         toBe("<p style=\"color:blue\">an html\n" +
711              "<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
712              "snippet</p>");
713     });
714
715     it('should escape snippet without any filter', function() {
716       expect(using('#bind-default').element('div').html()).
717         toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
718              "&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
719              "snippet&lt;/p&gt;");
720     });
721
722     it('should update', function() {
723       input('snippet').enter('new <b onclick="alert(1)">text</b>');
724       expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>');
725       expect(using('#bind-html-with-trust').element('div').html()).toBe(
726         'new <b onclick="alert(1)">text</b>');
727       expect(using('#bind-default').element('div').html()).toBe(
728         "new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
729     });
730   </doc:scenario>
731   </doc:example>
732 */
733function $SanitizeProvider() {
734  this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
735    return function(html) {
736      var buf = [];
737      htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
738        return !/^unsafe/.test($$sanitizeUri(uri, isImage));
739      }));
740      return buf.join('');
741    };
742  }];
743}
744
745function sanitizeText(chars) {
746  var buf = [];
747  var writer = htmlSanitizeWriter(buf, angular.noop);
748  writer.chars(chars);
749  return buf.join('');
750}
751
752
753// Regular Expressions for parsing tags and attributes
754var START_TAG_REGEXP =
755       /^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,
756  END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/,
757  ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
758  BEGIN_TAG_REGEXP = /^</,
759  BEGING_END_TAGE_REGEXP = /^<\s*\//,
760  COMMENT_REGEXP = /<!--(.*?)-->/g,
761  DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
762  CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
763  // Match everything outside of normal chars and " (quote character)
764  NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
765
766
767// Good source of info about elements and attributes
768// http://dev.w3.org/html5/spec/Overview.html#semantics
769// http://simon.html5.org/html-elements
770
771// Safe Void Elements - HTML5
772// http://dev.w3.org/html5/spec/Overview.html#void-elements
773var voidElements = makeMap("area,br,col,hr,img,wbr");
774
775// Elements that you can, intentionally, leave open (and which close themselves)
776// http://dev.w3.org/html5/spec/Overview.html#optional-tags
777var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
778    optionalEndTagInlineElements = makeMap("rp,rt"),
779    optionalEndTagElements = angular.extend({},
780                                            optionalEndTagInlineElements,
781                                            optionalEndTagBlockElements);
782
783// Safe Block Elements - HTML5
784var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
785        "aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
786        "h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
787
788// Inline Elements - HTML5
789var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
790        "bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
791        "samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
792
793
794// Special Elements (can contain anything)
795var specialElements = makeMap("script,style");
796
797var validElements = angular.extend({},
798                                   voidElements,
799                                   blockElements,
800                                   inlineElements,
801                                   optionalEndTagElements);
802
803//Attributes that have href and hence need to be sanitized
804var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
805var validAttrs = angular.extend({}, uriAttrs, makeMap(
806    'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
vendor: 4,733 bytes, lines 807-972
807    'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
808    'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
809    'scope,scrolling,shape,span,start,summary,target,title,type,'+
810    'valign,value,vspace,width'));
811
812function makeMap(str) {
813  var obj = {}, items = str.split(','), i;
814  for (i = 0; i < items.length; i++) obj[items[i]] = true;
815  return obj;
816}
817
818
819/**
820 * @example
821 * htmlParser(htmlString, {
822 *     start: function(tag, attrs, unary) {},
823 *     end: function(tag) {},
824 *     chars: function(text) {},
825 *     comment: function(text) {}
826 * });
827 *
828 * @param {string} html string
829 * @param {object} handler
830 */
831function htmlParser( html, handler ) {
832  var index, chars, match, stack = [], last = html;
833  stack.last = function() { return stack[ stack.length - 1 ]; };
834
835  while ( html ) {
836    chars = true;
837
838    // Make sure we're not in a script or style element
839    if ( !stack.last() || !specialElements[ stack.last() ] ) {
840
841      // Comment
842      if ( html.indexOf("<!--") === 0 ) {
843        // comments containing -- are not allowed unless they terminate the comment
844        index = html.indexOf("--", 4);
845
846        if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
847          if (handler.comment) handler.comment( html.substring( 4, index ) );
848          html = html.substring( index + 3 );
849          chars = false;
850        }
851      // DOCTYPE
852      } else if ( DOCTYPE_REGEXP.test(html) ) {
853        match = html.match( DOCTYPE_REGEXP );
854
855        if ( match ) {
856          html = html.replace( match[0] , '');
857          chars = false;
858        }
859      // end tag
860      } else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
861        match = html.match( END_TAG_REGEXP );
862
863        if ( match ) {
864          html = html.substring( match[0].length );
865          match[0].replace( END_TAG_REGEXP, parseEndTag );
866          chars = false;
867        }
868
869      // start tag
870      } else if ( BEGIN_TAG_REGEXP.test(html) ) {
871        match = html.match( START_TAG_REGEXP );
872
873        if ( match ) {
874          html = html.substring( match[0].length );
875          match[0].replace( START_TAG_REGEXP, parseStartTag );
876          chars = false;
877        }
878      }
879
880      if ( chars ) {
881        index = html.indexOf("<");
882
883        var text = index < 0 ? html : html.substring( 0, index );
884        html = index < 0 ? "" : html.substring( index );
885
886        if (handler.chars) handler.chars( decodeEntities(text) );
887      }
888
889    } else {
890      html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
891        function(all, text){
892          text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
893
894          if (handler.chars) handler.chars( decodeEntities(text) );
895
896          return "";
897      });
898
899      parseEndTag( "", stack.last() );
900    }
901
902    if ( html == last ) {
903      throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
904                                        "of html: {0}", html);
905    }
906    last = html;
907  }
908
909  // Clean up any remaining tags
910  parseEndTag();
911
912  function parseStartTag( tag, tagName, rest, unary ) {
913    tagName = angular.lowercase(tagName);
914    if ( blockElements[ tagName ] ) {
915      while ( stack.last() && inlineElements[ stack.last() ] ) {
916        parseEndTag( "", stack.last() );
917      }
918    }
919
920    if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
921      parseEndTag( "", tagName );
922    }
923
924    unary = voidElements[ tagName ] || !!unary;
925
926    if ( !unary )
927      stack.push( tagName );
928
929    var attrs = {};
930
931    rest.replace(ATTR_REGEXP,
932      function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
933        var value = doubleQuotedValue
934          || singleQuotedValue
935          || unquotedValue
936          || '';
937
938        attrs[name] = decodeEntities(value);
939    });
940    if (handler.start) handler.start( tagName, attrs, unary );
941  }
942
943  function parseEndTag( tag, tagName ) {
944    var pos = 0, i;
945    tagName = angular.lowercase(tagName);
946    if ( tagName )
947      // Find the closest opened tag of the same type
948      for ( pos = stack.length - 1; pos >= 0; pos-- )
949        if ( stack[ pos ] == tagName )
950          break;
951
952    if ( pos >= 0 ) {
953      // Close all the open elements, up the stack
954      for ( i = stack.length - 1; i >= pos; i-- )
955        if (handler.end) handler.end( stack[ i ] );
956
957      // Remove the open elements from the stack
958      stack.length = pos;
959    }
960  }
961}
962
963var hiddenPre=document.createElement("pre");
964var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
965/**
966 * decodes all entities into regular string
967 * @param value
968 * @returns {string} A string with decoded entities.
969 */
970function decodeEntities(value) {
971  if (!value) { return ''; }
972
973  // Note: IE8 does not preserve spaces at the start/end of innerHTML
974  // so we must capture them and reattach them afterward
975  var parts = spaceRe.exec(value);
976  var spaceBefore = parts[1];
977  var spaceAfter = parts[3];
978  var content = parts[2];
979  if (content) {
980    hiddenPre.innerHTML=content.replace(/</g,"&lt;");
981    // innerText depends on styling as it doesn't display hidden elements.
982    // Therefore, it's better to use textContent not to cause unnecessary
983    // reflows. However, IE<9 don't support textContent so the innerText
984    // fallback is necessary.
985    content = 'textContent' in hiddenPre ?
986      hiddenPre.textContent : hiddenPre.innerText;
987  }
988  return spaceBefore + content + spaceAfter;
989}
990
991/**
992 * Escapes all potentially dangerous characters, so that the
993 * resulting string can be safely inserted into attribute or
994 * element text.
995 * @param value
996 * @returns escaped text
997 */
998function encodeEntities(value) {
999  return value.
1000    replace(/&/g, '&amp;').
1001    replace(NON_ALPHANUMERIC_REGEXP, function(value){
1002      return '&#' + value.charCodeAt(0) + ';';
1003    }).
1004    replace(/</g, '&lt;').
1005    replace(/>/g, '&gt;');
1006}
1007
1008/**
1009 * create an HTML/XML writer which writes to buffer
1010 * @param {Array} buf use buf.jain('') to get out sanitized html string
1011 * @returns {object} in the form of {
1012 *     start: function(tag, attrs, unary) {},
1013 *     end: function(tag) {},
1014 *     chars: function(text) {},
1015 *     comment: function(text) {}
1016 * }
1017 */
1018function htmlSanitizeWriter(buf, uriValidator){
1019  var ignore = false;
1020  var out = angular.bind(buf, buf.push);
1021  return {
1022    start: function(tag, attrs, unary){
1023      tag = angular.lowercase(tag);
1024      if (!ignore && specialElements[tag]) {
1025        ignore = tag;
1026      }
vendor: 3,181 bytes, lines 1027-1133
1027      if (!ignore && validElements[tag] === true) {
1028        out('<');
1029        out(tag);
1030        angular.forEach(attrs, function(value, key){
1031          var lkey=angular.lowercase(key);
1032          var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
1033          if (validAttrs[lkey] === true &&
1034            (uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
1035            out(' ');
1036            out(key);
1037            out('="');
1038            out(encodeEntities(value));
1039            out('"');
1040          }
1041        });
1042        out(unary ? '/>' : '>');
1043      }
1044    },
1045    end: function(tag){
1046        tag = angular.lowercase(tag);
1047        if (!ignore && validElements[tag] === true) {
1048          out('</');
1049          out(tag);
1050          out('>');
1051        }
1052        if (tag == ignore) {
1053          ignore = false;
1054        }
1055      },
1056    chars: function(chars){
1057        if (!ignore) {
1058          out(encodeEntities(chars));
1059        }
1060      }
1061  };
1062}
1063
1064
1065// define ngSanitize module and register $sanitize service
1066angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
1067
1068/* global sanitizeText: false */
1069
1070/**
1071 * @ngdoc filter
1072 * @name ngSanitize.filter:linky
1073 * @function
1074 *
1075 * @description
1076 * Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
1077 * plain email address links.
1078 *
1079 * Requires the {@link ngSanitize `ngSanitize`} module to be installed.
1080 *
1081 * @param {string} text Input text.
1082 * @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
1083 * @returns {string} Html-linkified text.
1084 *
1085 * @usage
1086   <span ng-bind-html="linky_expression | linky"></span>
1087 *
1088 * @example
1089   <doc:example module="ngSanitize">
1090     <doc:source>
1091       <script>
1092         function Ctrl($scope) {
1093           $scope.snippet =
1094             'Pretty text with some links:\n'+
1095             'http://angularjs.org/,\n'+
1096             'mailto:[email protected],\n'+
1097             '[email protected],\n'+
1098             'and one more: ftp://127.0.0.1/.';
1099           $scope.snippetWithTarget = 'http://angularjs.org/';
1100         }
1101       </script>
1102       <div ng-controller="Ctrl">
1103       Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
1104       <table>
1105         <tr>
1106           <td>Filter</td>
1107           <td>Source</td>
1108           <td>Rendered</td>
1109         </tr>
1110         <tr id="linky-filter">
1111           <td>linky filter</td>
1112           <td>
1113             <pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
1114           </td>
1115           <td>
1116             <div ng-bind-html="snippet | linky"></div>
1117           </td>
1118         </tr>
1119         <tr id="linky-target">
1120          <td>linky target</td>
1121          <td>
1122            <pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
1123          </td>
1124          <td>
1125            <div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
1126          </td>
1127         </tr>
1128         <tr id="escaped-html">
1129           <td>no filter</td>
1130           <td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
1131           <td><div ng-bind="snippet"></div></td>
1132         </tr>
1133       </table>
1134     </doc:source>
1135     <doc:scenario>
1136       it('should linkify the snippet with urls', function() {
1137         expect(using('#linky-filter').binding('snippet | linky')).
1138           toBe('Pretty text with some links:&#10;' +
1139                '<a href="http://angularjs.org/">http://angularjs.org/</a>,&#10;' +
1140                '<a href="mailto:[email protected]">[email protected]</a>,&#10;' +
1141                '<a href="mailto:[email protected]">[email protected]</a>,&#10;' +
1142                'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.');
1143       });
1144
1145       it ('should not linkify snippet without the linky filter', function() {
1146         expect(using('#escaped-html').binding('snippet')).
1147           toBe("Pretty text with some links:\n" +
1148                "http://angularjs.org/,\n" +
1149                "mailto:[email protected],\n" +
1150                "[email protected],\n" +
1151                "and one more: ftp://127.0.0.1/.");
1152       });
1153
1154       it('should update', function() {
1155         input('snippet').enter('new http://link.');
1156         expect(using('#linky-filter').binding('snippet | linky')).
1157           toBe('new <a href="http://link">http://link</a>.');
1158         expect(using('#escaped-html').binding('snippet')).toBe('new http://link.');
1159       });
1160
1161       it('should work with the target property', function() {
1162        expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")).
1163          toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>');
1164       });
1165     </doc:scenario>
1166   </doc:example>
1167 */
1168angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
1169  var LINKY_URL_REGEXP =
1170        /((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,
1171      MAILTO_REGEXP = /^mailto:/;
1172
1173  return function(text, target) {
1174    if (!text) return text;
1175    var match;
1176    var raw = text;
1177    var html = [];
1178    var url;
1179    var i;
1180    while ((match = raw.match(LINKY_URL_REGEXP))) {
1181      // We can not end in these as they are sometimes found at the end of the sentence
1182      url = match[0];
1183      // if we did not match ftp/http/mailto then assume mailto
1184      if (match[2] == match[3]) url = 'mailto:' + url;
1185      i = match.index;
1186      addText(raw.substr(0, i));
1187      addLink(url, match[0].replace(MAILTO_REGEXP, ''));
1188      raw = raw.substring(i + match[0].length);
1189    }
1190    addText(raw);
1191    return $sanitize(html.join(''));
1192
1193    function addText(text) {
1194      if (!text) {
1195        return;
1196      }
1197      html.push(sanitizeText(text));
1198    }
1199
1200    function addLink(url, text) {
1201      html.push('<a ');
1202      if (angular.isDefined(target)) {
1203        html.push('target="');
1204        html.push(target);
1205        html.push('" ');
1206      }
1207      html.push('href="');
1208      html.push(url);
1209      html.push('">');
1210      addText(text);
1211      html.push('</a>');
1212    }
1213  };
1214}]);
1215
1216
1217})(window, window.angular);
1218
1219/**
1220 * @license AngularJS v1.2.6
1221 * (c) 2010-2014 Google, Inc. http://angularjs.org
1222 * License: MIT
1223 */
1224(function(window, angular, undefined) {'use strict';
1225
1226/**
1227 * @ngdoc overview
1228 * @name ngRoute
1229 * @description
1230 *
1231 * # ngRoute
1232 *
1233 * The `ngRoute` module provides routing and deeplinking services and directives for angular apps.
1234 *
1235 * ## Example
1236 * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
1237 * 
1238 * {@installModule route}
1239 *
1240 * <div doc-module-components="ngRoute"></div>
1241 */
1242 /* global -ngRouteModule */
1243var ngRouteModule = angular.module('ngRoute', ['ng']).
1244                        provider('$route', $RouteProvider);
1245
1246/**
1247 * @ngdoc object
1248 * @name ngRoute.$routeProvider
1249 * @function
1250 *
1251 * @description
1252 *
1253 * Used for configuring routes.
1254 * 
1255 * ## Example
1256 * See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`.
1257 *
1258 * ## Dependencies
1259 * Requires the {@link ngRoute `ngRoute`} module to be installed.
1260 */
1261function $RouteProvider(){
1262  function inherit(parent, extra) {
1263    return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra);
1264  }
1265
1266  var routes = {};
1267
1268  /**
1269   * @ngdoc method
1270   * @name ngRoute.$routeProvider#when
1271   * @methodOf ngRoute.$routeProvider
1272   *
1273   * @param {string} path Route path (matched against `$location.path`). If `$location.path`
1274   *    contains redundant trailing slash or is missing one, the route will still match and the
1275   *    `$location.path` will be updated to add or drop the trailing slash to exactly match the
1276   *    route definition.
1277   *
1278   *      * `path` can contain named groups starting with a colon: e.g. `:name`. All characters up
1279   *        to the next slash are matched and stored in `$routeParams` under the given `name`
1280   *        when the route matches.
1281   *      * `path` can contain named groups starting with a colon and ending with a star:
1282   *        e.g.`:name*`. All characters are eagerly stored in `$routeParams` under the given `name`
1283   *        when the route matches.
1284   *      * `path` can contain optional named groups with a question mark: e.g.`:name?`.
1285   *
1286   *    For example, routes like `/color/:color/largecode/:largecode*\/edit` will match
1287   *    `/color/brown/largecode/code/with/slashs/edit` and extract:
1288   *
1289   *      * `color: brown`
1290   *      * `largecode: code/with/slashs`.
1291   *
1292   *
1293   * @param {Object} route Mapping information to be assigned to `$route.current` on route
1294   *    match.
1295   *
1296   *    Object properties:
1297   *
1298   *    - `controller` – `{(string|function()=}` – Controller fn that should be associated with
1299   *      newly created scope or the name of a {@link angular.Module#controller registered
1300   *      controller} if passed as a string.
1301   *    - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be
1302   *      published to scope under the `controllerAs` name.
1303   *    - `template` – `{string=|function()=}` – html template as a string or a function that
1304   *      returns an html template as a string which should be used by {@link
1305   *      ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives.
1306   *      This property takes precedence over `templateUrl`.
1307   *
1308   *      If `template` is a function, it will be called with the following parameters:
1309   *
1310   *      - `{Array.<Object>}` - route parameters extracted from the current
1311   *        `$location.path()` by applying the current route
1312   *
1313   *    - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html
1314   *      template that should be used by {@link ngRoute.directive:ngView ngView}.
1315   *
1316   *      If `templateUrl` is a function, it will be called with the following parameters:
1317   *
1318   *      - `{Array.<Object>}` - route parameters extracted from the current
1319   *        `$location.path()` by applying the current route
1320   *
1321   *    - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should
1322   *      be injected into the controller. If any of these dependencies are promises, the router
1323   *      will wait for them all to be resolved or one to be rejected before the controller is
1324   *      instantiated.
1325   *      If all the promises are resolved successfully, the values of the resolved promises are
1326   *      injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is
1327   *      fired. If any of the promises are rejected the
1328   *      {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object
1329   *      is:
1330   *
1331   *      - `key` – `{string}`: a name of a dependency to be injected into the controller.
1332   *      - `factory` - `{string|function}`: If `string` then it is an alias for a service.
1333   *        Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected}
1334   *        and the return value is treated as the dependency. If the result is a promise, it is
1335   *        resolved before its value is injected into the controller. Be aware that
1336   *        `ngRoute.$routeParams` will still refer to the previous route within these resolve
1337   *        functions.  Use `$route.current.params` to access the new route parameters, instead.
1338   *
1339   *    - `redirectTo` – {(string|function())=} – value to update
1340   *      {@link ng.$location $location} path with and trigger route redirection.
1341   *
1342   *      If `redirectTo` is a function, it will be called with the following parameters:
1343   *
1344   *      - `{Object.<string>}` - route parameters extracted from the current
1345   *        `$location.path()` by applying the current route templateUrl.
1346   *      - `{string}` - current `$location.path()`
1347   *      - `{Object}` - current `$location.search()`
1348   *
1349   *      The custom `redirectTo` function is expected to return a string which will be used
1350   *      to update `$location.path()` and `$location.search()`.
1351   *
1352   *    - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()`
1353   *      or `$location.hash()` changes.
1354   *
1355   *      If the option is set to `false` and url in the browser changes, then
1356   *      `$routeUpdate` event is broadcasted on the root scope.
1357   *
1358   *    - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive
1359   *
1360   *      If the option is set to `true`, then the particular route can be matched without being
1361   *      case sensitive
1362   *
1363   * @returns {Object} self
1364   *
1365   * @description
1366   * Adds a new route definition to the `$route` service.
1367   */
1368  this.when = function(path, route) {
1369    routes[path] = angular.extend(
1370      {reloadOnSearch: true},
1371      route,
1372      path && pathRegExp(path, route)
1373    );
1374
1375    // create redirection for trailing slashes
1376    if (path) {
1377      var redirectPath = (path[path.length-1] == '/')
1378            ? path.substr(0, path.length-1)
1379            : path +'/';
1380
1381      routes[redirectPath] = angular.extend(
1382        {redirectTo: path},
1383        pathRegExp(redirectPath, route)
1384      );
1385    }
1386
1387    return this;
1388  };
1389
1390   /**
1391    * @param path {string} path
1392    * @param opts {Object} options
1393    * @return {?Object}
1394    *
1395    * @description
1396    * Normalizes the given path, returning a regular expression
1397    * and the original path.
1398    *
1399    * Inspired by pathRexp in visionmedia/express/lib/utils.js.
1400    */
1401  function pathRegExp(path, opts) {
1402    var insensitive = opts.caseInsensitiveMatch,
1403        ret = {
1404          originalPath: path,
1405          regexp: path
1406        },
1407        keys = ret.keys = [];
1408
1409    path = path
1410      .replace(/([().])/g, '\\$1')
1411      .replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){
1412        var optional = option === '?' ? option : null;
1413        var star = option === '*' ? option : null;
1414        keys.push({ name: key, optional: !!optional });
1415        slash = slash || '';
1416        return ''
1417          + (optional ? '' : slash)
1418          + '(?:'
1419          + (optional ? slash : '')
1420          + (star && '(.+?)' || '([^/]+)')
1421          + (optional || '')
1422          + ')'
1423          + (optional || '');
1424      })
1425      .replace(/([\/$\*])/g, '\\$1');
1426
1427    ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : '');
1428    return ret;
1429  }
1430
1431  /**
1432   * @ngdoc method
1433   * @name ngRoute.$routeProvider#otherwise
1434   * @methodOf ngRoute.$routeProvider
1435   *
1436   * @description
1437   * Sets route definition that will be used on route change when no other route definition
1438   * is matched.
1439   *
1440   * @param {Object} params Mapping information to be assigned to `$route.current`.
1441   * @returns {Object} self
1442   */
1443  this.otherwise = function(params) {
1444    this.when(null, params);
1445    return this;
1446  };
1447
1448
1449  this.$get = ['$rootScope',
1450               '$location',
1451               '$routeParams',
1452               '$q',
1453               '$injector',
1454               '$http',
1455               '$templateCache',
1456               '$sce',
1457      function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) {
1458
1459    /**
1460     * @ngdoc object
1461     * @name ngRoute.$route
1462     * @requires $location
1463     * @requires $routeParams
1464     *
1465     * @property {Object} current Reference to the current route definition.
1466     * The route definition contains:
1467     *
1468     *   - `controller`: The controller constructor as define in route definition.
1469     *   - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for
1470     *     controller instantiation. The `locals` contain
1471     *     the resolved values of the `resolve` map. Additionally the `locals` also contain:
1472     *
1473     *     - `$scope` - The current route scope.
1474     *     - `$template` - The current route template HTML.
1475     *
1476     * @property {Array.<Object>} routes Array of all configured routes.
1477     *
1478     * @description
1479     * `$route` is used for deep-linking URLs to controllers and views (HTML partials).
1480     * It watches `$location.url()` and tries to map the path to an existing route definition.
1481     *
1482     * Requires the {@link ngRoute `ngRoute`} module to be installed.
1483     *
1484     * You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API.
1485     *
1486     * The `$route` service is typically used in conjunction with the
1487     * {@link ngRoute.directive:ngView `ngView`} directive and the
1488     * {@link ngRoute.$routeParams `$routeParams`} service.
1489     *
1490     * @example
1491       This example shows how changing the URL hash causes the `$route` to match a route against the
1492       URL, and the `ngView` pulls in the partial.
1493
1494       Note that this example is using {@link ng.directive:script inlined templates}
1495       to get it working on jsfiddle as well.
1496
1497     <example module="ngViewExample" deps="angular-route.js">
1498       <file name="index.html">
1499         <div ng-controller="MainCntl">
1500           Choose:
1501           <a href="Book/Moby">Moby</a> |
1502           <a href="Book/Moby/ch/1">Moby: Ch1</a> |
1503           <a href="Book/Gatsby">Gatsby</a> |
1504           <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
1505           <a href="Book/Scarlet">Scarlet Letter</a><br/>
1506
1507           <div ng-view></div>
1508           <hr />
1509
1510           <pre>$location.path() = {{$location.path()}}</pre>
1511           <pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre>
1512           <pre>$route.current.params = {{$route.current.params}}</pre>
1513           <pre>$route.current.scope.name = {{$route.current.scope.name}}</pre>
1514           <pre>$routeParams = {{$routeParams}}</pre>
1515         </div>
1516       </file>
1517
1518       <file name="book.html">
1519         controller: {{name}}<br />
1520         Book Id: {{params.bookId}}<br />
1521       </file>
1522
1523       <file name="chapter.html">
1524         controller: {{name}}<br />
1525         Book Id: {{params.bookId}}<br />
1526         Chapter Id: {{params.chapterId}}
1527       </file>
1528
1529       <file name="script.js">
1530         angular.module('ngViewExample', ['ngRoute'])
1531
1532         .config(function($routeProvider, $locationProvider) {
1533           $routeProvider.when('/Book/:bookId', {
1534             templateUrl: 'book.html',
1535             controller: BookCntl,
1536             resolve: {
1537               // I will cause a 1 second delay
1538               delay: function($q, $timeout) {
1539                 var delay = $q.defer();
1540                 $timeout(delay.resolve, 1000);
1541                 return delay.promise;
1542               }
1543             }
1544           });
1545           $routeProvider.when('/Book/:bookId/ch/:chapterId', {
1546             templateUrl: 'chapter.html',
1547             controller: ChapterCntl
1548           });
1549
1550           // configure html5 to get links working on jsfiddle
1551           $locationProvider.html5Mode(true);
1552         });
1553
1554         function MainCntl($scope, $route, $routeParams, $location) {
1555           $scope.$route = $route;
1556           $scope.$location = $location;
1557           $scope.$routeParams = $routeParams;
1558         }
1559
1560         function BookCntl($scope, $routeParams) {
1561           $scope.name = "BookCntl";
1562           $scope.params = $routeParams;
1563         }
1564
1565         function ChapterCntl($scope, $routeParams) {
1566           $scope.name = "ChapterCntl";
1567           $scope.params = $routeParams;
1568         }
1569       </file>
1570
1571       <file name="scenario.js">
1572         it('should load and compile correct template', function() {
1573           element('a:contains("Moby: Ch1")').click();
1574           var content = element('.doc-example-live [ng-view]').text();
1575           expect(content).toMatch(/controller\: ChapterCntl/);
1576           expect(content).toMatch(/Book Id\: Moby/);
1577           expect(content).toMatch(/Chapter Id\: 1/);
1578
1579           element('a:contains("Scarlet")').click();
1580           sleep(2); // promises are not part of scenario waiting
1581           content = element('.doc-example-live [ng-view]').text();
1582           expect(content).toMatch(/controller\: BookCntl/);
1583           expect(content).toMatch(/Book Id\: Scarlet/);
1584         });
1585       </file>
1586     </example>
1587     */
1588
1589    /**
1590     * @ngdoc event
1591     * @name ngRoute.$route#$routeChangeStart
1592     * @eventOf ngRoute.$route
1593     * @eventType broadcast on root scope
1594     * @description
1595     * Broadcasted before a route change. At this  point the route services starts
1596     * resolving all of the dependencies needed for the route change to occurs.
1597     * Typically this involves fetching the view template as well as any dependencies
1598     * defined in `resolve` route property. Once  all of the dependencies are resolved
1599     * `$routeChangeSuccess` is fired.
1600     *
1601     * @param {Object} angularEvent Synthetic event object.
1602     * @param {Route} next Future route information.
1603     * @param {Route} current Current route information.
1604     */
1605
1606    /**
1607     * @ngdoc event
1608     * @name ngRoute.$route#$routeChangeSuccess
1609     * @eventOf ngRoute.$route
1610     * @eventType broadcast on root scope
1611     * @description
1612     * Broadcasted after a route dependencies are resolved.
1613     * {@link ngRoute.directive:ngView ngView} listens for the directive
1614     * to instantiate the controller and render the view.
1615     *
1616     * @param {Object} angularEvent Synthetic event object.
1617     * @param {Route} current Current route information.
1618     * @param {Route|Undefined} previous Previous route information, or undefined if current is
1619     * first route entered.
1620     */
1621
1622    /**
1623     * @ngdoc event
1624     * @name ngRoute.$route#$routeChangeError
1625     * @eventOf ngRoute.$route
1626     * @eventType broadcast on root scope
1627     * @description
1628     * Broadcasted if any of the resolve promises are rejected.
1629     *
1630     * @param {Object} angularEvent Synthetic event object
1631     * @param {Route} current Current route information.
1632     * @param {Route} previous Previous route information.
1633     * @param {Route} rejection Rejection of the promise. Usually the error of the failed promise.
1634     */
1635
1636    /**
1637     * @ngdoc event
1638     * @name ngRoute.$route#$routeUpdate
1639     * @eventOf ngRoute.$route
1640     * @eventType broadcast on root scope
1641     * @description
1642     *
1643     * The `reloadOnSearch` property has been set to false, and we are reusing the same
1644     * instance of the Controller.
1645     */
1646
1647    var forceReload = false,
1648        $route = {
1649          routes: routes,
1650
1651          /**
1652           * @ngdoc method
1653           * @name ngRoute.$route#reload
1654           * @methodOf ngRoute.$route
1655           *
1656           * @description
1657           * Causes `$route` service to reload the current route even if
1658           * {@link ng.$location $location} hasn't changed.
1659           *
1660           * As a result of that, {@link ngRoute.directive:ngView ngView}
1661           * creates new scope, reinstantiates the controller.
1662           */
1663          reload: function() {
1664            forceReload = true;
1665            $rootScope.$evalAsync(updateRoute);
1666          }
1667        };
1668
1669    $rootScope.$on('$locationChangeSuccess', updateRoute);
1670
1671    return $route;
1672
1673    /////////////////////////////////////////////////////
1674
1675    /**
1676     * @param on {string} current url
1677     * @param route {Object} route regexp to match the url against
1678     * @return {?Object}
1679     *
1680     * @description
1681     * Check if the route matches the current url.
1682     *
1683     * Inspired by match in
1684     * visionmedia/express/lib/router/router.js.
1685     */
1686    function switchRouteMatcher(on, route) {
1687      var keys = route.keys,
1688          params = {};
1689
1690      if (!route.regexp) return null;
1691
1692      var m = route.regexp.exec(on);
1693      if (!m) return null;
1694
1695      for (var i = 1, len = m.length; i < len; ++i) {
1696        var key = keys[i - 1];
1697
1698        var val = 'string' == typeof m[i]
1699              ? decodeURIComponent(m[i])
1700              : m[i];
1701
1702        if (key && val) {
1703          params[key.name] = val;
1704        }
1705      }
1706      return params;
1707    }
1708
1709    function updateRoute() {
1710      var next = parseRoute(),
1711          last = $route.current;
1712
1713      if (next && last && next.$$route === last.$$route
1714          && angular.equals(next.pathParams, last.pathParams)
1715          && !next.reloadOnSearch && !forceReload) {
1716        last.params = next.params;
1717        angular.copy(last.params, $routeParams);
1718        $rootScope.$broadcast('$routeUpdate', last);
1719      } else if (next || last) {
1720        forceReload = false;
1721        $rootScope.$broadcast('$routeChangeStart', next, last);
1722        $route.current = next;
1723        if (next) {
1724          if (next.redirectTo) {
1725            if (angular.isString(next.redirectTo)) {
1726              $location.path(interpolate(next.redirectTo, next.params)).search(next.params)
1727                       .replace();
1728            } else {
1729              $location.url(next.redirectTo(next.pathParams, $location.path(), $location.search()))
1730                       .replace();
1731            }
1732          }
1733        }
1734
1735        $q.when(next).
1736          then(function() {
1737            if (next) {
1738              var locals = angular.extend({}, next.resolve),
1739                  template, templateUrl;
1740
1741              angular.forEach(locals, function(value, key) {
1742                locals[key] = angular.isString(value) ?
1743                    $injector.get(value) : $injector.invoke(value);
1744              });
1745
1746              if (angular.isDefined(template = next.template)) {
1747                if (angular.isFunction(template)) {
1748                  template = template(next.params);
1749                }
1750              } else if (angular.isDefined(templateUrl = next.templateUrl)) {
1751                if (angular.isFunction(templateUrl)) {
1752                  templateUrl = templateUrl(next.params);
1753                }
1754                templateUrl = $sce.getTrustedResourceUrl(templateUrl);
1755                if (angular.isDefined(templateUrl)) {
1756                  next.loadedTemplateUrl = templateUrl;
1757                  template = $http.get(templateUrl, {cache: $templateCache}).
1758                      then(function(response) { return response.data; });
1759                }
1760              }
1761              if (angular.isDefined(template)) {
1762                locals['$template'] = template;
1763              }
1764              return $q.all(locals);
1765            }
1766          }).
1767          // after route change
1768          then(function(locals) {
1769            if (next == $route.current) {
1770              if (next) {
1771                next.locals = locals;
1772                angular.copy(next.params, $routeParams);
1773              }
1774              $rootScope.$broadcast('$routeChangeSuccess', next, last);
1775            }
1776          }, function(error) {
1777            if (next == $route.current) {
1778              $rootScope.$broadcast('$routeChangeError', next, last, error);
1779            }
1780          });
1781      }
1782    }
1783
1784
1785    /**
1786     * @returns the current active route, by matching it against the URL
1787     */
1788    function parseRoute() {
1789      // Match a route
1790      var params, match;
1791      angular.forEach(routes, function(route, path) {
1792        if (!match && (params = switchRouteMatcher($location.path(), route))) {
1793          match = inherit(route, {
1794            params: angular.extend({}, $location.search(), params),
1795            pathParams: params});
1796          match.$$route = route;
1797        }
1798      });
1799      // No route matched; fallback to "otherwise" route
1800      return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}});
1801    }
1802
1803    /**
1804     * @returns interpolation of the redirect path with the parameters
1805     */
1806    function interpolate(string, params) {
1807      var result = [];
1808      angular.forEach((string||'').split(':'), function(segment, i) {
1809        if (i === 0) {
1810          result.push(segment);
1811        } else {
1812          var segmentMatch = segment.match(/(\w+)(.*)/);
1813          var key = segmentMatch[1];
1814          result.push(params[key]);
1815          result.push(segmentMatch[2] || '');
1816          delete params[key];
1817        }
1818      });
1819      return result.join('');
1820    }
1821  }];
1822}
1823
1824ngRouteModule.provider('$routeParams', $RouteParamsProvider);
1825
1826
1827/**
1828 * @ngdoc object
1829 * @name ngRoute.$routeParams
1830 * @requires $route
1831 *
1832 * @description
1833 * The `$routeParams` service allows you to retrieve the current set of route parameters.
1834 *
1835 * Requires the {@link ngRoute `ngRoute`} module to be installed.
1836 *
1837 * The route parameters are a combination of {@link ng.$location `$location`}'s
1838 * {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}.
1839 * The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched.
1840 *
1841 * In case of parameter name collision, `path` params take precedence over `search` params.
1842 *
1843 * The service guarantees that the identity of the `$routeParams` object will remain unchanged
1844 * (but its properties will likely change) even when a route change occurs.
1845 *
1846 * Note that the `$routeParams` are only updated *after* a route change completes successfully.
1847 * This means that you cannot rely on `$routeParams` being correct in route resolve functions.
1848 * Instead you can use `$route.current.params` to access the new route's parameters.
1849 *
1850 * @example
1851 * <pre>
1852 *  // Given:
1853 *  // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
1854 *  // Route: /Chapter/:chapterId/Section/:sectionId
1855 *  //
1856 *  // Then
1857 *  $routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
1858 * </pre>
1859 */
1860function $RouteParamsProvider() {
1861  this.$get = function() { return {}; };
1862}
1863
1864ngRouteModule.directive('ngView', ngViewFactory);
1865ngRouteModule.directive('ngView', ngViewFillContentFactory);
1866
1867
1868/**
1869 * @ngdoc directive
1870 * @name ngRoute.directive:ngView
1871 * @restrict ECA
1872 *
1873 * @description
1874 * # Overview
1875 * `ngView` is a directive that complements the {@link ngRoute.$route $route} service by
1876 * including the rendered template of the current route into the main layout (`index.html`) file.
1877 * Every time the current route changes, the included view changes with it according to the
1878 * configuration of the `$route` service.
1879 *
1880 * Requires the {@link ngRoute `ngRoute`} module to be installed.
1881 *
1882 * @animations
1883 * enter - animation is used to bring new content into the browser.
1884 * leave - animation is used to animate existing content away.
1885 *
1886 * The enter and leave animation occur concurrently.
1887 *
1888 * @scope
1889 * @priority 400
1890 * @example
1891    <example module="ngViewExample" deps="angular-route.js" animations="true">
1892      <file name="index.html">
1893        <div ng-controller="MainCntl as main">
1894          Choose:
1895          <a href="Book/Moby">Moby</a> |
1896          <a href="Book/Moby/ch/1">Moby: Ch1</a> |
1897          <a href="Book/Gatsby">Gatsby</a> |
1898          <a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> |
1899          <a href="Book/Scarlet">Scarlet Letter</a><br/>
1900
1901          <div class="view-animate-container">
1902            <div ng-view class="view-animate"></div>
1903          </div>
1904          <hr />
1905
1906          <pre>$location.path() = {{main.$location.path()}}</pre>
1907          <pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre>
1908          <pre>$route.current.params = {{main.$route.current.params}}</pre>
1909          <pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre>
1910          <pre>$routeParams = {{main.$routeParams}}</pre>
1911        </div>
1912      </file>
1913
1914      <file name="book.html">
1915        <div>
1916          controller: {{book.name}}<br />
1917          Book Id: {{book.params.bookId}}<br />
1918        </div>
1919      </file>
1920
1921      <file name="chapter.html">
1922        <div>
1923          controller: {{chapter.name}}<br />
1924          Book Id: {{chapter.params.bookId}}<br />
1925          Chapter Id: {{chapter.params.chapterId}}
1926        </div>
1927      </file>
1928
1929      <file name="animations.css">
1930        .view-animate-container {
1931          position:relative;
1932          height:100px!important;
1933          position:relative;
1934          background:white;
1935          border:1px solid black;
1936          height:40px;
1937          overflow:hidden;
1938        }
1939
1940        .view-animate {
1941          padding:10px;
1942        }
1943
1944        .view-animate.ng-enter, .view-animate.ng-leave {
1945          -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
1946          transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s;
1947
1948          display:block;
1949          width:100%;
1950          border-left:1px solid black;
1951
1952          position:absolute;
1953          top:0;
1954          left:0;
1955          right:0;
1956          bottom:0;
1957          padding:10px;
1958        }
1959
1960        .view-animate.ng-enter {
1961          left:100%;
1962        }
1963        .view-animate.ng-enter.ng-enter-active {
1964          left:0;
1965        }
1966        .view-animate.ng-leave.ng-leave-active {
1967          left:-100%;
1968        }
1969      </file>
1970
1971      <file name="script.js">
1972        angular.module('ngViewExample', ['ngRoute', 'ngAnimate'],
1973          function($routeProvider, $locationProvider) {
1974            $routeProvider.when('/Book/:bookId', {
1975              templateUrl: 'book.html',
1976              controller: BookCntl,
1977              controllerAs: 'book'
1978            });
1979            $routeProvider.when('/Book/:bookId/ch/:chapterId', {
1980              templateUrl: 'chapter.html',
1981              controller: ChapterCntl,
1982              controllerAs: 'chapter'
1983            });
1984
1985            // configure html5 to get links working on jsfiddle
1986            $locationProvider.html5Mode(true);
1987        });
1988
1989        function MainCntl($route, $routeParams, $location) {
1990          this.$route = $route;
1991          this.$location = $location;
1992          this.$routeParams = $routeParams;
1993        }
1994
1995        function BookCntl($routeParams) {
1996          this.name = "BookCntl";
1997          this.params = $routeParams;
1998        }
1999
2000        function ChapterCntl($routeParams) {
2001          this.name = "ChapterCntl";
2002          this.params = $routeParams;
2003        }
2004      </file>
2005
2006      <file name="scenario.js">
2007        it('should load and compile correct template', function() {
2008          element('a:contains("Moby: Ch1")').click();
2009          var content = element('.doc-example-live [ng-view]').text();
2010          expect(content).toMatch(/controller\: ChapterCntl/);
2011          expect(content).toMatch(/Book Id\: Moby/);
2012          expect(content).toMatch(/Chapter Id\: 1/);
2013
2014          element('a:contains("Scarlet")').click();
2015          content = element('.doc-example-live [ng-view]').text();
2016          expect(content).toMatch(/controller\: BookCntl/);
2017          expect(content).toMatch(/Book Id\: Scarlet/);
2018        });
2019      </file>
2020    </example>
2021 */
2022
2023
2024/**
2025 * @ngdoc event
2026 * @name ngRoute.directive:ngView#$viewContentLoaded
2027 * @eventOf ngRoute.directive:ngView
2028 * @eventType emit on the current ngView scope
2029 * @description
2030 * Emitted every time the ngView content is reloaded.
2031 */
2032ngViewFactory.$inject = ['$route', '$anchorScroll', '$animate'];
2033function ngViewFactory(   $route,   $anchorScroll,   $animate) {
2034  return {
2035    restrict: 'ECA',
2036    terminal: true,
2037    priority: 400,
2038    transclude: 'element',
2039    link: function(scope, $element, attr, ctrl, $transclude) {
2040        var currentScope,
2041            currentElement,
2042            autoScrollExp = attr.autoscroll,
2043            onloadExp = attr.onload || '';
2044
2045        scope.$on('$routeChangeSuccess', update);
2046        update();
2047
2048        function cleanupLastView() {
2049          if (currentScope) {
2050            currentScope.$destroy();
2051            currentScope = null;
2052          }
2053          if(currentElement) {
2054            $animate.leave(currentElement);
2055            currentElement = null;
2056          }
2057        }
2058
2059        function update() {
2060          var locals = $route.current && $route.current.locals,
2061              template = locals && locals.$template;
2062
2063          if (template) {
2064            var newScope = scope.$new();
2065            var current = $route.current;
2066
2067            // Note: This will also link all children of ng-view that were contained in the original
2068            // html. If that content contains controllers, ... they could pollute/change the scope.
2069            // However, using ng-view on an element with additional content does not make sense...
2070            // Note: We can't remove them in the cloneAttchFn of $transclude as that
2071            // function is called before linking the content, which would apply child
2072            // directives to non existing elements.
2073            var clone = $transclude(newScope, function(clone) {
2074              $animate.enter(clone, null, currentElement || $element, function onNgViewEnter () {
2075                if (angular.isDefined(autoScrollExp)
2076                  && (!autoScrollExp || scope.$eval(autoScrollExp))) {
2077                  $anchorScroll();
2078                }
2079              });
2080              cleanupLastView();
2081            });
2082
2083            currentElement = clone;
2084            currentScope = current.scope = newScope;
2085            currentScope.$emit('$viewContentLoaded');
2086            currentScope.$eval(onloadExp);
2087          } else {
2088            cleanupLastView();
2089          }
2090        }
2091    }
2092  };
2093}
2094
2095// This directive is called during the $transclude call of the first `ngView` directive.
2096// It will replace and compile the content of the element with the loaded template.
2097// We need this directive so that the element content is already filled when
2098// the link function of another directive on the same element as ngView
2099// is called.
2100ngViewFillContentFactory.$inject = ['$compile', '$controller', '$route'];
2101function ngViewFillContentFactory($compile, $controller, $route) {
2102  return {
2103    restrict: 'ECA',
2104    priority: -400,
2105    link: function(scope, $element) {
2106      var current = $route.current,
2107          locals = current.locals;
2108
2109      $element.html(locals.$template);
2110
2111      var link = $compile($element.contents());
2112
2113      if (current.controller) {
2114        locals.$scope = scope;
2115        var controller = $controller(current.controller, locals);
2116        if (current.controllerAs) {
2117          scope[current.controllerAs] = controller;
2118        }
2119        $element.data('$ngControllerController', controller);
2120        $element.children().data('$ngControllerController', controller);
2121      }
2122
2123      link(scope);
2124    }
2125  };
2126}
2127
2128
2129})(window, window.angular);
2130
2131angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
2132angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset-titles.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]);
2133angular.module('ui.bootstrap.transition', [])
2134
2135/**
2136 * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
2137 * @param  {DOMElement} element  The DOMElement that will be animated.
2138 * @param  {string|object|function} trigger  The thing that will cause the transition to start:
2139 *   - As a string, it represents the css class to be added to the element.
2140 *   - As an object, it represents a hash of style attributes to be applied to the element.
2141 *   - As a function, it represents a function to be called that will cause the transition to occur.
2142 * @return {Promise}  A promise that is resolved when the transition finishes.
2143 */
2144.factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
2145
2146  var $transition = function(element, trigger, options) {
2147    options = options || {};
2148    var deferred = $q.defer();
2149    var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
2150
2151    var transitionEndHandler = function(event) {
2152      $rootScope.$apply(function() {
2153        element.unbind(endEventName, transitionEndHandler);
2154        deferred.resolve(element);
2155      });
2156    };
2157
2158    if (endEventName) {
2159      element.bind(endEventName, transitionEndHandler);
2160    }
2161
2162    // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
2163    $timeout(function() {
2164      if ( angular.isString(trigger) ) {
2165        element.addClass(trigger);
2166      } else if ( angular.isFunction(trigger) ) {
2167        trigger(element);
2168      } else if ( angular.isObject(trigger) ) {
2169        element.css(trigger);
2170      }
2171      //If browser does not support transitions, instantly resolve
2172      if ( !endEventName ) {
2173        deferred.resolve(element);
2174      }
2175    });
2176
2177    // Add our custom cancel function to the promise that is returned
2178    // We can call this if we are about to run a new transition, which we know 
2178will prevent this transition from ending,
2179    // i.e. it will therefore never raise a transitionEnd event for that transition
2180    deferred.promise.cancel = function() {
2181      if ( endEventName ) {
2182        element.unbind(endEventName, transitionEndHandler);
2183      }
2184      deferred.reject('Transition cancelled');
2185    };
2186
2187    return deferred.promise;
2188  };
2189
2190  // Work out the name of the transitionEnd event
2191  var transElement = document.createElement('trans');
2192  var transitionEndEventNames = {
2193    'WebkitTransition': 'webkitTransitionEnd',
2194    'MozTransition': 'transitionend',
2195    'OTransition': 'oTransitionEnd',
2196    'transition': 'transitionend'
2197  };
2198  var animationEndEventNames = {
2199    'WebkitTransition': 'webkitAnimationEnd',
2200    'MozTransition': 'animationend',
2201    'OTransition': 'oAnimationEnd',
2202    'transition': 'animationend'
2203  };
2204  function findEndEventName(endEventNames) {
2205    for (var name in endEventNames){
2206      if (transElement.style[name] !== undefined) {
2207        return endEventNames[name];
2208      }
2209    }
2210  }
2211  $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
2212  $transition.animationEndEventName = findEndEventName(animationEndEventNames);
2213  return $transition;
2214}]);
2215
2216angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
2217
2218// The collapsible directive indicates a block of html that will expand and collapse
2219.directive('collapse', ['$transition', function($transition) {
2220  // CSS transitions don't work with height: auto, so we have to manually change the height to a
2221  // specific value and then once the animation completes, we can reset the height to auto.
2222  // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
2223  // "collapse") then you trigger a change to height 0 in between.
2224  // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
2225  var fixUpHeight = function(scope, element, height) {
2226    // We remove the collapse CSS class to prevent a transition when we change to height: auto
2227    element.removeClass('collapse');
2228    element.css({ height: height });
2229    // It appears that  reading offsetWidth makes the browser realise that we have changed the
2230    // height already :-/
2231    var x = element[0].offsetWidth;
2232    element.addClass('collapse');
2233  };
2234
2235  return {
2236    link: function(scope, element, attrs) {
2237
2238      var isCollapsed;
2239      var initialAnimSkip = true;
2240
2241      scope.$watch(attrs.collapse, function(value) {
2242        if (value) {
2243          collapse();
2244        } else {
2245          expand();
2246        }
2247      });
2248      
2249
2250      var currentTransition;
2251      var doTransition = function(change) {
2252        if ( currentTransition ) {
2253          currentTransition.cancel();
2254        }
2255        currentTransition = $transition(element,change);
2256        currentTransition.then(
2257          function() { currentTransition = undefined; },
2258          function() { currentTransition = undefined; }
2259        );
2260        return currentTransition;
2261      };
2262
2263      var expand = function() {
2264        if (initialAnimSkip) {
2265          initialAnimSkip = false;
2266          if ( !isCollapsed ) {
2267            fixUpHeight(scope, element, 'auto');
2268            element.addClass('in');
2269          }
2270        } else {
2271          doTransition({ height : element[0].scrollHeight + 'px' })
2272          .then(function() {
2273            // This check ensures that we don't accidentally update the height if the user has closed
2274            // the group while the animation was still running
2275            if ( !isCollapsed ) {
2276              fixUpHeight(scope, element, 'auto');
2277              element.addClass('in');
2278            }
2279          });
2280        }
2281        isCollapsed = false;
2282      };
2283      
2284      var collapse = function() {
2285        isCollapsed = true;
2286        element.removeClass('in');
2287        if (initialAnimSkip) {
2288          initialAnimSkip = false;
2289          fixUpHeight(scope, element, 0);
2290        } else {
2291          fixUpHeight(scope, element, element[0].scrollHeight + 'px');
2292          doTransition({'height':'0'});
2293        }
2294      };
2295    }
2296  };
2297}]);
2298
2299angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
2300
2301.constant('accordionConfig', {
2302  closeOthers: true
2303})
2304
2305.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
2306
2307  // This array keeps track of the accordion groups
2308  this.groups = [];
2309
2310  // Keep reference to user's scope to properly assign `is-open`
2311  this.scope = $scope;
2312
2313  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
2314  this.closeOthers = function(openGroup) {
2315    var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
2316    if ( closeOthers ) {
2317      angular.forEach(this.groups, function (group) {
2318        if ( group !== openGroup ) {
2319          group.isOpen = false;
2320        }
2321      });
2322    }
2323  };
2324  
2325  // This is called from the accordion-group directive to add itself to the accordion
2326  this.addGroup = function(groupScope) {
2327    var that = this;
2328    this.groups.push(groupScope);
2329
2330    groupScope.$on('$destroy', function (event) {
2331      that.removeGroup(groupScope);
2332    });
2333  };
2334
2335  // This is called from the accordion-group directive when to remove itself
2336  this.removeGroup = function(group) {
2337    var index = this.groups.indexOf(group);
2338    if ( index !== -1 ) {
2339      this.groups.splice(this.groups.indexOf(group), 1);
2340    }
2341  };
2342
2343}])
2344
2345// The accordion directive simply sets up the directive controller
2346// and adds an accordion CSS class to itself element.
2347.directive('accordion', function () {
2348  return {
2349    restrict:'EA',
2350    controller:'AccordionController',
2351    transclude: true,
2352    replace: false,
2353    templateUrl: 'template/accordion/accordion.html'
2354  };
2355})
2356
2357// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
2358.directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
2359  return {
2360    require:'^accordion',         // We need this directive to be inside an accordion
2361    restrict:'EA',
2362    transclude:true,              // It transcludes the contents of the directive into the template
2363    replace: true,                // The element containing the directive will be replaced with the template
2364    templateUrl:'template/accordion/accordion-group.html',
2365    scope:{ heading:'@' },        // Create an isolated scope and interpolate the heading attribute onto this scope
2366    controller: ['$scope', function($scope) {
2367      this.setHeading = function(element) {
2368        this.heading = element;
2369      };
2370    }],
2371    link: function(scope, element, attrs, accordionCtrl) {
2372      var getIsOpen, setIsOpen;
2373
2374      accordionCtrl.addGroup(scope);
2375
2376      scope.isOpen = false;
2377      
2378      if ( attrs.isOpen ) {
2379        getIsOpen = $parse(attrs.isOpen);
2380        setIsOpen = getIsOpen.assign;
2381
2382        accordionCtrl.scope.$watch(getIsOpen, function(value) {
2383          scope.isOpen = !!value;
2384        });
2385      }
2386
2387      scope.$watch('isOpen', function(value) {
2388        if ( value ) {
2389          accordionCtrl.closeOthers(scope);
2390        }
2391        if ( setIsOpen ) {
2392          setIsOpen(accordionCtrl.scope, value);
2393        }
2394      });
2395    }
2396  };
2397}])
2398
2399// Use accordion-heading below an accordion-group to provide a heading containing HTML
2400// <accordion-group>
2401//   <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
2402// </accordion-group>
2403.directive('accordionHeading', function() {
2404  return {
2405    restrict: 'EA',
2406    transclude: true,   // Grab the contents to be used as the heading
2407    template: '',       // In effect remove this element!
2408    replace: true,
2409    require: '^accordionGroup',
2410    compile: function(element, attr, transclude) {
2411      return function link(scope, element, attr, accordionGroupCtrl) {
2412        // Pass the heading to the accordion-group controller
2413        // so that it can be transcluded into the right place in the template
2414        // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
2415        accordionGroupCtrl.setHeading(transclude(scope, function() {}));
2416      };
2417    }
2418  };
2419})
2420
2421// Use in the accordion-group template to indicate where you want the heading to be transcluded
2422// You must provide the property on the accordion-group controller that will hold the transcluded element
2423// <div class="accordion-group">
2424//   <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
2425//   ...
2426// </div>
2427.directive('accordionTransclude', function() {
2428  return {
2429    require: '^accordionGroup',
2430    link: function(scope, element, attr, controller) {
2431      scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
2432        if ( heading ) {
2433          element.html('');
2434          element.append(heading);
2435        }
2436      });
2437    }
2438  };
2439});
2440
2441angular.module("ui.bootstrap.alert", []).directive('alert', function () {
2442  return {
2443    restrict:'EA',
2444    templateUrl:'template/alert/alert.html',
2445    transclude:true,
2446    replace:true,
2447    scope: {
2448      type: '=',
2449      close: '&'
2450    },
2451    link: function(scope, iElement, iAttrs) {
2452      scope.closeable = "close" in iAttrs;
2453    }
2454  };
2455});
2456
2457angular.module('ui.bootstrap.bindHtml', [])
2458
2459  .directive('bindHtmlUnsafe', function () {
2460    return function (scope, element, attr) {
2461      element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe);
2462      scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) {
2463        element.html(value || '');
2464      });
2465    };
2466  });
2467angular.module('ui.bootstrap.buttons', [])
2468
2469.constant('buttonConfig', {
2470  activeClass: 'active',
2471  toggleEvent: 'click'
2472})
2473
2474.directive('btnRadio', ['buttonConfig', function (buttonConfig) {
2475  var activeClass = buttonConfig.activeClass || 'active';
2476  var toggleEvent = buttonConfig.toggleEvent || 'click';
2477
2478  return {
2479    require: 'ngModel',
2480    link: function (scope, element, attrs, ngModelCtrl) {
2481
2482      //model -> UI
2483      ngModelCtrl.$render = function () {
2484        element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
2485      };
2486
2487      //ui->model
2488      element.bind(toggleEvent, function () {
2489        if (!element.hasClass(activeClass)) {
2490          scope.$apply(function () {
2491            ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
2492            ngModelCtrl.$render();
2493          });
2494        }
2495      });
2496    }
2497  };
2498}])
2499
2500.directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
2501  var activeClass = buttonConfig.activeClass || 'active';
2502  var toggleEvent = buttonConfig.toggleEvent || 'click';
2503
2504  return {
2505    require: 'ngModel',
2506    link: function (scope, element, attrs, ngModelCtrl) {
2507
2508      function getTrueValue() {
2509        var trueValue = scope.$eval(attrs.btnCheckboxTrue);
2510        return angular.isDefined(trueValue) ? trueValue : true;
2511      }
2512
2513      function getFalseValue() {
2514        var falseValue = scope.$eval(attrs.btnCheckboxFalse);
2515        return angular.isDefined(falseValue) ? falseValue : false;
2516      }
2517
2518      //model -> UI
2519      ngModelCtrl.$render = function () {
2520        element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
2521      };
2522
2523      //ui->model
2524      element.bind(toggleEvent, function () {
2525        scope.$apply(function () {
2526          ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
2527          ngModelCtrl.$render();
2528        });
2529      });
2530    }
2531  };
2532}]);
2533
2534/**
2535* @ngdoc overview
2536* @name ui.bootstrap.carousel
2537*
2538* @description
2539* AngularJS version of an image carousel.
2540*
2541*/
2542angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
2543.controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
2544  var self = this,
2545    slides = self.slides = [],
2546    currentIndex = -1,
2547    currentTimeout, isPlaying;
2548  self.currentSlide = null;
2549
2550  /* direction: "prev" or "next" */
2551  self.select = function(nextSlide, direction) {
2552    var nextIndex = slides.indexOf(nextSlide);
2553    //Decide direction if it's not given
2554    if (direction === undefined) {
2555      direction = nextIndex > currentIndex ? "next" : "prev";
2556    }
2557    if (nextSlide && nextSlide !== self.currentSlide) {
2558      if ($scope.$currentTransition) {
2559        $scope.$currentTransition.cancel();
2560        //Timeout so ng-class in template has time to fix classes for finished slide
2561        $timeout(goNext);
2562      } else {
2563        goNext();
2564      }
2565    }
2566    function goNext() {
2567      //If we have a slide to transition from and we have a transition type and we're allowed, go
2568      if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
2569        //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
2570        nextSlide.$element.addClass(direction);
2571        var reflow = nextSlide.$element[0].offsetWidth; //force reflow
2572
2573        //Set all other slides to stop doing their stuff for the new transition
2574        angular.forEach(slides, function(slide) {
2575          angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
2576        });
2577        angular.extend(nextSlide, {direction: direction, active: true, entering: true});
2578        angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
2579
2580        $scope.$currentTransition = $transition(nextSlide.$element, {});
2581        //We have to create new pointers inside a closure since next & current will change
2582        (function(next,current) {
2583          $scope.$currentTransition.then(
2584            function(){ transitionDone(next, current); },
2585            function(){ transitionDone(next, current); }
2586          );
2587        }(nextSlide, self.currentSlide));
2588      } else {
2589        transitionDone(nextSlide, self.currentSlide);
2590      }
2591      self.currentSlide = nextSlide;
2592      currentIndex = nextIndex;
2593      //every time you change slides, reset the timer
2594      restartTimer();
2595    }
2596    function transitionDone(next, current) {
2597      angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
2598      angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
2599      $scope.$currentTransition = null;
2600    }
2601  };
2602
2603  /* Allow outside people to call indexOf on slides array */
2604  self.indexOfSlide = function(slide) {
2605    return slides.indexOf(slide);
2606  };
2607
2608  $scope.next = function() {
2609    var newIndex = (currentIndex + 1) % slides.length;
2610
2611    //Prevent this user-triggered transition from occurring if there is already one in progress
2612    if (!$scope.$currentTransition) {
2613      return self.select(slides[newIndex], 'next');
2614    }
2615  };
2616
2617  $scope.prev = function() {
2618    var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
2619
2620    //Prevent this user-triggered transition from occurring if there is already one in progress
2621    if (!$scope.$currentTransition) {
2622      return self.select(slides[newIndex], 'prev');
2623    }
2624  };
2625
2626  $scope.select = function(slide) {
2627    self.select(slide);
2628  };
2629
2630  $scope.isActive = function(slide) {
2631     return self.currentSlide === slide;
2632  };
2633
2634  $scope.slides = function() {
2635    return slides;
2636  };
2637
2638  $scope.$watch('interval', restartTimer);
2639  function restartTimer() {
2640    if (currentTimeout) {
2641      $timeout.cancel(currentTimeout);
2642    }
2643    function go() {
2644      if (isPlaying) {
2645        $scope.next();
2646        restartTimer();
2647      } else {
2648        $scope.pause();
2649      }
2650    }
2651    var interval = +$scope.interval;
2652    if (!isNaN(interval) && interval>=0) {
2653      currentTimeout = $timeout(go, interval);
2654    }
2655  }
2656  $scope.play = function() {
2657    if (!isPlaying) {
2658      isPlaying = true;
2659      restartTimer();
2660    }
2661  };
2662  $scope.pause = function() {
2663    if (!$scope.noPause) {
2664      isPlaying = false;
2665      if (currentTimeout) {
2666        $timeout.cancel(currentTimeout);
2667      }
2668    }
2669  };
2670
2671  self.addSlide = function(slide, element) {
2672    slide.$element = element;
2673    slides.push(slide);
2674    //if this is the first slide or the slide is set to active, select it
2675    if(slides.length === 1 || slide.active) {
2676      self.select(slides[slides.length-1]);
2677      if (slides.length == 1) {
2678        $scope.play();
2679      }
2680    } else {
2681      slide.active = false;
2682    }
2683  };
2684
2685  self.removeSlide = function(slide) {
2686    //get the index of the slide inside the carousel
2687    var index = slides.indexOf(slide);
2688    slides.splice(index, 1);
2689    if (slides.length > 0 && slide.active) {
2690      if (index >= slides.length) {
2691        self.select(slides[index-1]);
2692      } else {
2693        self.select(slides[index]);
2694      }
2695    } else if (currentIndex > index) {
2696      currentIndex--;
2697    }
2698  };
2699}])
2700
2701/**
2702 * @ngdoc directive
2703 * @name ui.bootstrap.carousel.directive:carousel
2704 * @restrict EA
2705 *
2706 * @description
2707 * Carousel is the outer container for a set of image 'slides' to showcase.
2708 *
2709 * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
2710 * @param {boolean=} noTransition Whether to disable transitions on the carousel.
2711 * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
2712 *
2713 * @example
2714<example module="ui.bootstrap">
2715  <file name="index.html">
2716    <carousel>
2717      <slide>
2718        <img src="http://placekitten.com/150/150" style="margin:auto;">
2719        <div class="carousel-caption">
2720          <p>Beautiful!</p>
2721        </div>
2722      </slide>
2723      <slide>
2724        <img src="http://placekitten.com/100/150" style="margin:auto;">
2725        <div class="carousel-caption">
2726          <p>D'aww!</p>
2727        </div>
2728      </slide>
2729    </carousel>
2730  </file>
2731  <file name="demo.css">
2732    .carousel-indicators {
2733      top: auto;
2734      bottom: 15px;
2735    }
2736  </file>
2737</example>
2738 */
2739.directive('carousel', [function() {
2740  return {
2741    restrict: 'EA',
2742    transclude: true,
2743    replace: true,
2744    controller: 'CarouselController',
2745    require: 'carousel',
2746    templateUrl: 'template/carousel/carousel.html',
2747    scope: {
2748      interval: '=',
2749      noTransition: '=',
2750      noPause: '='
2751    }
2752  };
2753}])
2754
2755/**
2756 * @ngdoc directive
2757 * @name ui.bootstrap.carousel.directive:slide
2758 * @restrict EA
2759 *
2760 * @description
2761 * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}.  Must be placed as a child of a carousel element.
2762 *
2763 * @param {boolean=} active Model binding, whether or not this slide is currently active.
2764 *
2765 * @example
2766<example module="ui.bootstrap">
2767  <file name="index.html">
2768<div ng-controller="CarouselDemoCtrl">
2769  <carousel>
2770    <slide ng-repeat="slide in slides" active="slide.active">
2771      <img ng-src="{{slide.image}}" style="margin:auto;">
2772      <div class="carousel-caption">
2773        <h4>Slide {{$index}}</h4>
2774        <p>{{slide.text}}</p>
2775      </div>
2776    </slide>
2777  </carousel>
2778  <div class="row-fluid">
2779    <div class="span6">
2780      <ul>
2781        <li ng-repeat="slide in slides">
2782          <button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
2783          {{$index}}: {{slide.text}}
2784        </li>
2785      </ul>
2786      <a class="btn" ng-click="addSlide()">Add Slide</a>
2787    </div>
2788    <div class="span6">
2789      Interval, in milliseconds: <input type="number" ng-model="myInterval">
2790      <br />Enter a negative number to stop the interval.
2791    </div>
2792  </div>
2793</div>
2794  </file>
2795  <file name="script.js">
2796function CarouselDemoCtrl($scope) {
2797  $scope.myInterval = 5000;
2798  var slides = $scope.slides = [];
2799  $scope.addSlide = function() {
2800    var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
2801    slides.push({
2802      image: 'http://placekitten.com/' + newWidth + '/200',
2803      text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
2804        ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
2805    });
2806  };
2807  for (var i=0; i<4; i++) $scope.addSlide();
2808}
2809  </file>
2810  <file name="demo.css">
2811    .carousel-indicators {
2812      top: auto;
2813      bottom: 15px;
2814    }
2815  </file>
2816</example>
2817*/
2818
2819.directive('slide', ['$parse', function($parse) {
2820  return {
2821    require: '^carousel',
2822    restrict: 'EA',
2823    transclude: true,
2824    replace: true,
2825    templateUrl: 'template/carousel/slide.html',
2826    scope: {
2827    },
2828    link: function (scope, element, attrs, carouselCtrl) {
2829      //Set up optional 'active' = binding
2830      if (attrs.active) {
2831        var getActive = $parse(attrs.active);
2832        var setActive = getActive.assign;
2833        var lastValue = scope.active = getActive(scope.$parent);
2834        scope.$watch(function parentActiveWatch() {
2835          var parentActive = getActive(scope.$parent);
2836
2837          if (parentActive !== scope.active) {
2838            // we are out of sync and need to copy
2839            if (parentActive !== lastValue) {
2840              // parent changed and it has precedence
2841              lastValue = scope.active = parentActive;
2842            } else {
2843              // if the parent can be assigned then do so
2844              setActive(scope.$parent, parentActive = lastValue = scope.active);
2845            }
2846          }
2847          return parentActive;
2848        });
2849      }
2850
2851      carouselCtrl.addSlide(scope, element);
2852      //when the scope is destroyed then remove the slide from the current slides array
2853      scope.$on('$destroy', function() {
2854        carouselCtrl.removeSlide(scope);
2855      });
2856
2857      scope.$watch('active', function(active) {
2858        if (active) {
2859          carouselCtrl.select(scope);
2860        }
2861      });
2862    }
2863  };
2864}]);
2865
2866angular.module('ui.bootstrap.position', [])
2867
2868/**
2869 * A set of utility methods that can be use to retrieve position of DOM elements.
2870 * It is meant to be used where we need to absolute-position DOM elements in
2871 * relation to other, existing elements (this is the case for tooltips, popovers,
2872 * typeahead suggestions etc.).
2873 */
2874  .factory('$position', ['$document', '$window', function ($document, $window) {
2875
2876    function getStyle(el, cssprop) {
2877      if (el.currentStyle) { //IE
2878        return el.currentStyle[cssprop];
2879      } else if ($window.getComputedStyle) {
2880        return $window.getComputedStyle(el)[cssprop];
2881      }
2882      // finally try and get inline style
2883      return el.style[cssprop];
2884    }
2885
2886    /**
2887     * Checks if a given element is statically positioned
2888     * @param element - raw DOM element
2889     */
2890    function isStaticPositioned(element) {
2891      return (getStyle(element, "position") || 'static' ) === 'static';
2892    }
2893
2894    /**
2895     * returns the closest, non-statically positioned parentOffset of a given element
2896     * @param element
2897     */
2898    var parentOffsetEl = function (element) {
2899      var docDomEl = $document[0];
2900      var offsetParent = element.offsetParent || docDomEl;
2901      while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
2902        offsetParent = offsetParent.offsetParent;
2903      }
2904      return offsetParent || docDomEl;
2905    };
2906
2907    return {
2908      /**
2909       * Provides read-only equivalent of jQuery's position function:
2910       * http://api.jquery.com/position/
2911       */
2912      position: function (element) {
2913        var elBCR = this.offset(element);
2914        var offsetParentBCR = { top: 0, left: 0 };
2915        var offsetParentEl = parentOffsetEl(element[0]);
2916        if (offsetParentEl != $document[0]) {
2917          offsetParentBCR = this.offset(angular.element(offsetParentEl));
2918          offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
2919          offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
2920        }
2921
2922        var boundingClientRect = element[0].getBoundingClientRect();
2923        return {
2924          width: boundingClientRect.width || element.prop('offsetWidth'),
2925          height: boundingClientRect.height || element.prop('offsetHeight'),
2926          top: elBCR.top - offsetParentBCR.top,
2927          left: elBCR.left - offsetParentBCR.left
2928        };
2929      },
2930
2931      /**
2932       * Provides read-only equivalent of jQuery's offset function:
2933       * http://api.jquery.com/offset/
2934       */
2935      offset: function (element) {
2936        var boundingClientRect = element[0].getBoundingClientRect();
2937        return {
2938          width: boundingClientRect.width || element.prop('offsetWidth'),
2939          height: boundingClientRect.height || element.prop('offsetHeight'),
2940          top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
2941          left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft  || $document[0].documentElement.scrollLeft)
2942        };
2943      }
2944    };
2945  }]);
2946
2947angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
2948
2949.constant('datepickerConfig', {
2950  dayFormat: 'dd',
2951  monthFormat: 'MMMM',
2952  yearFormat: 'yyyy',
2953  dayHeaderFormat: 'EEE',
2954  dayTitleFormat: 'MMMM yyyy',
2955  monthTitleFormat: 'yyyy',
2956  showWeeks: true,
2957  startingDay: 0,
2958  yearRange: 20,
2959  minDate: null,
2960  maxDate: null
2961})
2962
2963.controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
2964  var format = {
2965    day:        getValue($attrs.dayFormat,        dtConfig.dayFormat),
2966    month:      getValue($attrs.monthFormat,      dtConfig.monthFormat),
2967    year:       getValue($attrs.yearFormat,       dtConfig.yearFormat),
2968    dayHeader:  getValue($attrs.dayHeaderFormat,  dtConfig.dayHeaderFormat),
2969    dayTitle:   getValue($attrs.dayTitleFormat,   dtConfig.dayTitleFormat),
2970    monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
2971  },
2972  startingDay = getValue($attrs.startingDay,      dtConfig.startingDay),
2973  yearRange =   getValue($attrs.yearRange,        dtConfig.yearRange);
2974
2975  this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
2976  this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
2977
2978  function getValue(value, defaultValue) {
2979    return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
2980  }
2981
2982  function getDaysInMonth( year, month ) {
2983    return new Date(year, month, 0).getDate();
2984  }
2985
2986  function getDates(startDate, n) {
2987    var dates = new Array(n);
2988    var current = startDate, i = 0;
2989    while (i < n) {
2990      dates[i++] = new Date(current);
2991      current.setDate( current.getDate() + 1 );
2992    }
2993    return dates;
2994  }
2995
2996  function makeDate(date, format, isSelected, isSecondary) {
2997    return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
2998  }
2999
3000  this.modes = [
3001    {
3002      name: 'day',
3003      getVisibleDates: function(date, selected) {
3004        var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
3005        var difference = startingDay - firstDayOfMonth.getDay(),
3006        numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
3007        firstDate = new Date(firstDayOfMonth), numDates = 0;
3008
3009        if ( numDisplayedFromPreviousMonth > 0 ) {
3010          firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
3011          numDates += numDisplayedFromPreviousMonth; // Previous
3012        }
3013        numDates += getDaysInMonth(year, month + 1); // Current
3014        numDates += (7 - numDates % 7) % 7; // Next
3015
3016        var days = getDates(firstDate, numDates), labels = new Array(7);
3017        for (var i = 0; i < numDates; i ++) {
3018          var dt = new Date(days[i]);
3019          days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
3020        }
3021        for (var j = 0; j < 7; j++) {
3022          labels[j] = dateFilter(days[j].date, format.dayHeader);
3023        }
3024        return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
3025      },
3026      compare: function(date1, date2) {
3027        return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
3028      },
3029      split: 7,
3030      step: { months: 1 }
3031    },
3032    {
3033      name: 'month',
3034      getVisibleDates: function(date, selected) {
3035        var months = new Array(12), year = date.getFullYear();
3036        for ( var i = 0; i < 12; i++ ) {
3037          var dt = new Date(year, i, 1);
3038          months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
3039        }
3040        return { objects: months, title: dateFilter(date, format.monthTitle) };
3041      },
3042      compare: function(date1, date2) {
3043        return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
3044      },
3045      split: 3,
3046      step: { years: 1 }
3047    },
3048    {
3049      name: 'year',
3050      getVisibleDates: function(date, selected) {
3051        var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
3052        for ( var i = 0; i < yearRange; i++ ) {
3053          var dt = new Date(startYear + i, 0, 1);
3054          years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
3055        }
3056        return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
3057      },
3058      compare: function(date1, date2) {
3059        return date1.getFullYear() - date2.getFullYear();
3060      },
3061      split: 5,
3062      step: { years: yearRange }
3063    }
3064  ];
3065
3066  this.isDisabled = function(date, mode) {
3067    var currentMode = this.modes[mode || 0];
3068    return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
3069  };
3070}])
3071
3072.directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
3073  return {
3074    restrict: 'EA',
3075    replace: true,
3076    templateUrl: 'template/datepicker/datepicker.html',
3077    scope: {
3078      dateDisabled: '&'
3079    },
3080    require: ['datepicker', '?^ngModel'],
3081    controller: 'DatepickerController',
3082    link: function(scope, element, attrs, ctrls) {
3083      var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
3084
3085      if (!ngModel) {
3086        return; // do nothing if no ng-model
3087      }
3088
3089      // Configuration parameters
3090      var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
3091
3092      if (attrs.showWeeks) {
3093        scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
3094          showWeeks = !! value;
3095          updateShowWeekNumbers();
3096        });
3097      } else {
3098        updateShowWeekNumbers();
3099      }
3100
3101      if (attrs.min) {
3102        scope.$parent.$watch($parse(attrs.min), function(value) {
3103          datepickerCtrl.minDate = value ? new Date(value) : null;
3104          refill();
3105        });
3106      }
3107      if (attrs.max) {
3108        scope.$parent.$watch($parse(attrs.max), function(value) {
3109          datepickerCtrl.maxDate = value ? new Date(value) : null;
3110          refill();
3111        });
3112      }
3113
3114      function updateShowWeekNumbers() {
3115        scope.showWeekNumbers = mode === 0 && showWeeks;
3116      }
3117
3118      // Split array into smaller arrays
3119      function split(arr, size) {
3120        var arrays = [];
3121        while (arr.length > 0) {
3122          arrays.push(arr.splice(0, size));
3123        }
3124        return arrays;
3125      }
3126
3127      function refill( updateSelected ) {
3128        var date = null, valid = true;
3129
3130        if ( ngModel.$modelValue ) {
3131          date = new Date( ngModel.$modelValue );
3132
3133          if ( isNaN(date) ) {
3134            valid = false;
3135            $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
3136          } else if ( updateSelected ) {
3137            selected = date;
3138          }
3139        }
3140        ngModel.$setValidity('date', valid);
3141
3142        var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
3143        angular.forEach(data.objects, function(obj) {
3144          obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
3145        });
3146
3147        ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
3148
3149        scope.rows = split(data.objects, currentMode.split);
3150        scope.labels = data.labels || [];
3151        scope.title = data.title;
3152      }
3153
3154      function setMode(value) {
3155        mode = value;
3156        updateShowWeekNumbers();
3157        refill();
3158      }
3159
3160      ngModel.$render = function() {
3161        refill( true );
3162      };
3163
3164      scope.select = function( date ) {
3165        if ( mode === 0 ) {
3166          var dt = new Date( ngModel.$modelValue );
3167          dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
3168          ngModel.$setViewValue( dt );
3169          refill( true );
3170        } else {
3171          selected = date;
3172          setMode( mode - 1 );
3173        }
3174      };
3175      scope.move = function(direction) {
3176        var step = datepickerCtrl.modes[mode].step;
3177        selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
3178        selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
3179        refill();
3180      };
3181      scope.toggleMode = function() {
3182        setMode( (mode + 1) % datepickerCtrl.modes.length );
3183      };
3184      scope.getWeekNumber = function(row) {
3185        return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
3186      };
3187
3188      function getISO8601WeekNumber(date) {
3189        var checkDate = new Date(date);
3190        checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
3191        var time = checkDate.getTime();
3192        checkDate.setMonth(0); // Compare with Jan 1
3193        checkDate.setDate(1);
3194        return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
3195      }
3196    }
3197  };
3198}])
3199
3200.constant('datepickerPopupConfig', {
3201  dateFormat: 'yyyy-MM-dd',
3202  currentText: 'Today',
3203  toggleWeeksText: 'Weeks',
3204  clearText: 'Clear',
3205  closeText: 'Done',
3206  closeOnDateSelection: true,
3207  appendToBody: false
3208})
3209
3210.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
3211function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) {
3212  return {
3213    restrict: 'EA',
3214    require: 'ngModel',
3215    link: function(originalScope, element, attrs, ngModel) {
3216      var dateFormat;
3217      attrs.$observe('datepickerPopup', function(value) {
3218          dateFormat = value || datepickerPopupConfig.dateFormat;
3219          ngModel.$render();
3220      });
3221
3222      var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
3223      var appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
3224
3225      // create a child scope for the datepicker directive so we are not polluting original scope
3226      var scope = originalScope.$new();
3227
3228      originalScope.$on('$destroy', function() {
3229        scope.$destroy();
3230      });
3231
3232      attrs.$observe('currentText', function(text) {
3233        scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText;
3234      });
3235      attrs.$observe('toggleWeeksText', function(text) {
3236        scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText;
3237      });
3238      attrs.$observe('clearText', function(text) {
3239        scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText;
3240      });
3241      attrs.$observe('closeText', function(text) {
3242        scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText;
3243      });
3244
3245      var getIsOpen, setIsOpen;
3246      if ( attrs.isOpen ) {
3247        getIsOpen = $parse(attrs.isOpen);
3248        setIsOpen = getIsOpen.assign;
3249
3250        originalScope.$watch(getIsOpen, function updateOpen(value) {
3251          scope.isOpen = !! value;
3252        });
3253      }
3254      scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
3255
3256      function setOpen( value ) {
3257        if (setIsOpen) {
3258          setIsOpen(originalScope, !!value);
3259        } else {
3260          scope.isOpen = !!value;
3261        }
3262      }
3263
3264      var documentClickBind = function(event) {
3265        if (scope.isOpen && event.target !== element[0]) {
3266          scope.$apply(function() {
3267            setOpen(false);
3268          });
3269        }
3270      };
3271
3272      var elementFocusBind = function() {
3273        scope.$apply(function() {
3274          setOpen( true );
3275        });
3276      };
3277
3278      // popup element used to display calendar
3279      var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
3280      popupEl.attr({
3281        'ng-model': 'date',
3282        'ng-change': 'dateSelection()'
3283      });
3284      var datepickerEl = angular.element(popupEl.children()[0]);
3285      if (attrs.datepickerOptions) {
3286        datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
3287      }
3288
3289      // TODO: reverse from dateFilter string to Date object
3290      function parseDate(viewValue) {
3291        if (!viewValue) {
3292          ngModel.$setValidity('date', true);
3293          return null;
3294        } else if (angular.isDate(viewValue)) {
3295          ngModel.$setValidity('date', true);
3296          return viewValue;
3297        } else if (angular.isString(viewValue)) {
3298          var date = new Date(viewValue);
3299          if (isNaN(date)) {
3300            ngModel.$setValidity('date', false);
3301            return undefined;
3302          } else {
3303            ngModel.$setValidity('date', true);
3304            return date;
3305          }
3306        } else {
3307          ngModel.$setValidity('date', false);
3308          return undefined;
3309        }
3310      }
3311      ngModel.$parsers.unshift(parseDate);
3312
3313      // Inner change
3314      scope.dateSelection = function() {
3315        ngModel.$setViewValue(scope.date);
3316        ngModel.$render();
3317
3318        if (closeOnDateSelection) {
3319          setOpen( false );
3320        }
3321      };
3322
3323      element.bind('input change keyup', function() {
3324        scope.$apply(function() {
3325          updateCalendar();
3326        });
3327      });
3328
3329      // Outter change
3330      ngModel.$render = function() {
3331        var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
3332        element.val(date);
3333
3334        updateCalendar();
3335      };
3336
3337      function updateCalendar() {
3338        scope.date = ngModel.$modelValue;
3339        updatePosition();
3340      }
3341
3342      function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
3343        if (attribute) {
3344          originalScope.$watch($parse(attribute), function(value){
3345            scope[scopeProperty] = value;
3346          });
3347          datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
3348        }
3349      }
3350      addWatchableAttribute(attrs.min, 'min');
3351      addWatchableAttribute(attrs.max, 'max');
3352      if (attrs.showWeeks) {
3353        addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
3354      } else {
3355        scope.showWeeks = datepickerConfig.showWeeks;
3356        datepickerEl.attr('show-weeks', 'showWeeks');
3357      }
3358      if (attrs.dateDisabled) {
3359        datepickerEl.attr('date-disabled', attrs.dateDisabled);
3360      }
3361
3362      function updatePosition() {
3363        scope.position = appendToBody ? $position.offset(element) : $position.position(element);
3364        scope.position.top = scope.position.top + element.prop('offsetHeight');
3365      }
3366
3367      var documentBindingInitialized = false, elementFocusInitialized = false;
3368      scope.$watch('isOpen', function(value) {
3369        if (value) {
3370          updatePosition();
3371          $document.bind('click', documentClickBind);
3372          if(elementFocusInitialized) {
3373            element.unbind('focus', elementFocusBind);
3374          }
3375          element[0].focus();
3376          documentBindingInitialized = true;
3377        } else {
3378          if(documentBindingInitialized) {
3379            $document.unbind('click', documentClickBind);
3380          }
3381          element.bind('focus', elementFocusBind);
3382          elementFocusInitialized = true;
3383        }
3384
3385        if ( setIsOpen ) {
3386          setIsOpen(originalScope, value);
3387        }
3388      });
3389
3390      var $setModelValue = $parse(attrs.ngModel).assign;
3391
3392      scope.today = function() {
3393        $setModelValue(originalScope, new Date());
3394      };
3395      scope.clear = function() {
3396        $setModelValue(originalScope, null);
3397      };
3398
3399      var $popup = $compile(popupEl)(scope);
3400      if ( appendToBody ) {
3401        $document.find('body').append($popup);
3402      } else {
3403        element.after($popup);
3404      }
3405    }
3406  };
3407}])
3408
3409.directive('datepickerPopupWrap', function() {
3410  return {
3411    restrict:'EA',
3412    replace: true,
3413    transclude: true,
3414    templateUrl: 'template/datepicker/popup.html',
3415    link:function (scope, element, attrs) {
3416      element.bind('click', function(event) {
3417        event.preventDefault();
3418        event.stopPropagation();
3419      });
3420    }
3421  };
3422});
3423
3424/*
3425 * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
3426 * @restrict class or attribute
3427 * @example:
3428   <li class="dropdown">
3429     <a class="dropdown-toggle">My Dropdown Menu</a>
3430     <ul class="dropdown-menu">
3431       <li ng-repeat="choice in dropChoices">
3432         <a ng-href="{{choice.href}}">{{choice.text}}</a>
3433       </li>
3434     </ul>
3435   </li>
3436 */
3437
3438angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
3439  var openElement = null,
3440      closeMenu   = angular.noop;
3441  return {
3442    restrict: 'CA',
3443    link: function(scope, element, attrs) {
3444      scope.$watch('$location.path', function() { closeMenu(); });
3445      element.parent().bind('click', function() { closeMenu(); });
3446      element.bind('click', function (event) {
3447
3448        var elementWasOpen = (element === openElement);
3449
3450        event.preventDefault();
3451        event.stopPropagation();
3452
3453        if (!!openElement) {
3454          closeMenu();
3455        }
3456
3457        if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
3458          element.parent().addClass('open');
3459          openElement = element;
3460          closeMenu = function (event) {
3461            if (event) {
3462              event.preventDefault();
3463              event.stopPropagation();
3464            }
3465            $document.unbind('click', closeMenu);
3466            element.parent().removeClass('open');
3467            closeMenu = angular.noop;
3468            openElement = null;
3469          };
3470          $document.bind('click', closeMenu);
3471        }
3472      });
3473    }
3474  };
3475}]);
3476
3477angular.module('ui.bootstrap.modal', [])
3478
3479/**
3480 * A helper, internal data structure that acts as a map but also allows getting / removing
3481 * elements in the LIFO order
3482 */
3483  .factory('$$stackedMap', function () {
3484    return {
3485      createNew: function () {
3486        var stack = [];
3487
3488        return {
3489          add: function (key, value) {
3490            stack.push({
3491              key: key,
3492              value: value
3493            });
3494          },
3495          get: function (key) {
3496            for (var i = 0; i < stack.length; i++) {
3497              if (key == stack[i].key) {
3498                return stack[i];
3499              }
3500            }
3501          },
3502          keys: function() {
3503            var keys = [];
3504            for (var i = 0; i < stack.length; i++) {
3505              keys.push(stack[i].key);
3506            }
3507            return keys;
3508          },
3509          top: function () {
3510            return stack[stack.length - 1];
3511          },
3512          remove: function (key) {
3513            var idx = -1;
3514            for (var i = 0; i < stack.length; i++) {
3515              if (key == stack[i].key) {
3516                idx = i;
3517                break;
3518              }
3519            }
3520            return stack.splice(idx, 1)[0];
3521          },
3522          removeTop: function () {
3523            return stack.splice(stack.length - 1, 1)[0];
3524          },
3525          length: function () {
3526            return stack.length;
3527          }
3528        };
3529      }
3530    };
3531  })
3532
3533/**
3534 * A helper directive for the $modal service. It creates a backdrop element.
3535 */
3536  .directive('modalBackdrop', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
3537    return {
3538      restrict: 'EA',
3539      replace: true,
3540      templateUrl: 'template/modal/backdrop.html',
3541      link: function (scope, element, attrs) {
3542
3543        //trigger CSS transitions
3544        $timeout(function () {
3545          scope.animate = true;
3546        });
3547
3548        scope.close = function (evt) {
3549          var modal = $modalStack.getTop();
3550          if (modal && modal.value.backdrop && modal.value.backdrop != 'static') {
3551            evt.preventDefault();
3552            evt.stopPropagation();
3553            $modalStack.dismiss(modal.key, 'backdrop click');
3554          }
3555        };
3556      }
3557    };
3558  }])
3559
3560  .directive('modalWindow', ['$timeout', function ($timeout) {
3561    return {
3562      restrict: 'EA',
3563      scope: {
3564        index: '@'
3565      },
3566      replace: true,
3567      transclude: true,
3568      templateUrl: 'template/modal/window.html',
3569      link: function (scope, element, attrs) {
3570        scope.windowClass = attrs.windowClass || '';
3571
3572        //trigger CSS transitions
3573        $timeout(function () {
3574          scope.animate = true;
3575        });
3576      }
3577    };
3578  }])
3579
3580  .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
3581    function ($document, $compile, $rootScope, $$stackedMap) {
3582
3583      var backdropjqLiteEl, backdropDomEl;
3584      var backdropScope = $rootScope.$new(true);
3585      var body = $document.find('body').eq(0);
3586      var openedWindows = $$stackedMap.createNew();
3587      var $modalStack = {};
3588
3589      function backdropIndex() {
3590        var topBackdropIndex = -1;
3591        var opened = openedWindows.keys();
3592        for (var i = 0; i < opened.length; i++) {
3593          if (openedWindows.get(opened[i]).value.backdrop) {
3594            topBackdropIndex = i;
3595          }
3596        }
3597        return topBackdropIndex;
3598      }
3599
3600      $rootScope.$watch(backdropIndex, function(newBackdropIndex){
3601        backdropScope.index = newBackdropIndex;
3602      });
3603
3604      function removeModalWindow(modalInstance) {
3605
3606        var modalWindow = openedWindows.get(modalInstance).value;
3607
3608        //clean up the stack
3609        openedWindows.remove(modalInstance);
3610
3611        //remove window DOM element
3612        modalWindow.modalDomEl.remove();
3613
3614        //remove backdrop if no longer needed
3615        if (backdropDomEl && backdropIndex() == -1) {
3616          backdropDomEl.remove();
3617          backdropDomEl = undefined;
3618        }
3619
3620        //destroy scope
3621        modalWindow.modalScope.$destroy();
3622      }
3623
3624      $document.bind('keydown', function (evt) {
3625        var modal;
3626
3627        if (evt.which === 27) {
3628          modal = openedWindows.top();
3629          if (modal && modal.value.keyboard) {
3630            $rootScope.$apply(function () {
3631              $modalStack.dismiss(modal.key);
3632            });
3633          }
3634        }
3635      });
3636
3637      $modalStack.open = function (modalInstance, modal) {
3638
3639        openedWindows.add(modalInstance, {
3640          deferred: modal.deferred,
3641          modalScope: modal.scope,
3642          backdrop: modal.backdrop,
3643          keyboard: modal.keyboard
3644        });
3645
3646        var angularDomEl = angular.element('<div modal-window></div>');
3647        angularDomEl.attr('window-class', modal.windowClass);
3648        angularDomEl.attr('index', openedWindows.length() - 1);
3649        angularDomEl.html(modal.content);
3650
3651        var modalDomEl = $compile(angularDomEl)(modal.scope);
3652        openedWindows.top().value.modalDomEl = modalDomEl;
3653        body.append(modalDomEl);
3654
3655        if (backdropIndex() >= 0 && !backdropDomEl) {
3656            backdropjqLiteEl = angular.element('<div modal-backdrop></div>');
3657            backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
3658            body.append(backdropDomEl);
3659        }
3660      };
3661
3662      $modalStack.close = function (modalInstance, result) {
3663        var modal = openedWindows.get(modalInstance);
3664        if (modal) {
3665          modal.value.deferred.resolve(result);
3666          removeModalWindow(modalInstance);
3667        }
3668      };
3669
3670      $modalStack.dismiss = function (modalInstance, reason) {
3671        var modalWindow = openedWindows.get(modalInstance).value;
3672        if (modalWindow) {
3673          modalWindow.deferred.reject(reason);
3674          removeModalWindow(modalInstance);
3675        }
3676      };
3677
3678      $modalStack.getTop = function () {
3679        return openedWindows.top();
3680      };
3681
3682      return $modalStack;
3683    }])
3684
3685  .provider('$modal', function () {
3686
3687    var $modalProvider = {
3688      options: {
3689        backdrop: true, //can be also false or 'static'
3690        keyboard: true
3691      },
3692      $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
3693        function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
3694
3695          var $modal = {};
3696
3697          function getTemplatePromise(options) {
3698            return options.template ? $q.when(options.template) :
3699              $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
3700                return result.data;
3701              });
3702          }
3703
3704          function getResolvePromises(resolves) {
3705            var promisesArr = [];
3706            angular.forEach(resolves, function (value, key) {
3707              if (angular.isFunction(value) || angular.isArray(value)) {
3708                promisesArr.push($q.when($injector.invoke(value)));
3709              }
3710            });
3711            return promisesArr;
3712          }
3713
3714          $modal.open = function (modalOptions) {
3715
3716            var modalResultDeferred = $q.defer();
3717            var modalOpenedDeferred = $q.defer();
3718
3719            //prepare an instance of a modal to be injected into controllers and returned to a caller
3720            var modalInstance = {
3721              result: modalResultDeferred.promise,
3722              opened: modalOpenedDeferred.promise,
3723              close: function (result) {
3724                $modalStack.close(modalInstance, result);
3725              },
3726              dismiss: function (reason) {
3727                $modalStack.dismiss(modalInstance, reason);
3728              }
3729            };
3730
3731            //merge and clean up options
3732            modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
3733            modalOptions.resolve = modalOptions.resolve || {};
3734
3735            //verify options
3736            if (!modalOptions.template && !modalOptions.templateUrl) {
3737              throw new Error('One of template or templateUrl options is required.');
3738            }
3739
3740            var templateAndResolvePromise =
3741              $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
3742
3743
3744            templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
3745
3746              var modalScope = (modalOptions.scope || $rootScope).$new();
3747              modalScope.$close = modalInstance.close;
3748              modalScope.$dismiss = modalInstance.dismiss;
3749
3750              var ctrlInstance, ctrlLocals = {};
3751              var resolveIter = 1;
3752
3753              //controllers
3754              if (modalOptions.controller) {
3755                ctrlLocals.$scope = modalScope;
3756                ctrlLocals.$modalInstance = modalInstance;
3757                angular.forEach(modalOptions.resolve, function (value, key) {
3758                  ctrlLocals[key] = tplAndVars[resolveIter++];
3759                });
3760
3761                ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
3762              }
3763
3764              $modalStack.open(modalInstance, {
3765                scope: modalScope,
3766                deferred: modalResultDeferred,
3767                content: tplAndVars[0],
3768                backdrop: modalOptions.backdrop,
3769                keyboard: modalOptions.keyboard,
3770                windowClass: modalOptions.windowClass
3771              });
3772
3773            }, function resolveError(reason) {
3774              modalResultDeferred.reject(reason);
3775            });
3776
3777            templateAndResolvePromise.then(function () {
3778              modalOpenedDeferred.resolve(true);
3779            }, function () {
3780              modalOpenedDeferred.reject(false);
3781            });
3782
3783            return modalInstance;
3784          };
3785
3786          return $modal;
3787        }]
3788    };
3789
3790    return $modalProvider;
3791  });
3792
3793angular.module('ui.bootstrap.pagination', [])
3794
3795.controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
3796  var self = this,
3797      setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
3798
3799  this.init = function(defaultItemsPerPage) {
3800    if ($attrs.itemsPerPage) {
3801      $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
3802        self.itemsPerPage = parseInt(value, 10);
3803        $scope.totalPages = self.calculateTotalPages();
3804      });
3805    } else {
3806      this.itemsPerPage = defaultItemsPerPage;
3807    }
3808  };
3809
3810  this.noPrevious = function() {
3811    return this.page === 1;
3812  };
3813  this.noNext = function() {
3814    return this.page === $scope.totalPages;
3815  };
3816
3817  this.isActive = function(page) {
3818    return this.page === page;
3819  };
3820
3821  this.calculateTotalPages = function() {
3822    var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
3823    return Math.max(totalPages || 0, 1);
3824  };
3825
3826  this.getAttributeValue = function(attribute, defaultValue, interpolate) {
3827    return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
3828  };
3829
3830  this.render = function() {
3831    this.page = parseInt($scope.page, 10) || 1;
3832    if (this.page > 0 && this.page <= $scope.totalPages) {
3833      $scope.pages = this.getPages(this.page, $scope.totalPages);
3834    }
3835  };
3836
3837  $scope.selectPage = function(page) {
3838    if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
3839      $scope.page = page;
3840      $scope.onSelectPage({ page: page });
3841    }
3842  };
3843
3844  $scope.$watch('page', function() {
3845    self.render();
3846  });
3847
3848  $scope.$watch('totalItems', function() {
3849    $scope.totalPages = self.calculateTotalPages();
3850  });
3851
3852  $scope.$watch('totalPages', function(value) {
3853    setNumPages($scope.$parent, value); // Readonly variable
3854
3855    if ( self.page > value ) {
3856      $scope.selectPage(value);
3857    } else {
3858      self.render();
3859    }
3860  });
3861}])
3862
3863.constant('paginationConfig', {
3864  itemsPerPage: 10,
3865  boundaryLinks: false,
3866  directionLinks: true,
3867  firstText: 'First',
3868  previousText: 'Previous',
3869  nextText: 'Next',
3870  lastText: 'Last',
3871  rotate: true
3872})
3873
3874.directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
3875  return {
3876    restrict: 'EA',
3877    scope: {
3878      page: '=',
3879      totalItems: '=',
3880      onSelectPage:' &'
3881    },
3882    controller: 'PaginationController',
3883    templateUrl: 'template/pagination/pagination.html',
3884    replace: true,
3885    link: function(scope, element, attrs, paginationCtrl) {
3886
3887      // Setup configuration parameters
3888      var maxSize,
3889      boundaryLinks  = paginationCtrl.getAttributeValue(attrs.boundaryLinks,  config.boun
3889daryLinks      ),
3890      directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks     ),
3891      firstText      = paginationCtrl.getAttributeValue(attrs.firstText,      config.firstText,     true),
3892      previousText   = paginationCtrl.getAttributeValue(attrs.previousText,   config.previousText,  true),
3893      nextText       = paginationCtrl.getAttributeValue(attrs.nextText,       config.nextText,      true),
3894      lastText       = paginationCtrl.getAttributeValue(attrs.lastText,       config.lastText,      true),
3895      rotate         = paginationCtrl.getAttributeValue(attrs.rotate,         config.rotate);
3896
3897      paginationCtrl.init(config.itemsPerPage);
3898
3899      if (attrs.maxSize) {
3900        scope.$parent.$watch($parse(attrs.maxSize), function(value) {
3901          maxSize = parseInt(value, 10);
3902          paginationCtrl.render();
3903        });
3904      }
3905
3906      // Create page object used in template
3907      function makePage(number, text, isActive, isDisabled) {
3908        return {
3909          number: number,
3910          text: text,
3911          active: isActive,
3912          disabled: isDisabled
3913        };
3914      }
3915
3916      paginationCtrl.getPages = function(currentPage, totalPages) {
3917        var pages = [];
3918
3919        // Default page limits
3920        var startPage = 1, endPage = totalPages;
3921        var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
3922
3923        // recompute if maxSize
3924        if ( isMaxSized ) {
3925          if ( rotate ) {
3926            // Current page is displayed in the middle of the visible ones
3927            startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
3928            endPage   = startPage + maxSize - 1;
3929
3930            // Adjust if limit is exceeded
3931            if (endPage > totalPages) {
3932              endPage   = totalPages;
3933              startPage = endPage - maxSize + 1;
3934            }
3935          } else {
3936            // Visible pages are paginated with maxSize
3937            startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
3938
3939            // Adjust last page if limit is exceeded
3940            endPage = Math.min(startPage + maxSize - 1, totalPages);
3941          }
3942        }
3943
3944        // Add page number links
3945        for (var number = startPage; number <= endPage; number++) {
3946          var page = makePage(number, number, paginationCtrl.isActive(number), false);
3947          pages.push(page);
3948        }
3949
3950        // Add links to move between page sets
3951        if ( isMaxSized && ! rotate ) {
3952          if ( startPage > 1 ) {
3953            var previousPageSet = makePage(startPage - 1, '...', false, false);
3954            pages.unshift(previousPageSet);
3955          }
3956
3957          if ( endPage < totalPages ) {
3958            var nextPageSet = makePage(endPage + 1, '...', false, false);
3959            pages.push(nextPageSet);
3960          }
3961        }
3962
3963        // Add previous & next links
3964        if (directionLinks) {
3965          var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
3966          pages.unshift(previousPage);
3967
3968          var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
3969          pages.push(nextPage);
3970        }
3971
3972        // Add first & last links
3973        if (boundaryLinks) {
3974          var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
3975          pages.unshift(firstPage);
3976
3977          var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
3978          pages.push(lastPage);
3979        }
3980
3981        return pages;
3982      };
3983    }
3984  };
3985}])
3986
3987.constant('pagerConfig', {
3988  itemsPerPage: 10,
3989  previousText: '« Previous',
3990  nextText: 'Next »',
3991  align: true
3992})
3993
3994.directive('pager', ['pagerConfig', function(config) {
3995  return {
3996    restrict: 'EA',
3997    scope: {
3998      page: '=',
3999      totalItems: '=',
4000      onSelectPage:' &'
4001    },
4002    controller: 'PaginationController',
4003    templateUrl: 'template/pagination/pager.html',
4004    replace: true,
4005    link: function(scope, element, attrs, paginationCtrl) {
4006
4007      // Setup configuration parameters
4008      var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
4009      nextText         = paginationCtrl.getAttributeValue(attrs.nextText,     config.nextText,     true),
4010      align            = paginationCtrl.getAttributeValue(attrs.align,        config.align);
4011
4012      paginationCtrl.init(config.itemsPerPage);
4013
4014      // Create page object used in template
4015      function makePage(number, text, isDisabled, isPrevious, isNext) {
4016        return {
4017          number: number,
4018          text: text,
4019          disabled: isDisabled,
4020          previous: ( align && isPrevious ),
4021          next: ( align && isNext )
4022        };
4023      }
4024
4025      paginationCtrl.getPages = function(currentPage) {
4026        return [
4027          makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
4028          makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
4029        ];
4030      };
4031    }
4032  };
4033}]);
4034
4035/**
4036 * The following features are still outstanding: animation as a
4037 * function, placement as a function, inside, support for more triggers than
4038 * just mouse enter/leave, html tooltips, and selector delegation.
4039 */
4040angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
4041
4042/**
4043 * The $tooltip service creates tooltip- and popover-like directives as well as
4044 * houses global options for them.
4045 */
4046.provider( '$tooltip', function () {
4047  // The default options tooltip and popover.
4048  var defaultOptions = {
4049    placement: 'top',
4050    animation: true,
4051    popupDelay: 0
4052  };
4053
4054  // Default hide triggers for each show trigger
4055  var triggerMap = {
4056    'mouseenter': 'mouseleave',
4057    'click': 'click',
4058    'focus': 'blur'
4059  };
4060
4061  // The options specified to the provider globally.
4062  var globalOptions = {};
4063  
4064  /**
4065   * `options({})` allows global configuration of all tooltips in the
4066   * application.
4067   *
4068   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
4069   *     // place tooltips left instead of top by default
4070   *     $tooltipProvider.options( { placement: 'left' } );
4071   *   });
4072   */
4073	this.options = function( value ) {
4074		angular.extend( globalOptions, value );
4075	};
4076
4077  /**
4078   * This allows you to extend the set of trigger mappings available. E.g.:
4079   *
4080   *   $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
4081   */
4082  this.setTriggers = function setTriggers ( triggers ) {
4083    angular.extend( triggerMap, triggers );
4084  };
4085
4086  /**
4087   * This is a helper function for translating camel-case to snake-case.
4088   */
4089  function snake_case(name){
4090    var regexp = /[A-Z]/g;
4091    var separator = '-';
4092    return name.replace(regexp, function(letter, pos) {
4093      return (pos ? separator : '') + letter.toLowerCase();
4094    });
4095  }
4096
4097  /**
4098   * Returns the actual instance of the $tooltip service.
4099   * TODO support multiple triggers
4100   */
4101  this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
4102    return function $tooltip ( type, prefix, defaultTriggerShow ) {
4103      var options = angular.extend( {}, defaultOptions, globalOptions );
4104
4105      /**
4106       * Returns an object of show and hide triggers.
4107       *
4108       * If a trigger is supplied,
4109       * it is used to show the tooltip; otherwise, it will use the `trigger`
4110       * option passed to the `$tooltipProvider.options` method; else it will
4111       * default to the trigger supplied to this directive factory.
4112       *
4113       * The hide trigger is based on the show trigger. If the `trigger` option
4114       * was passed to the `$tooltipProvider.options` method, it will use the
4115       * mapped trigger from `triggerMap` or the passed trigger if the map is
4116       * undefined; otherwise, it uses the `triggerMap` value of the show
4117       * trigger; else it will just use the show trigger.
4118       */
4119      function getTriggers ( trigger ) {
4120        var show = trigger || options.trigger || defaultTriggerShow;
4121        var hide = triggerMap[show] || show;
4122        return {
4123          show: show,
4124          hide: hide
4125        };
4126      }
4127
4128      var directiveName = snake_case( type );
4129
4130      var startSym = $interpolate.startSymbol();
4131      var endSym = $interpolate.endSymbol();
4132      var template = 
4133        '<'+ directiveName +'-popup '+
4134          'title="'+startSym+'tt_title'+endSym+'" '+
4135          'content="'+startSym+'tt_content'+endSym+'" '+
4136          'placement="'+startSym+'tt_placement'+endSym+'" '+
4137          'animation="tt_animation" '+
4138          'is-open="tt_isOpen"'+
4139          '>'+
4140        '</'+ directiveName +'-popup>';
4141
4142      return {
4143        restrict: 'EA',
4144        scope: true,
4145        link: function link ( scope, element, attrs ) {
4146          var tooltip = $compile( template )( scope );
4147          var transitionTimeout;
4148          var popupTimeout;
4149          var $body = $document.find( 'body' );
4150          var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
4151          var triggers = getTriggers( undefined );
4152          var hasRegisteredTriggers = false;
4153          var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
4154
4155          // By default, the tooltip is not open.
4156          // TODO add ability to start tooltip opened
4157          scope.tt_isOpen = false;
4158
4159          function toggleTooltipBind () {
4160            if ( ! scope.tt_isOpen ) {
4161              showTooltipBind();
4162            } else {
4163              hideTooltipBind();
4164            }
4165          }
4166          
4167          // Show the tooltip with delay if specified, otherwise show it immediately
4168          function showTooltipBind() {
4169            if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
4170              return;
4171            }
4172            if ( scope.tt_popupDelay ) {
4173              popupTimeout = $timeout( show, scope.tt_popupDelay );
4174            } else {
4175              scope.$apply( show );
4176            }
4177          }
4178
4179          function hideTooltipBind () {
4180            scope.$apply(function () {
4181              hide();
4182            });
4183          }
4184          
4185          // Show the tooltip popup element.
4186          function show() {
4187            var position,
4188                ttWidth,
4189                ttHeight,
4190                ttPosition;
4191
4192            // Don't show empty tooltips.
4193            if ( ! scope.tt_content ) {
4194              return;
4195            }
4196
4197            // If there is a pending remove transition, we must cancel it, lest the
4198            // tooltip be mysteriously removed.
4199            if ( transitionTimeout ) {
4200              $timeout.cancel( transitionTimeout );
4201            }
4202            
4203            // Set the initial positioning.
4204            tooltip.css({ top: 0, left: 0, display: 'block' });
4205            
4206            // Now we add it to the DOM because need some info about it. But it's not 
4207            // visible yet anyway.
4208            if ( appendToBody ) {
4209                $body.append( tooltip );
4210            } else {
4211              element.after( tooltip );
4212            }
4213
4214            // Get the position of the directive element.
4215            position = appendToBody ? $position.offset( element ) : $position.position( element );
4216
4217            // Get the height and width of the tooltip so we can center it.
4218            ttWidth = tooltip.prop( 'offsetWidth' );
4219            ttHeight = tooltip.prop( 'offsetHeight' );
4220            
4221            // Calculate the tooltip's top and left coordinates to center it with
4222            // this directive.
4223            switch ( scope.tt_placement ) {
4224              case 'right':
4225                ttPosition = {
4226                  top: position.top + position.height / 2 - ttHeight / 2,
4227                  left: position.left + position.width
4228                };
4229                break;
4230              case 'bottom':
4231                ttPosition = {
4232                  top: position.top + position.height,
4233                  left: position.left + position.width / 2 - ttWidth / 2
4234                };
4235                break;
4236              case 'left':
4237                ttPosition = {
4238                  top: position.top + position.height / 2 - ttHeight / 2,
4239                  left: position.left - ttWidth
4240                };
4241                break;
4242              default:
4243                ttPosition = {
4244                  top: position.top - ttHeight,
4245                  left: position.left + position.width / 2 - ttWidth / 2
4246                };
4247                break;
4248            }
4249
4250            ttPosition.top += 'px';
4251            ttPosition.left += 'px';
4252
4253            // Now set the calculated positioning.
4254            tooltip.css( ttPosition );
4255              
4256            // And show the tooltip.
4257            scope.tt_isOpen = true;
4258          }
4259          
4260          // Hide the tooltip popup element.
4261          function hide() {
4262            // First things first: we don't show it anymore.
4263            scope.tt_isOpen = false;
4264
4265            //if tooltip is going to be shown after delay, we must cancel this
4266            $timeout.cancel( popupTimeout );
4267            
4268            // And now we remove it from the DOM. However, if we have animation, we 
4269            // need to wait for it to expire beforehand.
4270            // FIXME: this is a placeholder for a port of the transitions library.
4271            if ( scope.tt_animation ) {
4272              transitionTimeout = $timeout(function () {
4273                tooltip.remove();
4274              }, 500);
4275            } else {
4276              tooltip.remove();
4277            }
4278          }
4279
4280          /**
4281           * Observe the relevant attributes.
4282           */
4283          attrs.$observe( type, function ( val ) {
4284            if (val) {
4285              scope.tt_content = val;
4286            } else {
4287              if ( scope.tt_isOpen ) {
4288                hide();
4289              }
4290            }
4291          });
4292
4293          attrs.$observe( prefix+'Title', function ( val ) {
4294            scope.tt_title = val;
4295          });
4296
4297          attrs.$observe( prefix+'Placement', function ( val ) {
4298            scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
4299          });
4300
4301          attrs.$observe(prefix + 'Animation', function (val) {
4302            scope.tt_animation = angular.isDefined(val) ? !!val : options.animation;
4303          });
4304
4305          attrs.$observe( prefix+'PopupDelay', function ( val ) {
4306            var delay = parseInt( val, 10 );
4307            scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
4308          });
4309
4310          attrs.$observe( prefix+'Trigger', function ( val ) {
4311
4312            if (hasRegisteredTriggers) {
4313              element.unbind( triggers.show, showTooltipBind );
4314              element.unbind( triggers.hide, hideTooltipBind );
4315            }
4316
4317            triggers = getTriggers( val );
4318
4319            if ( triggers.show === triggers.hide ) {
4320              element.bind( triggers.show, toggleTooltipBind );
4321            } else {
4322              element.bind( triggers.show, showTooltipBind );
4323              element.bind( triggers.hide, hideTooltipBind );
4324            }
4325
4326            hasRegisteredTriggers = true;
4327          });
4328
4329          attrs.$observe( prefix+'AppendToBody', function ( val ) {
4330            appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
4331          });
4332
4333          // if a tooltip is attached to <body> we need to remove it on
4334          // location change as its parent scope will probably not be destroyed
4335          // by the change.
4336          if ( appendToBody ) {
4337            scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
4338            if ( scope.tt_isOpen ) {
4339              hide();
4340            }
4341          });
4342          }
4343
4344          // Make sure tooltip is destroyed and removed.
4345          scope.$on('$destroy', function onDestroyTooltip() {
4346            $timeout.cancel( popupTimeout );
4347            tooltip.remove();
4348            tooltip.unbind();
4349            tooltip = null;
4350            $body = null;
4351          });
4352        }
4353      };
4354    };
4355  }];
4356})
4357
4358.directive( 'tooltipPopup', function () {
4359  return {
4360    restrict: 'E',
4361    replace: true,
4362    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
4363    templateUrl: 'template/tooltip/tooltip-popup.html'
4364  };
4365})
4366
4367.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
4368  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
4369}])
4370
4371.directive( 'tooltipHtmlUnsafePopup', function () {
4372  return {
4373    restrict: 'E',
4374    replace: true,
4375    scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
4376    templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
4377  };
4378})
4379
4380.directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
4381  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
4382}]);
4383
4384/**
4385 * The following features are still outstanding: popup delay, animation as a
4386 * function, placement as a function, inside, support for more triggers than
4387 * just mouse enter/leave, html popovers, and selector delegatation.
4388 */
4389angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
4390.directive( 'popoverPopup', function () {
4391  return {
4392    restrict: 'EA',
4393    replace: true,
4394    scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
4395    templateUrl: 'template/popover/popover.html'
4396  };
4397})
4398.directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
4399  return $tooltip( 'popover', 'popover', 'click' );
4400}]);
4401
4402
4403angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
4404
4405.constant('progressConfig', {
4406  animate: true,
4407  autoType: false,
4408  stackedTypes: ['success', 'info', 'warning', 'danger']
4409})
4410
4411.controller('ProgressBarController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
4412
4413    // Whether bar transitions should be animated
4414    var animate = angular.isDefined($attrs.animate) ? $scope.$eval($attrs.animate) : progressConfig.animate;
4415    var autoType = angular.isDefined($attrs.autoType) ? $scope.$eval($attrs.autoType) : progressConfig.autoType;
4416    var stackedTypes = angular.isDefined($attrs.stackedTypes) ? $scope.$eval('[' + $attrs.stackedTypes + ']') : progressConfig.stackedTypes;
4417
4418    // Create bar object
4419    this.makeBar = function(newBar, oldBar, index) {
4420        var newValue = (angular.isObject(newBar)) ? newBar.value : (newB
4420ar || 0);
4421        var oldValue =  (angular.isObject(oldBar)) ? oldBar.value : (oldBar || 0);
4422        var type = (angular.isObject(newBar) && angular.isDefined(newBar.type)) ? newBar.type : (autoType) ? getStackedType(index || 0) : null;
4423
4424        return {
4425            from: oldValue,
4426            to: newValue,
4427            type: type,
4428            animate: animate
4429        };
4430    };
4431
4432    function getStackedType(index) {
4433        return stackedTypes[index];
4434    }
4435
4436    this.addBar = function(bar) {
4437        $scope.bars.push(bar);
4438        $scope.totalPercent += bar.to;
4439    };
4440
4441    this.clearBars = function() {
4442        $scope.bars = [];
4443        $scope.totalPercent = 0;
4444    };
4445    this.clearBars();
4446}])
4447
4448.directive('progress', function() {
4449    return {
4450        restrict: 'EA',
4451        replace: true,
4452        controller: 'ProgressBarController',
4453        scope: {
4454            value: '=percent',
4455            onFull: '&',
4456            onEmpty: '&'
4457        },
4458        templateUrl: 'template/progressbar/progress.html',
4459        link: function(scope, element, attrs, controller) {
4460            scope.$watch('value', function(newValue, oldValue) {
4461                controller.clearBars();
4462
4463                if (angular.isArray(newValue)) {
4464                    // Stacked progress bar
4465                    for (var i=0, n=newValue.length; i < n; i++) {
4466                        controller.addBar(controller.makeBar(newValue[i], oldValue[i], i));
4467                    }
4468                } else {
4469                    // Simple bar
4470                    controller.addBar(controller.makeBar(newValue, oldValue));
4471                }
4472            }, true);
4473
4474            // Total percent listeners
4475            scope.$watch('totalPercent', function(value) {
4476              if (value >= 100) {
4477                scope.onFull();
4478              } else if (value <= 0) {
4479                scope.onEmpty();
4480              }
4481            }, true);
4482        }
4483    };
4484})
4485
4486.directive('progressbar', ['$transition', function($transition) {
4487    return {
4488        restrict: 'EA',
4489        replace: true,
4490        scope: {
4491            width: '=',
4492            old: '=',
4493            type: '=',
4494            animate: '='
4495        },
4496        templateUrl: 'template/progressbar/bar.html',
4497        link: function(scope, element) {
4498            scope.$watch('width', function(value) {
4499                if (scope.animate) {
4500                    element.css('width', scope.old + '%');
4501                    $transition(element, {width: value + '%'});
4502                } else {
4503                    element.css('width', value + '%');
4504                }
4505            });
4506        }
4507    };
4508}]);
4509angular.module('ui.bootstrap.rating', [])
4510
4511.constant('ratingConfig', {
4512  max: 5,
4513  stateOn: null,
4514  stateOff: null
4515})
4516
4517.controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
4518
4519  this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
4520  this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
4521  this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
4522
4523  this.createRateObjects = function(states) {
4524    var defaultOptions = {
4525      stateOn: this.stateOn,
4526      stateOff: this.stateOff
4527    };
4528
4529    for (var i = 0, n = states.length; i < n; i++) {
4530      states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
4531    }
4532    return states;
4533  };
4534
4535  // Get objects used in template
4536  $scope.range = angular.isDefined($attrs.ratingStates) ?  this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
4537
4538  $scope.rate = function(value) {
4539    if ( $scope.readonly || $scope.value === value) {
4540      return;
4541    }
4542
4543    $scope.value = value;
4544  };
4545
4546  $scope.enter = function(value) {
4547    if ( ! $scope.readonly ) {
4548      $scope.val = value;
4549    }
4550    $scope.onHover({value: value});
4551  };
4552
4553  $scope.reset = function() {
4554    $scope.val = angular.copy($scope.value);
4555    $scope.onLeave();
4556  };
4557
4558  $scope.$watch('value', function(value) {
4559    $scope.val = value;
4560  });
4561
4562  $scope.readonly = false;
4563  if ($attrs.readonly) {
4564    $scope.$parent.$watch($parse($attrs.readonly), function(value) {
4565      $scope.readonly = !!value;
4566    });
4567  }
4568}])
4569
4570.directive('rating', function() {
4571  return {
4572    restrict: 'EA',
4573    scope: {
4574      value: '=',
4575      onHover: '&',
4576      onLeave: '&'
4577    },
4578    controller: 'RatingController',
4579    templateUrl: 'template/rating/rating.html',
4580    replace: true
4581  };
4582});
4583
4584/**
4585 * @ngdoc overview
4586 * @name ui.bootstrap.tabs
4587 *
4588 * @description
4589 * AngularJS version of the tabs directive.
4590 */
4591
4592angular.module('ui.bootstrap.tabs', [])
4593
4594.directive('tabs', function() {
4595  return function() {
4596    throw new Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md");
4597  };
4598})
4599
4600.controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
4601  var ctrl = this,
4602      tabs = ctrl.tabs = $scope.tabs = [];
4603
4604  ctrl.select = function(tab) {
4605    angular.forEach(tabs, function(tab) {
4606      tab.active = false;
4607    });
4608    tab.active = true;
4609  };
4610
4611  ctrl.addTab = function addTab(tab) {
4612    tabs.push(tab);
4613    if (tabs.length === 1 || tab.active) {
4614      ctrl.select(tab);
4615    }
4616  };
4617
4618  ctrl.removeTab = function removeTab(tab) {
4619    var index = tabs.indexOf(tab);
4620    //Select a new tab if the tab to be removed is selected
4621    if (tab.active && tabs.length > 1) {
4622      //If this is the last tab, select the previous tab. else, the next tab.
4623      var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
4624      ctrl.select(tabs[newActiveIndex]);
4625    }
4626    tabs.splice(index, 1);
4627  };
4628}])
4629
4630/**
4631 * @ngdoc directive
4632 * @name ui.bootstrap.tabs.directive:tabset
4633 * @restrict EA
4634 *
4635 * @description
4636 * Tabset is the outer container for the tabs directive
4637 *
4638 * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
4639 * @param {string=} direction  What direction the tabs should be rendered. Available:
4640 * 'right', 'left', 'below'.
4641 *
4642 * @example
4643<example module="ui.bootstrap">
4644  <file name="index.html">
4645    <tabset>
4646      <tab heading="Vertical Tab 1"><b>First</b> Content!</tab>
4647      <tab heading="Vertical Tab 2"><i>Second</i> Content!</tab>
4648    </tabset>
4649    <hr />
4650    <tabset vertical="true">
4651      <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
4652      <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
4653    </tabset>
4654  </file>
4655</example>
4656 */
4657.directive('tabset', function() {
4658  return {
4659    restrict: 'EA',
4660    transclude: true,
4661    replace: true,
4662    require: '^tabset',
4663    scope: {},
4664    controller: 'TabsetController',
4665    templateUrl: 'template/tabs/tabset.html',
4666    compile: function(elm, attrs, transclude) {
4667      return function(scope, element, attrs, tabsetCtrl) {
4668        scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
4669        scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
4670        scope.direction = angular.isDefined(attrs.direction) ? scope.$parent.$eval(attrs.direction) : 'top';
4671        scope.tabsAbove = (scope.direction != 'below');
4672        tabsetCtrl.$scope = scope;
4673        tabsetCtrl.$transcludeFn = transclude;
4674      };
4675    }
4676  };
4677})
4678
4679/**
4680 * @ngdoc directive
4681 * @name ui.bootstrap.tabs.directive:tab
4682 * @restrict EA
4683 *
4684 * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
4685 * @param {string=} select An expression to evaluate when the tab is selected.
4686 * @param {boolean=} active A binding, telling whether or not this tab is selected.
4687 * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
4688 *
4689 * @description
4690 * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
4691 *
4692 * @example
4693<example module="ui.bootstrap">
4694  <file name="index.html">
4695    <div ng-controller="TabsDemoCtrl">
4696      <button class="btn btn-small" ng-click="items[0].active = true">
4697        Select item 1, using active binding
4698      </button>
4699      <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
4700        Enable/disable item 2, using disabled binding
4701      </button>
4702      <br />
4703      <tabset>
4704        <tab heading="Tab 1">First Tab</tab>
4705        <tab select="alertMe()">
4706          <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
4707          Second Tab, with alert callback and html heading!
4708        </tab>
4709        <tab ng-repeat="item in items"
4710          heading="{{item.title}}"
4711          disabled="item.disabled"
4712          active="item.active">
4713          {{item.content}}
4714        </tab>
4715      </tabset>
4716    </div>
4717  </file>
4718  <file name="script.js">
4719    function TabsDemoCtrl($scope) {
4720      $scope.items = [
4721        { title:"Dynamic Title 1", content:"Dynamic Item 0" },
4722        { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
4723      ];
4724
4725      $scope.alertMe = function() {
4726        setTimeout(function() {
4727          alert("You've selected the alert tab!");
4728        });
4729      };
4730    };
4731  </file>
4732</example>
4733 */
4734
4735/**
4736 * @ngdoc directive
4737 * @name ui.bootstrap.tabs.directive:tabHeading
4738 * @restrict EA
4739 *
4740 * @description
4741 * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
4742 *
4743 * @example
4744<example module="ui.bootstrap">
4745  <file name="index.html">
4746    <tabset>
4747      <tab>
4748        <tab-heading><b>HTML</b> in my titles?!</tab-heading>
4749        And some content, too!
4750      </tab>
4751      <tab>
4752        <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
4753        That's right.
4754      </tab>
4755    </tabset>
4756  </file>
4757</example>
4758 */
4759.directive('tab', ['$parse', function($parse) {
4760  return {
4761    require: '^tabset',
4762    restrict: 'EA',
4763    replace: true,
4764    templateUrl: 'template/tabs/tab.html',
4765    transclude: true,
4766    scope: {
4767      heading: '@',
4768      onSelect: '&select', //This callback is called in contentHeadingTransclude
4769                          //once it inserts the tab's content into the dom
4770      onDeselect: '&deselect'
4771    },
4772    controller: function() {
4773      //Empty controller so other directives can require being 'under' a tab
4774    },
4775    compile: function(elm, attrs, transclude) {
4776      return function postLink(scope, elm, attrs, tabsetCtrl) {
4777        var getActive, setActive;
4778        if (attrs.active) {
4779          getActive = $parse(attrs.active);
4780          setActive = getActive.assign;
4781          scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
4782            // Avoid re-initializing scope.active as it is already initialized
4783            // below. (watcher is called async during init with value ===
4784            // oldVal)
4785            if (value !== oldVal) {
4786              scope.active = !!value;
4787            }
4788          });
4789          scope.active = getActive(scope.$parent);
4790        } else {
4791          setActive = getActive = angular.noop;
4792        }
4793
4794        scope.$watch('active', function(active) {
4795          // Note this watcher also initializes and assigns scope.active to the
4796          // attrs.active expression.
4797          setActive(scope.$parent, active);
4798          if (active) {
4799            tabsetCtrl.select(scope);
4800            scope.onSelect();
4801          } else {
4802            scope.onDeselect();
4803          }
4804        });
4805
4806        scope.disabled = false;
4807        if ( attrs.disabled ) {
4808          scope.$parent.$watch($parse(attrs.disabled), function(value) {
4809            scope.disabled = !! value;
4810          });
4811        }
4812
4813        scope.select = function() {
4814          if ( ! scope.disabled ) {
4815            scope.active = true;
4816          }
4817        };
4818
4819        tabsetCtrl.addTab(scope);
4820        scope.$on('$destroy', function() {
4821          tabsetCtrl.removeTab(scope);
4822        });
4823
4824
4825        //We need to transclude later, once the content container is ready.
4826        //when this link happens, we're inside a tab heading.
4827        scope.$transcludeFn = transclude;
4828      };
4829    }
4830  };
4831}])
4832
4833.directive('tabHeadingTransclude', [function() {
4834  return {
4835    restrict: 'A',
4836    require: '^tab',
4837    link: function(scope, elm, attrs, tabCtrl) {
4838      scope.$watch('headingElement', function updateHeadingElement(heading) {
4839        if (heading) {
4840          elm.html('');
4841          elm.append(heading);
4842        }
4843      });
4844    }
4845  };
4846}])
4847
4848.directive('tabContentTransclude', function() {
4849  return {
4850    restrict: 'A',
4851    require: '^tabset',
4852    link: function(scope, elm, attrs) {
4853      var tab = scope.$eval(attrs.tabContentTransclude);
4854
4855      //Now our tab is ready to be transcluded: both the tab heading area
4856      //and the tab content area are loaded.  Transclude 'em both.
4857      tab.$transcludeFn(tab.$parent, function(contents) {
4858        angular.forEach(contents, function(node) {
4859          if (isTabHeading(node)) {
4860            //Let tabHeadingTransclude know.
4861            tab.headingElement = node;
4862          } else {
4863            elm.append(node);
4864          }
4865        });
4866      });
4867    }
4868  };
4869  function isTabHeading(node) {
4870    return node.tagName &&  (
4871      node.hasAttribute('tab-heading') ||
4872      node.hasAttribute('data-tab-heading') ||
4873      node.tagName.toLowerCase() === 'tab-heading' ||
4874      node.tagName.toLowerCase() === 'data-tab-heading'
4875    );
4876  }
4877})
4878
4879.directive('tabsetTitles', function() {
4880  return {
4881    restrict: 'A',
4882    require: '^tabset',
4883    templateUrl: 'template/tabs/tabset-titles.html',
4884    replace: true,
4885    link: function(scope, elm, attrs, tabsetCtrl) {
4886      if (!scope.$eval(attrs.tabsetTitles)) {
4887        elm.remove();
4888      } else {
4889        //now that tabs location has been decided, transclude the tab titles in
4890        tabsetCtrl.$transcludeFn(tabsetCtrl.$scope.$parent, function(node) {
4891          elm.append(node);
4892        });
4893      }
4894    }
4895  };
4896});
4897
4898angular.module('ui.bootstrap.timepicker', [])
4899
4900.constant('timepickerConfig', {
4901  hourStep: 1,
4902  minuteStep: 1,
4903  showMeridian: true,
4904  meridians: ['AM', 'PM'],
4905  readonlyInput: false,
4906  mousewheel: true
4907})
4908
4909.directive('timepicker', ['$parse', '$log', 'timepickerConfig', function ($parse, $log, timepickerConfig) {
4910  return {
4911    restrict: 'EA',
4912    require:'?^ngModel',
4913    replace: true,
4914    scope: {},
4915    templateUrl: 'template/timepicker/timepicker.html',
4916    link: function(scope, element, attrs, ngModel) {
4917      if ( !ngModel ) {
4918        return; // do nothing if no ng-model
4919      }
4920
4921      var selected = new Date(), meridians = timepickerConfig.meridians;
4922
4923      var hourStep = timepickerConfig.hourStep;
4924      if (attrs.hourStep) {
4925        scope.$parent.$watch($parse(attrs.hourStep), function(value) {
4926          hourStep = parseInt(value, 10);
4927        });
4928      }
4929
4930      var minuteStep = timepickerConfig.minuteStep;
4931      if (attrs.minuteStep) {
4932        scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
4933          minuteStep = parseInt(value, 10);
4934        });
4935      }
4936
4937      // 12H / 24H mode
4938      scope.showMeridian = timepickerConfig.showMeridian;
4939      if (attrs.showMeridian) {
4940        scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
4941          scope.showMeridian = !!value;
4942
4943          if ( ngModel.$error.time ) {
4944            // Evaluate from template
4945            var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
4946            if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
4947              selected.setHours( hours );
4948              refresh();
4949            }
4950          } else {
4951            updateTemplate();
4952          }
4953        });
4954      }
4955
4956      // Get scope.hours in 24H mode if valid
4957      function getHoursFromTemplate ( ) {
4958        var hours = parseInt( scope.hours, 10 );
4959        var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
4960        if ( !valid ) {
4961          return undefined;
4962        }
4963
4964        if ( scope.showMeridian ) {
4965          if ( hours === 12 ) {
4966            hours = 0;
4967          }
4968          if ( scope.meridian === meridians[1] ) {
4969            hours = hours + 12;
4970          }
4971        }
4972        return hours;
4973      }
4974
4975      function getMinutesFromTemplate() {
4976        var minutes = parseInt(scope.minutes, 10);
4977        return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
4978      }
4979
4980      function pad( value ) {
4981        return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
4982      }
4983
4984      // Input elements
4985      var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
4986
4987      // Respond on mousewheel spin
4988      var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
4989      if ( mousewheel ) {
4990
4991        var isScrollingUp = function(e) {
4992          if (e.originalEvent) {
4993            e = e.originalEvent;
4994          }
4995          //pick correct delta variable depending on event
4996          var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
4997          return (e.detail || delta > 0);
4998        };
4999
5000        hoursInputEl.bind('mousewheel wheel', function(e) {
5001          scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
5002          e.preventDefault();
5003        });
5004
5005        minutesInputEl.bind('mousewheel wheel', function(e) {
5006          scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
5007          e.preventDefault();
5008        });
5009      }
5010
5011      scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
5012      if ( ! scope.readonlyInput ) {
5013
5014        var invalidate = function(invalidHours, invalidMinutes) {
5015          ngModel.$setViewValue( null );
5016          ngModel.$setValidity('time', false);
5017          if (angular.isDefined(invalidHours)) {
5018            scope.invalidHours = invalidHours;
5019          }
5020          if (angular.isDefined(invalidMinutes)) {
5021            scope.invalidMinutes = invalidMinutes;
5022          }
5023        };
5024
5025        scope.updateHours = function() {
5026          var hours = getHoursFromTemplate();
5027
5028          if ( angular.isDefined(hours) ) {
5029            selected.setHours( hours );
5030            refresh( 'h' );
5031          } else {
5032            invalidate(true);
5033          }
5034        };
5035
5036        hoursInputEl.bind('blur', function(e) {
5037          if ( !scope.validHours && scope.hours < 10) {
5038            scope.$apply( function() {
5039              scope.hours = pad( scope.hours );
5040            });
5041          }
5042        });
5043
5044        scope.updateMinutes = function() {
5045          var minutes = getMinutesFromTemplate();
5046
5047          if ( angular.isDefined(minutes) ) {
5048            selected.setMinutes( minutes );
5049            refresh( 'm' );
5050          } else {
5051            invalidate(undefined, true);
5052          }
5053        };
5054
5055        minutesInputEl.bind('blur', function(e) {
5056          if ( !scope.invalidMinutes && scope.minutes < 10 ) {
5057            scope.$apply( function() {
5058              scope.minutes = pad( scope.minutes );
5059            });
5060          }
5061        });
5062      } else {
5063        scope.updateHours = angular.noop;
5064        scope.updateMinutes = angular.noop;
5065      }
5066
5067      ngModel.$render = function() {
5068        var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
5069
5070        if ( isNaN(date) ) {
5071          ngModel.$setValidity('time', false);
5072          $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
5073        } else {
5074          if ( date ) {
5075            selected = date;
5076          }
5077          makeValid();
5078          updateTemplate();
5079        }
5080      };
5081
5082      // Call internally when we know that model is valid.
5083      function refresh( keyboardChange ) {
5084        makeValid();
5085        ngModel.$setViewValue( new Date(selected) );
5086        updateTemplate( keyboardChange );
5087      }
5088
5089      function makeValid() {
5090        ngModel.$setValidity('time', true);
5091        scope.invalidHours = false;
5092        scope.invalidMinutes = false;
5093      }
5094
5095      function updateTemplate( keyboardChange ) {
5096        var hours = selected.getHours(), minutes = selected.getMinutes();
5097
5098        if ( scope.showMeridian ) {
5099          hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
5100        }
5101        scope.hours =  keyboardChange === 'h' ? hours : pad(hours);
5102        scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
5103        scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
5104      }
5105
5106      function addMinutes( minutes ) {
5107        var dt = new Date( selected.getTime() + minutes * 60000 );
5108        selected.setHours( dt.getHours(), dt.getMinutes() );
5109        refresh();
5110      }
5111
5112      scope.incrementHours = function() {
5113        addMinutes( hourStep * 60 );
5114      };
5115      scope.decrementHours = function() {
5116        addMinutes( - hourStep * 60 );
5117      };
5118      scope.incrementMinutes = function() {
5119        addMinutes( minuteStep );
5120      };
5121      scope.decrementMinutes = function() {
5122        addMinutes( - minuteStep );
5123      };
5124      scope.toggleMeridian = function() {
5125        addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
5126      };
5127    }
5128  };
5129}]);
5130
5131angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
5132
5133/**
5134 * A helper service that can parse typeahead's syntax (string provided by users)
5135 * Extracted to a separate service for ease of unit testing
5136 */
5137  .factory('typeaheadParser', ['$parse', function ($parse) {
5138
5139  //                      00000111000000000000022200000000000000003333333333333330000000000044000
5140  var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
5141
5142  return {
5143    parse:function (input) {
5144
5145      var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
5146      if (!match) {
5147        throw new Error(
5148          "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
5149            " but got '" + input + "'.");
5150      }
5151
5152      return {
5153        itemName:match[3],
5154        source:$parse(match[4]),
5155        viewMapper:$parse(match[2] || match[1]),
5156        modelMapper:$parse(match[1])
5157      };
5158    }
5159  };
5160}])
5161
5162  .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
5163    function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
5164
5165  var HOT_KEYS = [9, 13, 27, 38, 40];
5166
5167  return {
5168    require:'ngModel',
5169    link:function (originalScope, element, attrs, modelCtrl) {
5170
5171      //SUPPORTED ATTRIBUTES (OPTIONS)
5172
5173      //minimal no of characters that needs to be entered before typeahead kicks-in
5174      var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
5175
5176      //minimal wait time after last character typed before typehead kicks-in
5177      var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
5178
5179      //should it restrict model values to the ones selected from the popup only?
5180      var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
5181
5182      //binding to a variable that indicates if matches are being retrieved asynchronously
5183      var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
5184
5185      //a callback executed when a match is selected
5186      var onSelectCallback = $parse(attrs.typeaheadOnSelect);
5187
5188      var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
5189
5190      //INTERNAL VARIABLES
5191
5192      //model setter executed upon match selection
5193      var $setModelValue = $parse(attrs.ngModel).assign;
5194
5195      //expressions used by typeahead
5196      var parserResult = typeaheadParser.parse(attrs.typeahead);
5197
5198      var hasFocus;
5199
5200      //pop-up element used to display matches
5201      var popUpEl = angular.element('<div typeahead-popup></div>');
5202      popUpEl.attr({
5203        matches: 'matches',
5204        active: 'activeIdx',
5205        select: 'select(activeIdx)',
5206        query: 'query',
5207        position: 'position'
5208      });
5209      //custom item template
5210      if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
5211        popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
5212      }
5213
5214      //create a child scope for the typeahead directive so we are not polluting original scope
5215      //with typeahead-specific data (matches, query etc.)
5216      var scope = originalScope.$new();
5217      originalScope.$on('$destroy', function(){
5218        scope.$destroy();
5219      });
5220
5221      var resetMatches = function() {
5222        scope.matches = [];
5223        scope.activeIdx = -1;
5224      };
5225
5226      var getMatchesAsync = function(inputValue) {
5227
5228        var locals = {$viewValue: inputValue};
5229        isLoadingSetter(originalScope, true);
5230        $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
5231
5232          //it might happen that several async queries were in progress if a user were typing fast
5233          //but we are interested only in responses that correspond to the current view value
5234          if (inputValue === modelCtrl.$viewValue && hasFocus) {
5235            if (matches.length > 0) {
5236
5237              scope.activeIdx = 0;
5238              scope.matches.length = 0;
5239
5240              //transform labels
5241              for(var i=0; i<matches.length; i++) {
5242                locals[parserResult.itemName] = matches[i];
5243                scope.matches.push({
5244                  label: parserResult.viewMapper(scope, locals),
5245                  model: matches[i]
5246                });
5247              }
5248
5249              scope.query = inputValue;
5250              //position pop-up with matches - we need to re-calculate its position each time we are opening a window
5251              //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
5252              //due to other elements being rendered
5253              scope.position = $position.position(element);
5254              scope.position.top = scope.position.top + element.prop('offsetHeight');
5255
5256            } else {
5257              resetMatches();
5258            }
5259            isLoadingSetter(originalScope, false);
5260          }
5261        }, function(){
5262          resetMatches();
5263          isLoadingSetter(originalScope, false);
5264        });
5265      };
5266
5267      resetMatches();
5268
5269      //we need to propagate user's query so we can higlight matches
5270      scope.query = undefined;
5271
5272      //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later 
5273      var timeoutPromise;
5274
5275      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
5276      //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
5277      modelCtrl.$parsers.unshift(function (inputValue) {
5278
5279        hasFocus = true;
5280
5281        if (inputValue && inputValue.length >= minSearch) {
5282          if (waitTime > 0) {
5283            if (timeoutPromise) {
5284              $timeout.cancel(timeoutPromise);//cancel previous timeout
5285            }
5286            timeoutPromise = $timeout(function () {
5287              getMatchesAsync(inputValue);
5288            }, waitTime);
5289          } else {
5290            getMatchesAsync(inputValue);
5291          }
5292        } else {
5293          isLoadingSetter(originalScope, false);
5294          resetMatches();
5295        }
5296
5297        if (isEditable) {
5298          return inputValue;
5299        } else {
5300          if (!inputValue) {
5301            // Reset in case user had typed something previously.
5302            modelCtrl.$setValidity('editable', true);
5303            return inputValue;
5304          } else {
5305            modelCtrl.$setValidity('editable', false);
5306            return undefined;
5307          }
5308        }
5309      });
5310
5311      modelCtrl.$formatters.push(function (modelValue) {
5312
5313        var candidateViewValue, emptyViewValue;
5314        var locals = {};
5315
5316        if (inputFormatter) {
5317
5318          locals['$model'] = modelValue;
5319          return inputFormatter(originalScope, locals);
5320
5321        } else {
5322
5323          //it might happen that we don't have enough info to properly render input value
5324          //we need to check for this situation and simply return model value if we can't apply custom formatting
5325          locals[parserResult.itemName] = modelValue;
5326          candidateViewValue = parserResult.viewMapper(originalScope, locals);
5327          locals[parserResult.itemName] = undefined;
5328          emptyViewValue = parserResult.viewMapper(originalScope, locals);
5329
5330          return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
5331        }
5332      });
5333
5334      scope.select = function (activeIdx) {
5335        //called from within the $digest() cycle
5336        var locals = {};
5337        var model, item;
5338
5339        locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
5340        model = parserResult.modelMapper(originalScope, locals);
5341        $setModelValue(originalScope, model);
5342        modelCtrl.$setValidity('editable', true);
5343
5344        onSelectCallback(originalScope, {
5345          $item: item,
5346          $model: model,
5347          $label: parserResult.viewMapper(originalScope, locals)
5348        });
5349
5350        resetMatches();
5351
5352        //return focus to the input element if a mach was selected via a mouse click event
5353        element[0].focus();
5354      };
5355
5356      //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
5357      element.bind('keydown', function (evt) {
5358
5359        //typeahead is open and an "interesting" key was pressed
5360        if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
5361          if (evt.which === 13) {
5362            evt.preventDefault();
5363          }
5364          return;
5365        }
5366
5367        evt.preventDefault();
5368
5369        if (evt.which === 40) {
5370          scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
5371          scope.$digest();
5372
5373        } else if (evt.which === 38) {
5374          scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
5375          scope.$digest();
5376
5377        } else if (evt.which === 13 || evt.which === 9) {
5378          scope.$apply(function () {
5379            scope.select(scope.activeIdx);
5380          });
5381
5382        } else if (evt.which === 27) {
5383          evt.stopPropagation();
5384
5385          resetMatches();
5386          scope.$digest();
5387        }
5388      });
5389
5390      element.bind('blur', function (evt) {
5391        hasFocus = false;
5392      });
5393
5394      // Keep reference to click handler to unbind it.
5395      var dismissClickHandler = function (evt) {
5396        if (element[0] !== evt.target) {
5397          resetMatches();
5398          scope.$digest();
5399        }
5400      };
5401
5402      $document.bind('click', dismissClickHandler);
5403
5404      originalScope.$on('$destroy', function(){
5405        $document.unbind('click', dismissClickHandler);
5406      });
5407
5408      element.after($compile(popUpEl)(scope));
5409    }
5410  };
5411
5412}])
5413
5414  .directive('typeaheadPopup', function () {
5415    return {
5416      restrict:'EA',
5417      scope:{
5418        matches:'=',
5419        query:'=',
5420        active:'=',
5421        position:'=',
5422        select:'&'
5423      },
5424      replace:true,
5425      templateUrl:'template/typeahead/typeahead-popup.html',
5426      link:function (scope, element, attrs) {
5427
5428        scope.templateUrl = attrs.templateUrl;
5429
5430        scope.isOpen = function () {
5431          return scope.matches.length > 0;
5432        };
5433
5434        scope.isActive = function (matchIdx) {
5435          return scope.active == matchIdx;
5436        };
5437
5438        scope.selectActive = function (matchIdx) {
5439          scope.active = matchIdx;
5440        };
5441
5442        scope.selectMatch = function (activeIdx) {
5443          scope.select({activeIdx:activeIdx});
5444        };
5445      }
5446    };
5447  })
5448
5449  .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
5450    return {
5451      restrict:'EA',
5452      scope:{
5453        index:'=',
5454        match:'=',
5455        query:'='
5456      },
5457      link:function (scope, element, attrs) {
5458        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
5459        $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
5460           element.replaceWith($compile(tplContent.trim())(scope));
5461        });
5462      }
5463    };
5464  }])
5465
5466  .filter('typeaheadHighlight', function() {
5467
5468    function escapeRegexp(queryToEscape) {
5469      return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
5470    }
5471
5472    return function(matchItem, query) {
5473      return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
5474    };
5475  });
5476angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
5477  $templateCache.put("template/accordion/accordion-group.html",
5478    "<div class=\"accordion-group\">\n" +
5479    "  <div class=\"accordion-heading\" ><a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a></div>\n" +
5480    "  <div class=\"accordion-body\" collapse=\"!isOpen\">\n" +
5481    "    <div class=\"accordion-inner\" ng-transclude></div>  </div>\n" +
5482    "</div>");
5483}]);
5484
5485angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
5486  $templateCache.put("template/accordion/accordion.html",
5487    "<div class=\"accordion\" ng-transclude></div>");
5488}]);
5489
5490angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
5491  $templateCache.put("template/alert/alert.html",
5492    "<div class='alert' ng-class='type && \"alert-\" + type'>\n" +
5493    "    <button ng-show='closeable' type='button' class='close' ng-click='close()'>&times;</button>\n" +
5494    "    <div ng-transclude></div>\n" +
5495    "</div>\n" +
5496    "");
5497}]);
5498
5499angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
5500  $templateCache.put("template/carousel/carousel.html",
5501    "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\">\n" +
5502    "    <ol class=\"carousel-indicators\" ng-show=\"slides().length > 1\">\n" +
5503    "        <li ng-repeat=\"slide in slides()\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
5504    "    </ol>\n" +
5505    "    <div class=\"carousel-inner\" ng-transclude></div>\n" +
5506    "    <a ng-click=\"prev()\" class=\"carousel-control left\" ng-show=\"slides().length > 1\">&lsaquo;</a>\n" +
5507    "    <a ng-click=\"next()\" class=\"carousel-control right\" ng-show=\"slides().length > 1\">&rsaquo;</a>\n" +
5508    "</div>\n" +
5509    "");
5510}]);
5511
5512angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
5513  $templateCache.put("template/carousel/slide.html",
5514    "<div ng-class=\"{\n" +
5515    "    'active': leaving || (active && !entering),\n" +
5516    "    'prev': (next || active) && direction=='prev',\n" +
5517    "    'next': (next || active) && direction=='next',\n" +
5518    "    'right': direction=='prev',\n" +
5519    "    'left': direction=='next'\n" +
5520    "  }\" class=\"item\" ng-transclude></div>\n" +
5521    "");
5522}]);
5523
5524angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
5525  $templateCache.put("template/datepicker/datepicker.html",
5526    "<table>\n" +
5527    "  <thead>\n" +
5528    "    <tr class=\"text-center\">\n" +
5529    "      <th><button type=\"button\" class=\"btn pull-left\" ng-click=\"move(-1)\"><i class=\"icon-chevron-left\"></i></button></th>\n" +
5530    "      <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button type=\"button\" class=\"btn btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
5531    "      <th><button type=\"button\" class=\"btn pull-right\" ng-click=\"move(1)\"><i class=\"icon-chevron-right\"></i></button></th>\n" +
5532    "    </tr>\n" +
5533    "    <tr class=\"text-center\" ng-show=\"labels.length > 0\">\n" +
5534    "      <th ng-show=\"showWeekNumbers\">#</th>\n" +
5535    "      <th ng-repeat=\"label in labels\">{{label}}</th>\n" +
5536    "    </tr>\n" +
5537    "  </thead>\n" +
5538    "  <tbody>\n" +
5539    "    <tr ng-repeat=\"row in rows\">\n" +
5540    "      <td ng-show=\"showWeekNumbers\" class=\"text-center\"><em>{{ getWeekNumber(row) }}</em></td>\n" +
5541    "      <td ng-repeat=\"dt in row\" class=\"text-center\">\n" +
5542    "        <button type=\"button\" style=\"width:100%;\" class=\"btn\" ng-class=\"{'btn-info': dt.selected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{muted: dt.secondary}\">{{dt.label}}</span></button>\n" +
5543    "      </td>\n" +
5544    "    </tr>\n" +
5545    "  </tbody>\n" +
5546    "</table>\n" +
5547    "");
5548}]);
5549
5550angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
5551  $templateCache.put("template/datepicker/popup.html",
5552    "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
5553    "	<li ng-transclude></li>\n" +
5554    "	<li class=\"divider\"></li>\n" +
5555    "	<li style=\"padding: 9px;\">\n" +
5556    "		<span class=\"btn-group\">\n" +
5557    "			<button type=\"button\" class=\"btn btn-small btn-inverse\" ng-click=\"today()\">{{currentText}}</button>\n" +
5558    "			<button type=\"button\" class=\"btn btn-small btn-info\" ng-click=\"showWeeks = ! showWeeks\" ng-class=\"{active: showWeeks}\">{{toggleWeeksText}}</button>\n" +
5559    "			<button type=\"button\" class=\"btn btn-small btn-danger\" ng-click=\"clear()\">{{clearText}}</button>\n" +
5560    "		</span>\n" +
5561    "		<button type=\"button\" class=\"btn btn-small btn-success pull-right\" ng-click=\"isOpen = false\">{{closeText}}</button>\n" +
5562    "	</li>\n" +
5563    "</ul>\n" +
5564    "");
5565}]);
5566
5567angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
5568  $templateCache.put("template/modal/backdrop.html",
5569    "<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}
5569\" ng-style=\"{'z-index': 1040 + index*10}\" ng-click=\"close($event)\"></div>");
5570}]);
5571
5572angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
5573  $templateCache.put("template/modal/window.html",
5574    "<div class=\"modal fade {{ windowClass }}\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10}\" ng-transclude></div>");
5575}]);
5576
5577angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
5578  $templateCache.put("template/pagination/pager.html",
5579    "<div class=\"pager\">\n" +
5580    "  <ul>\n" +
5581    "    <li ng-repeat=\"page in pages\" ng-class=\"{disabled: page.disabled, previous: page.previous, next: page.next}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
5582    "  </ul>\n" +
5583    "</div>\n" +
5584    "");
5585}]);
5586
5587angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
5588  $templateCache.put("template/pagination/pagination.html",
5589    "<div class=\"pagination\"><ul>\n" +
5590    "  <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
5591    "  </ul>\n" +
5592    "</div>\n" +
5593    "");
5594}]);
5595
5596angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) {
5597  $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html",
5598    "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
5599    "  <div class=\"tooltip-arrow\"></div>\n" +
5600    "  <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" +
5601    "</div>\n" +
5602    "");
5603}]);
5604
5605angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
5606  $templateCache.put("template/tooltip/tooltip-popup.html",
5607    "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
5608    "  <div class=\"tooltip-arrow\"></div>\n" +
5609    "  <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
5610    "</div>\n" +
5611    "");
5612}]);
5613
5614angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
5615  $templateCache.put("template/popover/popover.html",
5616    "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
5617    "  <div class=\"arrow\"></div>\n" +
5618    "\n" +
5619    "  <div class=\"popover-inner\">\n" +
5620    "      <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
5621    "      <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
5622    "  </div>\n" +
5623    "</div>\n" +
5624    "");
5625}]);
5626
5627angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
5628  $templateCache.put("template/progressbar/bar.html",
5629    "<div class=\"bar\" ng-class='type && \"bar-\" + type'></div>");
5630}]);
5631
5632angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
5633  $templateCache.put("template/progressbar/progress.html",
5634    "<div class=\"progress\"><progressbar ng-repeat=\"bar in bars\" width=\"bar.to\" old=\"bar.from\" animate=\"bar.animate\" type=\"bar.type\"></progressbar></div>");
5635}]);
5636
5637angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
5638  $templateCache.put("template/rating/rating.html",
5639    "<span ng-mouseleave=\"reset()\">\n" +
5640    "	<i ng-repeat=\"r in range\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" ng-class=\"$index < val && (r.stateOn || 'icon-star') || (r.stateOff || 'icon-star-empty')\"></i>\n" +
5641    "</span>");
5642}]);
5643
5644angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
5645  $templateCache.put("template/tabs/tab.html",
5646    "<li ng-class=\"{active: active, disabled: disabled}\">\n" +
5647    "  <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
5648    "</li>\n" +
5649    "");
5650}]);
5651
5652angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache) {
5653  $templateCache.put("template/tabs/tabset-titles.html",
5654    "<ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical}\">\n" +
5655    "</ul>\n" +
5656    "");
5657}]);
5658
5659angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
5660  $templateCache.put("template/tabs/tabset.html",
5661    "\n" +
5662    "<div class=\"tabbable\" ng-class=\"{'tabs-right': direction == 'right', 'tabs-left': direction == 'left', 'tabs-below': direction == 'below'}\">\n" +
5663    "  <div tabset-titles=\"tabsAbove\"></div>\n" +
5664    "  <div class=\"tab-content\">\n" +
5665    "    <div class=\"tab-pane\" \n" +
5666    "         ng-repeat=\"tab in tabs\" \n" +
5667    "         ng-class=\"{active: tab.active}\"\n" +
5668    "         tab-content-transclude=\"tab\">\n" +
5669    "    </div>\n" +
5670    "  </div>\n" +
5671    "  <div tabset-titles=\"!tabsAbove\"></div>\n" +
5672    "</div>\n" +
5673    "");
5674}]);
5675
5676angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
5677  $templateCache.put("template/timepicker/timepicker.html",
5678    "<table class=\"form-inline\">\n" +
5679    "	<tr class=\"text-center\">\n" +
5680    "		<td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
5681    "		<td>&nbsp;</td>\n" +
5682    "		<td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-up\"></i></a></td>\n" +
5683    "		<td ng-show=\"showMeridian\"></td>\n" +
5684    "	</tr>\n" +
5685    "	<tr>\n" +
5686    "		<td class=\"control-group\" ng-class=\"{'error': invalidHours}\"><input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"span1 text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\"></td>\n" +
5687    "		<td>:</td>\n" +
5688    "		<td class=\"control-group\" ng-class=\"{'error': invalidMinutes}\"><input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"span1 text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\"></td>\n" +
5689    "		<td ng-show=\"showMeridian\"><button type=\"button\" ng-click=\"toggleMeridian()\" class=\"btn text-center\">{{meridian}}</button></td>\n" +
5690    "	</tr>\n" +
5691    "	<tr class=\"text-center\">\n" +
5692    "		<td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
5693    "		<td>&nbsp;</td>\n" +
5694    "		<td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><i class=\"icon-chevron-down\"></i></a></td>\n" +
5695    "		<td ng-show=\"showMeridian\"></td>\n" +
5696    "	</tr>\n" +
5697    "</table>\n" +
5698    "");
5699}]);
5700
5701angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
5702  $templateCache.put("template/typeahead/typeahead-match.html",
5703    "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
5704}]);
5705
5706angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
5707  $templateCache.put("template/typeahead/typeahead-popup.html",
5708    "<ul class=\"typeahead dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
5709    "    <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
5710    "        <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
5711    "    </li>\n" +
5712    "</ul>");
5713}]);
5714
5715/**
5716 * @license AngularJS v1.2.6
5717 * (c) 2010-2014 Google, Inc. http://angularjs.org
5718 * License: MIT
5719 */
5720(function(window, angular, undefined) {'use strict';
5721
5722/* jshint maxlen: false */
5723
5724/**
5725 * @ngdoc overview
5726 * @name ngAnimate
5727 * @description
5728 *
5729 * # ngAnimate
5730 *
5731 * The `ngAnimate` module provides support for JavaScript, CSS3 transition and CSS3 keyframe animation hooks within existing core and custom directives.
5732 *
5733 * {@installModule animate}
5734 *
5735 * <div doc-module-components="ngAnimate"></div>
5736 *
5737 * # Usage
5738 *
5739 * To see animations in action, all that is required is to define the appropriate CSS classes
5740 * or to register a JavaScript animation via the myModule.animation() function. The directives that support animation automatically are:
5741 * `ngRepeat`, `ngInclude`, `ngIf`, `ngSwitch`, `ngShow`, `ngHide`, `ngView` and `ngClass`. Custom directives can take advantage of animation
5742 * by using the `$animate` service.
5743 *
5744 * Below is a more detailed breakdown of the supported animation events provided by pre-existing ng directives:
5745 *
5746 * | Directive                                                 | Supported Animations                               |
5747 * |---------------------------------------------------------- |----------------------------------------------------|
5748 * | {@link ng.directive:ngRepeat#usage_animations ngRepeat}         | enter, leave and move                              |
5749 * | {@link ngRoute.directive:ngView#usage_animations ngView}        | enter and leave                                    |
5750 * | {@link ng.directive:ngInclude#usage_animations ngInclude}       | enter and leave                                    |
5751 * | {@link ng.directive:ngSwitch#usage_animations ngSwitch}         | enter and leave                                    |
5752 * | {@link ng.directive:ngIf#usage_animations ngIf}                 | enter and leave                                    |
5753 * | {@link ng.directive:ngClass#usage_animations ngClass}           | add and remove                                     |
5754 * | {@link ng.directive:ngShow#usage_animations ngShow & ngHide}    | add and remove (the ng-hide class value)           |
5755 *
5756 * You can find out more information about animations upon visiting each directive page.
5757 *
5758 * Below is an example of how to apply animations to a directive that supports animation hooks:
5759 *
5760 * <pre>
5761 * <style type="text/css">
5762 * .slide.ng-enter, .slide.ng-leave {
5763 *   -webkit-transition:0.5s linear all;
5764 *   transition:0.5s linear all;
5765 * }
5766 *
5767 * .slide.ng-enter { }        /&#42; starting animations for enter &#42;/
5768 * .slide.ng-enter-active { } /&#42; terminal animations for enter &#42;/
5769 * .slide.ng-leave { }        /&#42; starting animations for leave &#42;/
5770 * .slide.ng-leave-active { } /&#42; terminal animations for leave &#42;/
5771 * </style>
5772 *
5773 * <!--
5774 * the animate service will automatically add .ng-enter and .ng-leave to the element
5775 * to trigger the CSS transition/animations
5776 * -->
5777 * <ANY class="slide" ng-include="..."></ANY>
5778 * </pre>
5779 *
5780 * Keep in mind that if an animation is running, any child elements cannot be animated until the parent element's
5781 * animation has completed.
5782 *
5783 * <h2>CSS-defined Animations</h2>
5784 * The animate service will automatically apply two CSS classes to the animated element and these two CSS classes
5785 * are designed to contain the start and end CSS styling. Both CSS transitions and keyframe animations are supported
5786 * and can be used to play along with this naming structure.
5787 *
5788 * The following code below demonstrates how to perform animations using **CSS transitions** with Angular:
5789 *
5790 * <pre>
5791 * <style type="text/css">
5792 * /&#42;
5793 *  The animate class is apart of the element and the ng-enter class
5794 *  is attached to the element once the enter animation event is triggered
5795 * &#42;/
5796 * .reveal-animation.ng-enter {
5797 *  -webkit-transition: 1s linear all; /&#42; Safari/Chrome &#42;/
5798 *  transition: 1s linear all; /&#42; All other modern browsers and IE10+ &#42;/
5799 *
5800 *  /&#42; The animation preparation code &#42;/
5801 *  opacity: 0;
5802 * }
5803 *
5804 * /&#42;
5805 *  Keep in mind that you want to combine both CSS
5806 *  classes together to avoid any CSS-specificity
5807 *  conflicts
5808 * &#42;/
5809 * .reveal-animation.ng-enter.ng-enter-active {
5810 *  /&#42; The animation code itself &#42;/
5811 *  opacity: 1;
5812 * }
5813 * </style>
5814 *
5815 * <div class="view-container">
5816 *   <div ng-view class="reveal-animation"></div>
5817 * </div>
5818 * </pre>
5819 *
5820 * The following code below demonstrates how to perform animations using **CSS animations** with Angular:
5821 *
5822 * <pre>
5823 * <style type="text/css">
5824 * .reveal-animation.ng-enter {
5825 *   -webkit-animation: enter_sequence 1s linear; /&#42; Safari/Chrome &#42;/
5826 *   animation: enter_sequence 1s linear; /&#42; IE10+ and Future Browsers &#42;/
5827 * }
5828 * &#64-webkit-keyframes enter_sequence {
5829 *   from { opacity:0; }
5830 *   to { opacity:1; }
5831 * }
5832 * &#64keyframes enter_sequence {
5833 *   from { opacity:0; }
5834 *   to { opacity:1; }
5835 * }
5836 * </style>
5837 *
5838 * <div class="view-container">
5839 *   <div ng-view class="reveal-animation"></div>
5840 * </div>
5841 * </pre>
5842 *
5843 * Both CSS3 animations and transitions can be used together and the animate service will figure out the correct duration and delay timing.
5844 *
5845 * Upon DOM mutation, the event class is added first (something like `ng-enter`), then the browser prepares itself to add
5846 * the active class (in this case `ng-enter-active`) which then triggers the animation. The animation module will automatically
5847 * detect the CSS code to determine when the animation ends. Once the animation is over then both CSS classes will be
5848 * removed from the DOM. If a browser does not support CSS transitions or CSS animations then the animation will start and end
5849 * immediately resulting in a DOM element that is at its final state. This final state is when the DOM element
5850 * has no CSS transition/animation classes applied to it.
5851 *
5852 * <h3>CSS Staggering Animations</h3>
5853 * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
5854 * curtain-like effect. The ngAnimate module, as of 1.2.0, supports staggering animations and the stagger effect can be
5855 * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
5856 * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
5857 * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
5858 *
5859 * <pre>
5860 * .my-animation.ng-enter {
5861 *   /&#42; standard transition code &#42;/
5862 *   -webkit-transition: 1s linear all;
5863 *   transition: 1s linear all;
5864 *   opacity:0;
5865 * }
5866 * .my-animation.ng-enter-stagger {
5867 *   /&#42; this will have a 100ms delay between each successive leave animation &#42;/
5868 *   -webkit-transition-delay: 0.1s;
5869 *   transition-delay: 0.1s;
5870 *
5871 *   /&#42; in case the stagger doesn't work then these two values
5872 *    must be set to 0 to avoid an accidental CSS inheritance &#42;/
5873 *   -webkit-transition-duration: 0s;
5874 *   transition-duration: 0s;
5875 * }
5876 * .my-animation.ng-enter.ng-enter-active {
5877 *   /&#42; standard transition styles &#42;/
5878 *   opacity:1;
5879 * }
5880 * </pre>
5881 *
5882 * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
5883 * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
5884 * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
5885 * will also be reset if more than 10ms has passed after the last animation has been fired.
5886 *
5887 * The following code will issue the **ng-leave-stagger** event on the element provided:
5888 *
5889 * <pre>
5890 * var kids = parent.children();
5891 *
5892 * $animate.leave(kids[0]); //stagger index=0
5893 * $animate.leave(kids[1]); //stagger index=1
5894 * $animate.leave(kids[2]); //stagger index=2
5895 * $animate.leave(kids[3]); //stagger index=3
5896 * $animate.leave(kids[4]); //stagger index=4
5897 *
5898 * $timeout(function() {
5899 *   //stagger has reset itself
5900 *   $animate.leave(kids[5]); //stagger index=0
5901 *   $animate.leave(kids[6]); //stagger index=1
5902 * }, 100, false);
5903 * </pre>
5904 *
5905 * Stagger animations are currently only supported within CSS-defined animations.
5906 *
5907 * <h2>JavaScript-defined Animations</h2>
5908 * In the event that you do not want to use CSS3 transitions or CSS3 animations or if you wish to offer animations on browsers that do not
5909 * yet support CSS transitions/animations, then you can make use of JavaScript animations defined inside of your AngularJS module.
5910 *
5911 * <pre>
5912 * //!annotate="YourApp" Your AngularJS Module|Replace this or ngModule with the module that you used to define your a
5912pplication.
5913 * var ngModule = angular.module('YourApp', ['ngAnimate']);
5914 * ngModule.animation('.my-crazy-animation', function() {
5915 *   return {
5916 *     enter: function(element, done) {
5917 *       //run the animation here and call done when the animation is complete
5918 *       return function(cancelled) {
5919 *         //this (optional) function will be called when the animation
5920 *         //completes or when the animation is cancelled (the cancelled
5921 *         //flag will be set to true if cancelled).
5922 *       };
5923 *     },
5924 *     leave: function(element, done) { },
5925 *     move: function(element, done) { },
5926 *
5927 *     //animation that can be triggered before the class is added
5928 *     beforeAddClass: function(element, className, done) { },
5929 *
5930 *     //animation that can be triggered after the class is added
5931 *     addClass: function(element, className, done) { },
5932 *
5933 *     //animation that can be triggered before the class is removed
5934 *     beforeRemoveClass: function(element, className, done) { },
5935 *
5936 *     //animation that can be triggered after the class is removed
5937 *     removeClass: function(element, className, done) { }
5938 *   };
5939 * });
5940 * </pre>
5941 *
5942 * JavaScript-defined animations are created with a CSS-like class selector and a collection of events which are set to run
5943 * a javascript callback function. When an animation is triggered, $animate will look for a matching animation which fits
5944 * the element's CSS class attribute value and then run the matching animation event function (if found).
5945 * In other words, if the CSS classes present on the animated element match any of the JavaScript animations then the callback function will
5946 * be executed. It should be also noted that only simple, single class selectors are allowed (compound class selectors are not supported).
5947 *
5948 * Within a JavaScript animation, an object containing various event callback animation functions is expected to be returned.
5949 * As explained above, these callbacks are triggered based on the animation event. Therefore if an enter animation is run,
5950 * and the JavaScript animation is found, then the enter callback will handle that animation (in addition to the CSS keyframe animation
5951 * or transition code that is defined via a stylesheet).
5952 *
5953 */
5954
5955angular.module('ngAnimate', ['ng'])
5956
5957  /**
5958   * @ngdoc object
5959   * @name ngAnimate.$animateProvider
5960   * @description
5961   *
5962   * The `$animateProvider` allows developers to register JavaScript animation event handlers directly inside of a module.
5963   * When an animation is triggered, the $animate service will query the $animate service to find any animations that match
5964   * the provided name value.
5965   *
5966   * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
5967   *
5968   * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
5969   *
5970   */
5971  .config(['$provide', '$animateProvider', function($provide, $animateProvider) {
5972    var noop = angular.noop;
5973    var forEach = angular.forEach;
5974    var selectors = $animateProvider.$$selectors;
5975
5976    var ELEMENT_NODE = 1;
5977    var NG_ANIMATE_STATE = '$$ngAnimateState';
5978    var NG_ANIMATE_CLASS_NAME = 'ng-animate';
5979    var rootAnimateState = {running: true};
5980
5981    function extractElementNode(element) {
5982      for(var i = 0; i < element.length; i++) {
5983        var elm = element[i];
5984        if(elm.nodeType == ELEMENT_NODE) {
5985          return elm;
5986        }
5987      }
5988    }
5989
5990    function isMatchingElement(elm1, elm2) {
5991      return extractElementNode(elm1) == extractElementNode(elm2);
5992    }
5993
5994    $provide.decorator('$animate', ['$delegate', '$injector', '$sniffer', '$rootElement', '$timeout', '$rootScope', '$document',
5995                            function($delegate,   $injector,   $sniffer,   $rootElement,   $timeout,   $rootScope,   $document) {
5996
5997      $rootElement.data(NG_ANIMATE_STATE, rootAnimateState);
5998
5999      // disable animations during bootstrap, but once we bootstrapped, wait again
6000      // for another digest until enabling animations. The reason why we digest twice
6001      // is because all structural animations (enter, leave and move) all perform a
6002      // post digest operation before animating. If we only wait for a single digest
6003      // to pass then the structural animation would render its animation on page load.
6004      // (which is what we're trying to avoid when the application first boots up.)
6005      $rootScope.$$postDigest(function() {
6006        $rootScope.$$postDigest(function() {
6007          rootAnimateState.running = false;
6008        });
6009      });
6010
6011      var classNameFilter = $animateProvider.classNameFilter();
6012      var isAnimatableClassName = !classNameFilter
6013              ? function() { return true; }
6014              : function(className) {
6015                return classNameFilter.test(className);
6016              };
6017
6018      function lookup(name) {
6019        if (name) {
6020          var matches = [],
6021              flagMap = {},
6022              classes = name.substr(1).split('.');
6023
6024          //the empty string value is the default animation
6025          //operation which performs CSS transition and keyframe
6026          //animations sniffing. This is always included for each
6027          //element animation procedure if the browser supports
6028          //transitions and/or keyframe animations
6029          if ($sniffer.transitions || $sniffer.animations) {
6030            classes.push('');
6031          }
6032
6033          for(var i=0; i < classes.length; i++) {
6034            var klass = classes[i],
6035                selectorFactoryName = selectors[klass];
6036            if(selectorFactoryName && !flagMap[klass]) {
6037              matches.push($injector.get(selectorFactoryName));
6038              flagMap[klass] = true;
6039            }
6040          }
6041          return matches;
6042        }
6043      }
6044
6045      /**
6046       * @ngdoc object
6047       * @name ngAnimate.$animate
6048       * @function
6049       *
6050       * @description
6051       * The `$animate` service provides animation detection support while performing DOM operations (enter, leave and move) as well as during addClass and removeClass operations.
6052       * When any of these operations are run, the $animate service
6053       * will examine any JavaScript-defined animations (which are defined by using the $animateProvider provider object)
6054       * as well as any CSS-defined animations against the CSS classes present on the element once the DOM operation is run.
6055       *
6056       * The `$animate` service is used behind the scenes with pre-existing directives and animation with these directives
6057       * will work out of the box without any extra configuration.
6058       *
6059       * Requires the {@link ngAnimate `ngAnimate`} module to be installed.
6060       *
6061       * Please visit the {@link ngAnimate `ngAnimate`} module overview page learn more about how to use animations in your application.
6062       *
6063       */
6064      return {
6065        /**
6066         * @ngdoc function
6067         * @name ngAnimate.$animate#enter
6068         * @methodOf ngAnimate.$animate
6069         * @function
6070         *
6071         * @description
6072         * Appends the element to the parentElement element that resides in the document and then runs the enter animation. Once
6073         * the animation is started, the following CSS classes will be present on the element for the duration of the animation:
6074         *
6075         * Below is a breakdown of each step that occurs during enter animation:
6076         *
6077         * | Animation Step                                                                               | What the element class attribute looks like |
6078         * |----------------------------------------------------------------------------------------------|---------------------------------------------|
6079         * | 1. $animate.enter(...) is called                                                             | class="my-animation"                        |
6080         * | 2. element is inserted into the parentElement element or beside the afterElement element     | class="my-animation"                        |
6081         * | 3. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
6082         * | 4. the .ng-enter class is added to the element                                               | class="my-animation ng-animate ng-enter"    |
6083         * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-enter"    |
6084         * | 6. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-enter"    |
6085         * | 7. the .ng-enter-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
6086         * | 8. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-enter ng-enter-active" |
6087         * | 9. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
6088         * | 10. The doneCallback() callback is fired (if provided)                                       | class="my-animation"                        |
6089         *
6090         * @param {jQuery/jqLite element} element the element that will be the focus of the enter animation
6091         * @param {jQuery/jqLite element} parentElement the parent element of the element that will be the focus of the enter animation
6092         * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the enter animation
6093         * @param {function()=} doneCallback the callback function that will be called once the animation is complete
6094        */
6095        enter : function(element, parentElement, afterElement, doneCallback) {
6096          this.enabled(false, element);
6097          $delegate.enter(element, parentElement, afterElement);
6098          $rootScope.$$postDigest(function() {
6099            performAnimation('enter', 'ng-enter', element, parentElement, afterElement, noop, doneCallback);
6100          });
6101        },
6102
6103        /**
6104         * @ngdoc function
6105         * @name ngAnimate.$animate#leave
6106         * @methodOf ngAnimate.$animate
6107         * @function
6108         *
6109         * @description
6110         * Runs the leave animation operation and, upon completion, removes the element from the DOM. Once
6111         * the animation is started, the following CSS classes will be added for the duration of the animation:
6112         *
6113         * Below is a breakdown of each step that occurs during leave animation:
6114         *
6115         * | Animation Step                                                                               | What the element class attribute looks like |
6116         * |----------------------------------------------------------------------------------------------|---------------------------------------------|
6117         * | 1. $animate.leave(...) is called                                                             | class="my-animation"                        |
6118         * | 2. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
6119         * | 3. the .ng-leave class is added to the element                                               | class="my-animation ng-animate ng-leave"    |
6120         * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-leave"    |
6121         * | 5. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-leave"    |
6122         * | 6. the .ng-leave-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
6123         * | 7. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-leave ng-leave-active" |
6124         * | 8. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
6125         * | 9. The element is removed from the DOM                                                       | ...                                         |
6126         * | 10. The doneCallback() callback is fired (if provided)                                       | ...                                         |
6127         *
6128         * @param {jQuery/jqLite element} element the element that will be the focus of the leave animation
6129         * @param {function()=} doneCallback the callback function that will be called once the animation is complete
6130        */
6131        leave : function(element, doneCallback) {
6132          cancelChildAnimations(element);
6133          this.enabled(false, element);
6134          $rootScope.$$postDigest(function() {
6135            performAnimation('leave', 'ng-leave', element, null, null, function() {
6136              $delegate.leave(element);
6137            }, doneCallback);
6138          });
6139        },
6140
6141        /**
6142         * @ngdoc function
6143         * @name ngAnimate.$animate#move
6144         * @methodOf ngAnimate.$animate
6145         * @function
6146         *
6147         * @description
6148         * Fires the move DOM operation. Just before the animation starts, the animate service will either append it into the parentElement container or
6149         * add the element directly after the afterElement element if present. Then the move animation will be run. Once
6150         * the animation is started, the following CSS classes will be added for the duration of the animation:
6151         *
6152         * Below is a breakdown of each step that occurs during move animation:
6153         *
6154         * | Animation Step                                                                               | What the element class attribute looks like |
6155         * |----------------------------------------------------------------------------------------------|---------------------------------------------|
6156         * | 1. $animate.move(...) is called                                                              | class="my-animation"                        |
6157         * | 2. element is moved into the parentElement element or beside the afterElement element        | class="my-animation"                        |
6158         * | 3. $animate runs any JavaScript-defined animations on the element                            | class="my-animation ng-animate"             |
6159         * | 4. the .ng-move class is added to the element                                                | class="my-animation ng-animate ng-move"     |
6160         * | 5. $animate scans the element styles to get the CSS transition/animation duration and delay  | class="my-animation ng-animate ng-move"     |
6161         * | 6. $animate waits for 10ms (this performs a reflow)                                          | class="my-animation ng-animate ng-move"     |
6162         * | 7. the .ng-move-active and .ng-animate-active classes is added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
6163         * | 8. $animate waits for X milliseconds for the animation to complete                           | class="my-animation ng-animate ng-animate-active ng-move ng-move-active" |
6164         * | 9. The animation ends and all generated CSS classes are removed from the element             | class="my-animation"                        |
6165         * | 10. The doneCallback() callback is fired (if provided)                                       | class="my-animation"                        |
6166         *
6167         * @param {jQuery/jqLite element} element the element that will be the focus of the move animation
6168         * @param {jQuery/jqLite element} parentElement the parentElement element of the element that will be the focus of the move animation
6169         * @param {jQuery/jqLite element} afterElement the sibling element (which is the previous element) of the element that will be the focus of the move animation
6170         * @param {function()=} doneCallback the callback function that will be called once the animation is complete
6171        */
6172        move : function(element, parentElement, afterElement, doneCallback) {
6173          cancelChildAnimations(element);
6174          this.enabled(false, element);
6175          $delegate.move(element, parentElement, afterElement);
6176          $rootScope.$$postDigest(function() {
6177            performAnimation('move', 'ng-move', element, parentElement, afterElement, noop, doneCallback);
6178          });
6179        },
6180
6181        /**
6182         * @ngdoc function
6183         * @name ngAnimate.$animate#addClass
6184         * @methodOf ngAnimate.$animate
6185         *
6186         * @description
6187         * Triggers a custom animation event based off the className variable and then attaches the className value to the element as a CSS class.
6188         * Unlike the other animation methods, the animate service will suffix the className value with {@type -add} in order to provide
6189         * the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if no CSS transitions
6190         * or keyframes are defined on the -add or base CSS class).
6191         *
6192         * Below is a breakdown of each step that occurs during addClass animation:
6193         *
6194         * | Animation Step                                                                                 | What the element class attribute looks like |
6195         * |------------------------------------------------------------------------------------------------|---------------------------------------------|
6196         * | 1. $animate.addClass(element, 'super') is called                                               | class="my-animation"                        |
6197         * | 2. $animate runs any JavaScript-defined animations on the element                              | class="my-animation ng-animate"             |
6198         * | 3. the .super-add class are added to the element                                               | class="my-animation ng-animate super-add"   |
6199         * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay    | class="my-animation ng-animate super-add"   |
6200         * | 5. $animate waits for 10ms (this performs a reflow)                                            | class="my-animation ng-animate super-add"   |
6201         * | 6. the .super, .super-add-active and .ng-animate-active classes are added (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super super-add super-add-active"          |
6202         * | 7. $animate waits for X milliseconds for the animation to complete                             | class="my-animation super-add super-add-active"  |
6203         * | 8. The animation ends and all generated CSS classes are removed from the element               | class="my-animation super"                  |
6204         * | 9. The super class is kept on the element                                                      | class="my-animation super"                  |
6205         * | 10. The doneCallback() callback is fired (if provided)                                         | class="my-animation super"                  |
6206         *
6207         * @param {jQuery/jqLite element} element the element that will be animated
6208         * @param {string} className the CSS class that will be added to the element and then animated
6209         * @param {function()=} doneCallback the callback function that will be called once the animation is complete
6210        */
6211        addClass : function(element, className, doneCallback) {
6212          performAnimation('addClass', className, element, null, null, function() {
6213            $delegate.addClass(element, className);
6214          }, doneCallback);
6215        },
6216
6217        /**
6218         * @ngdoc function
6219         * @name ngAnimate.$animate#removeClass
6220         * @methodOf ngAnimate.$animate
6221         *
6222         * @description
6223         * Triggers a custom animation event based off the className variable and then removes the CSS class provided by the className value
6224         * from the element. Unlike the other animation methods, the animate service will suffix the className value with {@type -remove} in
6225         * order to provide the animate service the setup and active CSS classes in order to trigger the animation (this will be skipped if
6226         * no CSS transitions or keyframes are defined on the -remove or base CSS classes).
6227         *
6228         * Below is a breakdown of each step that occurs during removeClass animation:
6229         *
6230         * | Animation Step                                                                                | What the element class attribute looks like     |
6231         * |-----------------------------------------------------------------------------------------------|---------------------------------------------|
6232         * | 1. $animate.removeClass(element, 'super') is called                                           | class="my-animation super"                  |
6233         * | 2. $animate runs any JavaScript-defined animations on the element                             | class="my-animation super ng-animate"       |
6234         * | 3. the .super-remove class are added to the element                                           | class="my-animation super ng-animate super-remove"|
6235         * | 4. $animate scans the element styles to get the CSS transition/animation duration and delay   | class="my-animation super ng-animate super-remove"   |
6236         * | 5. $animate waits for 10ms (this performs a reflow)                                           | class="my-animation super ng-animate super-remove"   |
6237         * | 6. the .super-remove-active and .ng-animate-active classes are added and .super is removed (this triggers the CSS transition/animation) | class="my-animation ng-animate ng-animate-active super-remove super-remove-active"          |
6238         * | 7. $animate waits for X milliseconds for the animation to complete                            | class="my-animation ng-animate ng-animate-active super-remove super-remove-active"   |
6239         * | 8. The animation ends and all generated CSS classes are removed from the element              | class="my-animation"                        |
6240         * | 9. The doneCallback() callback is fired (if provided)                                         | class="my-animation"                        |
6241         *
6242         *
6243         * @param {jQuery/jqLite element} element the element that will be animated
6244         * @param {string} className the CSS class that will be animated and then removed from the element
6245         * @param {function()=} doneCallback the callback function that will be called once the animation is complete
6246        */
6247        removeClass : function(element, className, doneCallback) {
6248          performAnimation('removeClass', className, element, null, null, function() {
6249            $delegate.removeClass(element, className);
6250          }, doneCallback);
6251        },
6252
6253        /**
6254         * @ngdoc function
6255         * @name ngAnimate.$animate#enabled
6256         * @methodOf ngAnimate.$animate
6257         * @function
6258         *
6259         * @param {boolean=} value If provided then set the animation on or off.
6260         * @param {jQuery/jqLite element=} element If provided then the element will be used to represent the enable/disable operation
6261         * @return {boolean} Current animation state.
6262         *
6263         * @description
6264         * Globally enables/disables animations.
6265         *
6266        */
6267        enabled : function(value, element) {
6268          switch(arguments.length) {
6269            case 2:
6270              if(value) {
6271                cleanup(element);
6272              } else {
6273                var data = element.data(NG_ANIMATE_STATE) || {};
6274                data.disabled = true;
6275                element.data(NG_ANIMATE_STATE, data);
6276              }
6277            break;
6278
6279            case 1:
6280              rootAnimateState.disabled = !value;
6281            break;
6282
6283            default:
6284              value = !rootAnimateState.disabled;
6285            break;
6286          }
6287          return !!value;
6288         }
6289      };
6290
6291      /*
6292        all animations call this shared animation triggering function internally.
6293        The animationEvent variable refers to the JavaScript animation event that will be triggered
6294        and the className value is the name of the animation that will be applied within the
6295        CSS code. Element, parentElement and afterElement are provided DOM elements for the animation
6296        and the onComplete callback will be fired once the animation is fully complete.
6297      */
6298      function performAnimation(animationEvent, className, element, parentElement, afterElement, domOperation, doneCallback) {
6299        var currentClassName, classes, node = extractElementNode(element);
6300        if(node) {
6301          currentClassName = node.className;
6302          classes = currentClassName + ' ' + className;
6303        }
6304
6305        //transcluded directives may sometimes fire an animation using only comment nodes
6306        //best to catch this early on to prevent any animation operations from occurring
6307        if(!node || !isAnimatableClassName(classes)) {
6308          fireDOMOperation();
6309          closeAnimation();
6310          return;
6311        }
6312
6313        var animationLookup = (' ' + classes).replace(/\s+/g,'.');
6314        if (!parentElement) {
6315          parentElement = afterElement ? afterElement.parent() : element.parent();
6316        }
6317
6318        var matches = lookup(animationLookup);
6319        var isClassBased = animationEvent == 'addClass' || animationEvent == 'removeClass';
6320        var ngAnimateState = element.data(NG_ANIMATE_STATE) || {};
6321
6322        //skip the animation if animations are disabled, a parent is already being animated,
6323        //the element is not currently attached to the document body or then completely close
6324        //the animation if any matching animations are not found at all.
6325        //NOTE: IE8 + IE9 should close properly (run closeAnimation()) in case a NO animation is not found.
6326        if (animationsDisabled(element, parentElement) || matches.length === 0) {
6327          fireDOMOperation();
6328          closeAnimation();
6329          return;
6330        }
6331
6332        var animations = [];
6333        //only add animations if the currently running animation is not structural
6334        //or if there is no animation running at all
6335        if(!ngAnimateState.running || !(isClassBased && ngAnimateState.structural)) {
6336          forEach(matches, function(animation) {
6337            //add the animation to the queue to if it is allowed to be cancelled
6338            if(!animation.allowCancel || animation.allowCancel(element, animationEvent, className)) {
6339              var beforeFn, afterFn = animation[animationEvent];
6340
6341              //Special case for a leave animation since there is no point in performing an
6342              //animation on a element node that has already been removed from the DOM
6343              if(animationEvent == 'leave') {
6344                beforeFn = afterFn;
6345                afterFn = null; //this must be falsy so that the animation is skipped for leave
6346              } else {
6347                beforeFn = animation['before' + animationEvent.charAt(0).toUpperCase() + animationEvent.substr(1)];
6348              }
6349              animations.push({
6350                before : beforeFn,
6351                after : afterFn
6352              });
6353            }
6354          });
6355        }
6356
6357        //this would mean that an animation was not allowed so let the existing
6358        //animation do it's thing and close this one early
6359        if(animations.length === 0) {
6360          fireDOMOperation();
6361          fireDoneCallbackAsync();
6362          return;
6363        }
6364
6365        //this value will be searched for class-based CSS className lookup. Therefore,
6366        //we prefix and suffix the current className value with spaces to avoid substring
6367        //lookups of className tokens
6368        var futureClassName = ' ' + currentClassName + ' ';
6369        if(ngAnimateState.running) {
6370          //if an animation is currently running on the element then lets take the steps
6371          //to cancel that animation and fire any required callbacks
6372          $timeout.cancel(ngAnimateState.closeAnimationTimeout);
6373          cleanup(element);
6374          cancelAnimations(ngAnimateState.animations);
6375
6376          //if the class is removed during the reflow then it will revert the styles temporarily
6377          //back to the base class CSS styling causing a jump-like effect to occur. This check
6378          //here ensures that the domOperation is only performed after the reflow has commenced
6379          if(ngAnimateState.beforeComplete) {
6380            (ngAnimateState.done || noop)(true);
6381          } else if(isClassBased && !ngAnimateState.structural) {
6382            //class-based animations will compare element className values after cancelling the
6383            //previous animation to see if the element properties already contain the final CSS
6384            //class and if so then the animation will be skipped. Since the domOperation will
6385            //be performed only after the reflow is complete then our element's className value
6386            //will be invalid. Therefore the same string manipulation that would occur within the
6387            //DOM operation will be performed below so that the class comparison is valid...
6388            futureClassName = ngAnimateState.event == 'removeClass' ?
6389              futureClassName.replace(ngAnimateState.className, '') 
6389:
6390              futureClassName + ngAnimateState.className + ' ';
6391          }
6392        }
6393
6394        //There is no point in perform a class-based animation if the element already contains
6395        //(on addClass) or doesn't contain (on removeClass) the className being animated.
6396        //The reason why this is being called after the previous animations are cancelled
6397        //is so that the CSS classes present on the element can be properly examined.
6398        var classNameToken = ' ' + className + ' ';
6399        if((animationEvent == 'addClass'    && futureClassName.indexOf(classNameToken) >= 0) ||
6400           (animationEvent == 'removeClass' && futureClassName.indexOf(classNameToken) == -1)) {
6401          fireDOMOperation();
6402          fireDoneCallbackAsync();
6403          return;
6404        }
6405
6406        //the ng-animate class does nothing, but it's here to allow for
6407        //parent animations to find and cancel child animations when needed
6408        element.addClass(NG_ANIMATE_CLASS_NAME);
6409
6410        element.data(NG_ANIMATE_STATE, {
6411          running:true,
6412          event:animationEvent,
6413          className:className,
6414          structural:!isClassBased,
6415          animations:animations,
6416          done:onBeforeAnimationsComplete
6417        });
6418
6419        //first we run the before animations and when all of those are complete
6420        //then we perform the DOM operation and run the next set of animations
6421        invokeRegisteredAnimationFns(animations, 'before', onBeforeAnimationsComplete);
6422
6423        function onBeforeAnimationsComplete(cancelled) {
6424          fireDOMOperation();
6425          if(cancelled === true) {
6426            closeAnimation();
6427            return;
6428          }
6429
6430          //set the done function to the final done function
6431          //so that the DOM event won't be executed twice by accident
6432          //if the after animation is cancelled as well
6433          var data = element.data(NG_ANIMATE_STATE);
6434          if(data) {
6435            data.done = closeAnimation;
6436            element.data(NG_ANIMATE_STATE, data);
6437          }
6438          invokeRegisteredAnimationFns(animations, 'after', closeAnimation);
6439        }
6440
6441        function invokeRegisteredAnimationFns(animations, phase, allAnimationFnsComplete) {
6442          var endFnName = phase + 'End';
6443          forEach(animations, function(animation, index) {
6444            var animationPhaseCompleted = function() {
6445              progress(index, phase);
6446            };
6447
6448            //there are no before functions for enter + move since the DOM
6449            //operations happen before the performAnimation method fires
6450            if(phase == 'before' && (animationEvent == 'enter' || animationEvent == 'move')) {
6451              animationPhaseCompleted();
6452              return;
6453            }
6454
6455            if(animation[phase]) {
6456              animation[endFnName] = isClassBased ?
6457                animation[phase](element, className, animationPhaseCompleted) :
6458                animation[phase](element, animationPhaseCompleted);
6459            } else {
6460              animationPhaseCompleted();
6461            }
6462          });
6463
6464          function progress(index, phase) {
6465            var phaseCompletionFlag = phase + 'Complete';
6466            var currentAnimation = animations[index];
6467            currentAnimation[phaseCompletionFlag] = true;
6468            (currentAnimation[endFnName] || noop)();
6469
6470            for(var i=0;i<animations.length;i++) {
6471              if(!animations[i][phaseCompletionFlag]) return;
6472            }
6473
6474            allAnimationFnsComplete();
6475          }
6476        }
6477
6478        function fireDoneCallbackAsync() {
6479          doneCallback && $timeout(doneCallback, 0, false);
6480        }
6481
6482        //it is less complicated to use a flag than managing and cancelling
6483        //timeouts containing multiple callbacks.
6484        function fireDOMOperation() {
6485          if(!fireDOMOperation.hasBeenRun) {
6486            fireDOMOperation.hasBeenRun = true;
6487            domOperation();
6488          }
6489        }
6490
6491        function closeAnimation() {
6492          if(!closeAnimation.hasBeenRun) {
6493            closeAnimation.hasBeenRun = true;
6494            var data = element.data(NG_ANIMATE_STATE);
6495            if(data) {
6496              /* only structural animations wait for reflow before removing an
6497                 animation, but class-based animations don't. An example of this
6498                 failing would be when a parent HTML tag has a ng-class attribute
6499                 causing ALL directives below to skip animations during the digest */
6500              if(isClassBased) {
6501                cleanup(element);
6502              } else {
6503                data.closeAnimationTimeout = $timeout(function() {
6504                  cleanup(element);
6505                }, 0, false);
6506                element.data(NG_ANIMATE_STATE, data);
6507              }
6508            }
6509            fireDoneCallbackAsync();
6510          }
6511        }
6512      }
6513
6514      function cancelChildAnimations(element) {
6515        var node = extractElementNode(element);
6516        forEach(node.querySelectorAll('.' + NG_ANIMATE_CLASS_NAME), function(element) {
6517          element = angular.element(element);
6518          var data = element.data(NG_ANIMATE_STATE);
6519          if(data) {
6520            cancelAnimations(data.animations);
6521            cleanup(element);
6522          }
6523        });
6524      }
6525
6526      function cancelAnimations(animations) {
6527        var isCancelledFlag = true;
6528        forEach(animations, function(animation) {
6529          if(!animations.beforeComplete) {
6530            (animation.beforeEnd || noop)(isCancelledFlag);
6531          }
6532          if(!animations.afterComplete) {
6533            (animation.afterEnd || noop)(isCancelledFlag);
6534          }
6535        });
6536      }
6537
6538      function cleanup(element) {
6539        if(isMatchingElement(element, $rootElement)) {
6540          if(!rootAnimateState.disabled) {
6541            rootAnimateState.running = false;
6542            rootAnimateState.structural = false;
6543          }
6544        } else {
6545          element.removeClass(NG_ANIMATE_CLASS_NAME);
6546          element.removeData(NG_ANIMATE_STATE);
6547        }
6548      }
6549
6550      function animationsDisabled(element, parentElement) {
6551        if (rootAnimateState.disabled) return true;
6552
6553        if(isMatchingElement(element, $rootElement)) {
6554          return rootAnimateState.disabled || rootAnimateState.running;
6555        }
6556
6557        do {
6558          //the element did not reach the root element which means that it
6559          //is not apart of the DOM. Therefore there is no reason to do
6560          //any animations on it
6561          if(parentElement.length === 0) break;
6562
6563          var isRoot = isMatchingElement(parentElement, $rootElement);
6564          var state = isRoot ? rootAnimateState : parentElement.data(NG_ANIMATE_STATE);
6565          var result = state && (!!state.disabled || !!state.running);
6566          if(isRoot || result) {
6567            return result;
6568          }
6569
6570          if(isRoot) return true;
6571        }
6572        while(parentElement = parentElement.parent());
6573
6574        return true;
6575      }
6576    }]);
6577
6578    $animateProvider.register('', ['$window', '$sniffer', '$timeout', function($window, $sniffer, $timeout) {
6579      // Detect proper transitionend/animationend event names.
6580      var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT;
6581
6582      // If unprefixed events are not supported but webkit-prefixed are, use the latter.
6583      // Otherwise, just use W3C names, browsers not supporting them at all will just ignore them.
6584      // Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend`
6585      // but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`.
6586      // Register both events in case `window.onanimationend` is not supported because of that,
6587      // do the same for `transitionend` as Safari is likely to exhibit similar behavior.
6588      // Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit
6589      // therefore there is no reason to test anymore for other vendor prefixes: http://caniuse.com/#search=transition
6590      if (window.ontransitionend === undefined && window.onwebkittransitionend !== undefined) {
6591        CSS_PREFIX = '-webkit-';
6592        TRANSITION_PROP = 'WebkitTransition';
6593        TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend';
6594      } else {
6595        TRANSITION_PROP = 'transition';
6596        TRANSITIONEND_EVENT = 'transitionend';
6597      }
6598
6599      if (window.onanimationend === undefined && window.onwebkitanimationend !== undefined) {
6600        CSS_PREFIX = '-webkit-';
6601        ANIMATION_PROP = 'WebkitAnimation';
6602        ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend';
6603      } else {
6604        ANIMATION_PROP = 'animation';
6605        ANIMATIONEND_EVENT = 'animationend';
6606      }
6607
6608      var DURATION_KEY = 'Duration';
6609      var PROPERTY_KEY = 'Property';
6610      var DELAY_KEY = 'Delay';
6611      var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount';
6612      var NG_ANIMATE_PARENT_KEY = '$$ngAnimateKey';
6613      var NG_ANIMATE_CSS_DATA_KEY = '$$ngAnimateCSS3Data';
6614      var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
6615      var CLOSING_TIME_BUFFER = 1.5;
6616      var ONE_SECOND = 1000;
6617
6618      var animationCounter = 0;
6619      var lookupCache = {};
6620      var parentCounter = 0;
6621      var animationReflowQueue = [];
6622      var animationElementQueue = [];
6623      var animationTimer;
6624      var closingAnimationTime = 0;
6625      var timeOut = false;
6626      function afterReflow(element, callback) {
6627        $timeout.cancel(animationTimer);
6628
6629        animationReflowQueue.push(callback);
6630
6631        var node = extractElementNode(element);
6632        element = angular.element(node);
6633        animationElementQueue.push(element);
6634
6635        var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
6636        closingAnimationTime = Math.max(closingAnimationTime,
6637          (elementData.maxDelay + elementData.maxDuration) * CLOSING_TIME_BUFFER * ONE_SECOND);
6638
6639        //by placing a counter we can avoid an accidental
6640        //race condition which may close an animation when
6641        //a follow-up animation is midway in its animation
6642        elementData.animationCount = animationCounter;
6643
6644        animationTimer = $timeout(function() {
6645          forEach(animationReflowQueue, function(fn) {
6646            fn();
6647          });
6648
6649          //copy the list of elements so that successive
6650          //animations won't conflict if they're added before
6651          //the closing animation timeout has run
6652          var elementQueueSnapshot = [];
6653          var animationCounterSnapshot = animationCounter;
6654          forEach(animationElementQueue, function(elm) {
6655            elementQueueSnapshot.push(elm);
6656          });
6657
6658          $timeout(function() {
6659            closeAllAnimations(elementQueueSnapshot, animationCounterSnapshot);
6660            elementQueueSnapshot = null;
6661          }, closingAnimationTime, false);
6662
6663          animationReflowQueue = [];
6664          animationElementQueue = [];
6665          animationTimer = null;
6666          lookupCache = {};
6667          closingAnimationTime = 0;
6668          animationCounter++;
6669        }, 10, false);
6670      }
6671
6672      function closeAllAnimations(elements, count) {
6673        forEach(elements, function(element) {
6674          var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
6675          if(elementData && elementData.animationCount == count) {
6676            (elementData.closeAnimationFn || noop)();
6677          }
6678        });
6679      }
6680
6681      function getElementAnimationDetails(element, cacheKey) {
6682        var data = cacheKey ? lookupCache[cacheKey] : null;
6683        if(!data) {
6684          var transitionDuration = 0;
6685          var transitionDelay = 0;
6686          var animationDuration = 0;
6687          var animationDelay = 0;
6688          var transitionDelayStyle;
6689          var animationDelayStyle;
6690          var transitionDurationStyle;
6691          var transitionPropertyStyle;
6692
6693          //we want all the styles defined before and after
6694          forEach(element, function(element) {
6695            if (element.nodeType == ELEMENT_NODE) {
6696              var elementStyles = $window.getComputedStyle(element) || {};
6697
6698              transitionDurationStyle = elementStyles[TRANSITION_PROP + DURATION_KEY];
6699
6700              transitionDuration = Math.max(parseMaxTime(transitionDurationStyle), transitionDuration);
6701
6702              transitionPropertyStyle = elementStyles[TRANSITION_PROP + PROPERTY_KEY];
6703
6704              transitionDelayStyle = elementStyles[TRANSITION_PROP + DELAY_KEY];
6705
6706              transitionDelay  = Math.max(parseMaxTime(transitionDelayStyle), transitionDelay);
6707
6708              animationDelayStyle = elementStyles[ANIMATION_PROP + DELAY_KEY];
6709
6710              animationDelay   = Math.max(parseMaxTime(animationDelayStyle), animationDelay);
6711
6712              var aDuration  = parseMaxTime(elementStyles[ANIMATION_PROP + DURATION_KEY]);
6713
6714              if(aDuration > 0) {
6715                aDuration *= parseInt(elementStyles[ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY], 10) || 1;
6716              }
6717
6718              animationDuration = Math.max(aDuration, animationDuration);
6719            }
6720          });
6721          data = {
6722            total : 0,
6723            transitionPropertyStyle: transitionPropertyStyle,
6724            transitionDurationStyle: transitionDurationStyle,
6725            transitionDelayStyle: transitionDelayStyle,
6726            transitionDelay: transitionDelay,
6727            transitionDuration: transitionDuration,
6728            animationDelayStyle: animationDelayStyle,
6729            animationDelay: animationDelay,
6730            animationDuration: animationDuration
6731          };
6732          if(cacheKey) {
6733            lookupCache[cacheKey] = data;
6734          }
6735        }
6736        return data;
6737      }
6738
6739      function parseMaxTime(str) {
6740        var maxValue = 0;
6741        var values = angular.isString(str) ?
6742          str.split(/\s*,\s*/) :
6743          [];
6744        forEach(values, function(value) {
6745          maxValue = Math.max(parseFloat(value) || 0, maxValue);
6746        });
6747        return maxValue;
6748      }
6749
6750      function getCacheKey(element) {
6751        var parentElement = element.parent();
6752        var parentID = parentElement.data(NG_ANIMATE_PARENT_KEY);
6753        if(!parentID) {
6754          parentElement.data(NG_ANIMATE_PARENT_KEY, ++parentCounter);
6755          parentID = parentCounter;
6756        }
6757        return parentID + '-' + extractElementNode(element).className;
6758      }
6759
6760      function animateSetup(element, className) {
6761        var cacheKey = getCacheKey(element);
6762        var eventCacheKey = cacheKey + ' ' + className;
6763        var stagger = {};
6764        var ii = lookupCache[eventCacheKey] ? ++lookupCache[eventCacheKey].total : 0;
6765
6766        if(ii > 0) {
6767          var staggerClassName = className + '-stagger';
6768          var staggerCacheKey = cacheKey + ' ' + staggerClassName;
6769          var applyClasses = !lookupCache[staggerCacheKey];
6770
6771          applyClasses && element.addClass(staggerClassName);
6772
6773          stagger = getElementAnimationDetails(element, staggerCacheKey);
6774
6775          applyClasses && element.removeClass(staggerClassName);
6776        }
6777
6778        element.addClass(className);
6779
6780        var timings = getElementAnimationDetails(element, eventCacheKey);
6781
6782        /* there is no point in performing a reflow if the animation
6783           timeout is empty (this would cause a flicker bug normally
6784           in the page. There is also no point in performing an animation
6785           that only has a delay and no duration */
6786        var maxDelay = Math.max(timings.transitionDelay, timings.animationDelay);
6787        var maxDuration = Math.max(timings.transitionDuration, timings.animationDuration);
6788        if(maxDuration === 0) {
6789          element.removeClass(className);
6790          return false;
6791        }
6792
6793        //temporarily disable the transition so that the enter styles
6794        //don't animate twice (this is here to avoid a bug in Chrome/FF).
6795        var activeClassName = '';
6796        timings.transitionDuration > 0 ?
6797          blockTransitions(element) :
6798          blockKeyframeAnimations(element);
6799
6800        forEach(className.split(' '), function(klass, i) {
6801          activeClassName += (i > 0 ? ' ' : '') + klass + '-active';
6802        });
6803
6804        element.data(NG_ANIMATE_CSS_DATA_KEY, {
6805          className : className,
6806          activeClassName : activeClassName,
6807          maxDuration : maxDuration,
6808          maxDelay : maxDelay,
6809          classes : className + ' ' + activeClassName,
6810          timings : timings,
6811          stagger : stagger,
6812          ii : ii
6813        });
6814
6815        return true;
6816      }
6817
6818      function blockTransitions(element) {
6819        extractElementNode(element).style[TRANSITION_PROP + PROPERTY_KEY] = 'none';
6820      }
6821
6822      function blockKeyframeAnimations(element) {
6823        extractElementNode(element).style[ANIMATION_PROP] = 'none 0s';
6824      }
6825
6826      function unblockTransitions(element) {
6827        var prop = TRANSITION_PROP + PROPERTY_KEY;
6828        var node = extractElementNode(element);
6829        if(node.style[prop] && node.style[prop].length > 0) {
6830          node.style[prop] = '';
6831        }
6832      }
6833
6834      function unblockKeyframeAnimations(element) {
6835        var prop = ANIMATION_PROP;
6836        var node = extractElementNode(element);
6837        if(node.style[prop] && node.style[prop].length > 0) {
6838          node.style[prop] = '';
6839        }
6840      }
6841
6842      function animateRun(element, className, activeAnimationComplete) {
6843        var elementData = element.data(NG_ANIMATE_CSS_DATA_KEY);
6844        var node = extractElementNode(element);
6845        if(node.className.indexOf(className) == -1 || !elementData) {
6846          activeAnimationComplete();
6847          return;
6848        }
6849
6850        var timings = elementData.timings;
6851        var stagger = elementData.stagger;
6852        var maxDuration = elementData.maxDuration;
6853        var activeClassName = elementData.activeClassName;
6854        var maxDelayTime = Math.max(timings.transitionDelay, timings.animationDelay) * ONE_SECOND;
6855        var startTime = Date.now();
6856        var css3AnimationEvents = ANIMATIONEND_EVENT + ' ' + TRANSITIONEND_EVENT;
6857        var ii = elementData.ii;
6858
6859        var style = '', appliedStyles = [];
6860        if(timings.transitionDuration > 0) {
6861          var propertyStyle = timings.transitionPropertyStyle;
6862          if(propertyStyle.indexOf('all') == -1) {
6863            style += CSS_PREFIX + 'transition-property: ' + propertyStyle + ';';
6864            style += CSS_PREFIX + 'transition-duration: ' + timings.transitionDurationStyle + 's;';
6865            appliedStyles.push(CSS_PREFIX + 'transition-property');
6866            appliedStyles.push(CSS_PREFIX + 'transition-duration');
6867          }
6868        }
6869
6870        if(ii > 0) {
6871          if(stagger.transitionDelay > 0 && stagger.transitionDuration === 0) {
6872            var delayStyle = timings.transitionDelayStyle;
6873            style += CSS_PREFIX + 'transition-delay: ' +
6874                     prepareStaggerDelay(delayStyle, stagger.transitionDelay, ii) + '; ';
6875            appliedStyles.push(CSS_PREFIX + 'transition-delay');
6876          }
6877
6878          if(stagger.animationDelay > 0 && stagger.animationDuration === 0) {
6879            style += CSS_PREFIX + 'animation-delay: ' +
6880                     prepareStaggerDelay(timings.animationDelayStyle, stagger.animationDelay, ii) + '; ';
6881            appliedStyles.push(CSS_PREFIX + 'animation-delay');
6882          }
6883        }
6884
6885        if(appliedStyles.length > 0) {
6886          //the element being animated may sometimes contain comment nodes in
6887          //the jqLite object, so we're safe to use a single variable to house
6888          //the styles since there is always only one element being animated
6889          var oldStyle = node.getAttribute('style') || '';
6890          node.setAttribute('style', oldStyle + ' ' + style);
6891        }
6892
6893        element.on(css3AnimationEvents, onAnimationProgress);
6894        element.addClass(activeClassName);
6895        elementData.closeAnimationFn = function() {
6896          onEnd();
6897          activeAnimationComplete();
6898        };
6899        return onEnd;
6900
6901        // This will automatically be called by $animate so
6902        // there is no need to attach this internally to the
6903        // timeout done method.
6904        function onEnd(cancelled) {
6905          element.off(css3AnimationEvents, onAnimationProgress);
6906          element.removeClass(activeClassName);
6907          animateClose(element, className);
6908          var node = extractElementNode(element);
6909          for (var i in appliedStyles) {
6910            node.style.removeProperty(appliedStyles[i]);
6911          }
6912        }
6913
6914        function onAnimationProgress(event) {
6915          event.stopPropagation();
6916          var ev = event.originalEvent || event;
6917          var timeStamp = ev.$manualTimeStamp || ev.timeStamp || Date.now();
6918          
6919          /* Firefox (or possibly just Gecko) likes to not round values up
6920           * when a ms measurement is used for the animation */
6921          var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
6922
6923          /* $manualTimeStamp is a mocked timeStamp value which is set
6924           * within browserTrigger(). This is only here so that tests can
6925           * mock animations properly. Real events fallback to event.timeStamp,
6926           * or, if they don't, then a timeStamp is automatically created for them.
6927           * We're checking to see if the timeStamp surpasses the expected delay,
6928           * but we're using elapsedTime instead of the timeStamp on the 2nd
6929           * pre-condition since animations sometimes close off early */
6930          if(Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
6931            activeAnimationComplete();
6932          }
6933        }
6934      }
6935
6936      function prepareStaggerDelay(delayStyle, staggerDelay, index) {
6937        var style = '';
6938        forEach(delayStyle.split(','), function(val, i) {
6939          style += (i > 0 ? ',' : '') +
6940                   (index * staggerDelay + parseInt(val, 10)) + 's';
6941        });
6942        return style;
6943      }
6944
6945      function animateBefore(element, className) {
6946        if(animateSetup(element, className)) {
6947          return function(cancelled) {
6948            cancelled && animateClose(element, className);
6949          };
6950        }
6951      }
6952
6953      function animateAfter(element, className, afterAnimationComplete) {
6954        if(element.data(NG_ANIMATE_CSS_DATA_KEY)) {
6955          return animateRun(element, className, afterAnimationComplete);
6956        } else {
6957          animateClose(element, className);
6958          afterAnimationComplete();
6959        }
6960      }
6961
6962      function animate(element, className, animationComplete) {
6963        //If the animateSetup function doesn't bother returning a
6964        //cancellation function then it means that there is no animation
6965        //to perform at all
6966        var preReflowCancellation = animateBefore(element, className);
6967        if(!preReflowCancellation) {
6968          animationComplete();
6969          return;
6970        }
6971
6972        //There are two cancellation functions: one is before the first
6973        //reflow animation and the second is during the active state
6974        //animation. The first function will take care of removing the
6975        //data from the element which will not make the 2nd animation
6976        //happen in the first place
6977        var cancel = preReflowCancellation;
6978        afterReflow(element, function() {
6979          unblockTransitions(element);
6980          unblockKeyframeAnimations(element);
6981          //once the reflow is complete then we point cancel to
6982          //the new cancellation function which will remove all of the
6983          //animation properties from the active animation
6984          cancel = animateAfter(element, className, animationComplete);
6985        });
6986
6987        return function(cancelled) {
6988          (cancel || noop)(cancelled);
6989        };
6990      }
6991
6992      function animateClose(element, className) {
6993        element.removeClass(className);
6994        element.removeData(NG_ANIMATE_CSS_DATA_KEY);
6995      }
6996
6997      return {
6998        allowCancel : function(element, animationEvent, className) {
6999          //always cancel the current animation if it is a
7000          //structural animation
7001          var oldClasses = (element.data(NG_ANIMATE_CSS_DATA_KEY) || {}).classes;
7002          if(!oldClasses || ['enter','leave','move'].indexOf(animationEvent) >= 0) {
7003            return true;
7004          }
7005
7006          var parentElement = element.parent();
7007          var clone = angular.element(extractElementNode(element).cloneNode());
7008
7009          //make the element super hidden and override any CSS style values
7010          clone.attr('style','position:absolute; top:-9999px; left:-9999px');
7011          clone.removeAttr('id');
7012          clone.empty();
7013
7014          forEach(oldClasses.split(' '), function(klass) {
7015            clone.removeClass(klass);
7016          });
7017
7018          var suffix = animationEvent == 'addClass' ? '-add' : '-remove';
7019          clone.addClass(suffixClasses(className, suffix));
7020          parentElement.append(clone);
7021
7022          var timings = getElementAnimationDetails(clone);
7023          clone.remove();
7024
7025          return Math.max(timings.transitionDuration, timings.animationDuration) > 0;
7026        },
7027
7028        enter : function(element, animationCompleted) {
7029          return animate(element, 'ng-enter', animationCompleted);
7030        },
7031
7032        leave : function(element, animationCompleted) {
7033          return animate(element, 'ng-leave', animationCompleted);
7034        },
7035
7036        move : function(element, animationCompleted) {
7037          return animate(element, 'ng-move', animationCompleted);
7038        },
7039
7040        beforeAddClass : function(element, className, animationCompleted) {
7041          var cancellationMethod = animateBefore(element, suffixClasses(className, '-add'));
7042          if(cancellationMethod) {
7043            afterReflow(element, function() {
7044              unblockTransitions(element);
7045              unblockKeyframeAnimations(element);
7046              animationCompleted();
7047            });
7048            return cancellationMethod;
7049          }
7050          animationCompleted();
7051        },
7052
7053        addClass : function(element, className, animationCompleted) {
7054          return animateAfter(element, suffixClasses(className, '-add'), animationCompleted);
7055        },
7056
7057        beforeRemoveClass : function(element, className, animationCompleted) {
7058          var cancellationMethod = animateBefore(element, suffixClasses(className, '-remove'));
7059          if(cancellationMethod) {
7060            afterReflow(element, function() {
7061              unblockTransitions(element);
7062              unblockKeyframeAnimations(element);
7063              animationCompleted();
7064            });
7065            return cancellationMethod;
7066          }
7067          animationCompleted();
7068        },
7069
7070        removeClass : function(element, className, animationCompleted) {
7071          return animateAfter(element, suffixClasses(className, '-remove'), animationCompleted);
7072        }
7073      };
7074
7075      function suffixClasses(classes, suffix) {
7076        var className = '';
7077        classes = angular.isArray(classes) ? classes : classes.split(/\s+/);
7078        forEach(classes, function(klass, i) {
7079          if(klass && klass.length > 0) {
7080            className += (i > 0 ? ' ' : '') + klass + suffix;
7081          }
7082        });
7083        return className;
7084      }
7085    }]);
7086  }]);
7087
7088
7089})(window, window.angular);
7090
7091/**
7092 * @license AngularJS v1.2.7
7093 * (c) 2010-2014 Google, Inc. http://angularjs.org
7094 * License: MIT
7095 */
7096(function(window, angular, undefined) {'use strict';
7097
7098/**
7099 * @ngdoc overview
7100 * @name ngTouch
7101 * @description
7102 *
7103 * # ngTouch
7104 *
7105 * The `ngTouch` module provides touch events and other helpers for touch-enabled devices.
7106 * The implementation is based on jQuery Mobile touch event handling 
7107 * ([jquerymobile.com](http://jquerymobile.com/)).
7108 *
7109 * {@installModule touch}
7110 *
7111 * See {@link ngTouch.$swipe `$swipe`} for usage.
7112 *
7113 * <div doc-module-components="ngTouch"></div>
7114 *
7115 */
7116
7117// define ngTouch module
7118/* global -ngTouch */
7119var ngTouch = angular.module('ngTouch', []);
7120
7121/* global ngTouch: false */
7122
7123    /**
7124     * @ngdoc object
7125     * @name ngTouch.$swipe
7126     *
7127     * @description
7128     * The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe
7129     * behavior, to make implementing swipe-related directives more convenient.
7130     *
7131     * Requires the {@link ngTouch `ngTouch`} module to be installed.
7132     *
7133     * `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by
7134     * `ngCarousel` in a separate component.
7135     *
7136     * # Usage
7137     * The `$swipe` service is an object with a single method: `bind`. `bind` takes an element
7138     * which is to be watched for swipes, and an object with four handler functions. See the
7139     * documentation for `bind` below.
7140     */
7141
7142ngTouch.factory('$swipe', [function() {
7143  // The total distance in any direction before we make the call on swipe vs. scroll.
7144  var MOVE_BUFFER_RADIUS = 10;
7145
7146  function getCoordinates(event) {
7147    var touches = event.touches && event.touches.length ? event.touches : [event];
7148    var e = (event.changedTouches && event.changedTouches[0]) ||
7149        (event.originalEvent && event.originalEvent.changedTouches &&
7150            event.originalEvent.changedTouches[0]) ||
7151        touches[0].originalEvent || touches[0];
7152
7153    return {
7154      x: e.clientX,
7155      y: e.clientY
7156    };
7157  }
7158
7159  return {
7160    /**
7161     * @ngdoc method
7162     * @name ngTouch.$swipe#bind
7163     * @methodOf ngTouch.$swipe
7164     *
7165     * @description
7166     * The main method of `$swipe`. It takes an element to be watched for swipe motions, and an
7167     * object containing event handlers.
7168     *
7169     * The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end`
7170     * receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`.
7171     *
7172     * `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is
7173     * watching for `touchmove` or `mousemove` events. These events are ignored until the total
7174     * distance moved in either dimension exceeds a small threshold.
7175     *
7176     * Once this threshold is exceeded, either the horizontal or vertical delta is greater.
7177     * - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow.
7178     * - If the vertical distance is greater, this is a scroll, and we let the browser take over.
7179     *   A `cancel` event is sent.
7180     *
7181     * `move` is called on `mousemove` and `touchmove` after the above logic has determined that
7182     * a swipe is in progress.
7183     *
7184     * `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`.
7185     *
7186     * `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling
7187     * as described above.
7188     *
7189     */
7190    bind: function(element, eventHandlers) {
7191      // Absolute total movement, used to control swipe vs. scroll.
7192      var totalX, totalY;
7193      // Coordinates of the start position.
7194      var startCoords;
7195      // Last event's position.
7196      var lastPos;
7197      // Whether a swipe is active.
7198      var active = false;
7199
7200      element.on('touchstart mousedown', function(event) {
7201        startCoords = getCoordinates(event);
7202        active = true;
7203        totalX = 0;
7204        totalY = 0;
7205        lastPos = startCoords;
7206        eventHandlers['start'] && eventHandlers['start'](startCoords, event);
7207      });
7208
7209      element.on('touchcancel', function(event) {
7210        active = false;
7211        eventHandlers['cancel'] && eventHandlers['cancel'](event);
7212      });
7213
7214      element.on('touchmove mousemove', function(event) {
7215        if (!active) return;
7216
7217        // Android will send a touchcancel if it thinks we're starting to scroll.
7218        // So when the total distance (+ or - or both) exceeds 10px in either direction,
7219        // we either:
7220        // - On totalX > totalY, we send preventDefault() and treat this as a swipe.
7221        // - On totalY > totalX, we let the browser handle it as a scroll.
7222
7223        if (!startCoords) return;
7224        var coords = getCoordinates(event);
7225
7226        totalX += Math.abs(coords.x - lastPos.x);
7227        totalY += Math.abs(coords.y - lastPos.y);
7228
7229        lastPos = coords;
7230
7231        if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) {
7232          return;
7233        }
7234
7235        // One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
7236        if (totalY > totalX) {
7237          // Allow native scrolling to take over.
7238          active = false;
7239          eventHandlers['cancel'] && eventHandlers['cancel'](event);
7240          return;
7241        } else {
7242          // Prevent the browser from scrolling.
7243          event.preventDefault();
7244          eventHandlers['move'] && eventHandlers['move'](coords, event);
7245        }
7246      });
7247
7248      element.on('touchend mouseup', function(event) {
7249        if (!active) return;
7250        active = false;
7251        eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event);
7252      });
7253    }
7254  };
7255}]);
7256
7257/* global ngTouch: false */
7258
7259/**
7260 * @ngdoc directive
7261 * @name ngTouch.directive:ngClick
7262 *
7263 * @description
7264 * A more powerful replacement for the default ngClick designed to be used on touchscreen
7265 * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending
7266 * the click event. This version handles them immediately, and then prevents the
7267 * following click event from propagating.
7268 *
7269 * Requires the {@link ngTouch `ngTouch`} module to be installed.
7270 *
7271 * This directive can fall back to using an ordinary click event, and so works on desktop
7272 * browsers as well as mobile.
7273 *
7274 * This directive also sets the CSS class `ng-click-active` while the element is being held
7275 * down (by a mouse click or touch) so you can restyle the depressed element if you wish.
7276 *
7277 * @element ANY
7278 * @param {expression} ngClick {@link guide/expression Expression} to evaluate
7279 * upon tap. (Event object is available as `$event`)
7280 *
7281 * @example
7282    <doc:example>
7283      <doc:source>
7284        <button ng-click="count = count + 1" ng-init="count=0">
7285          Increment
7286        </button>
7287        count: {{ count }}
7288      </doc:source>
7289    </doc:example>
7290 */
7291
7292ngTouch.config(['$provide', function($provide) {
7293  $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
7294    // drop the default ngClick directive
7295    $delegate.shift();
7296    return $delegate;
7297  }]);
7298}]);
7299
7300ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
7301    function($parse, $timeout, $rootElement) {
7302  var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
7303  var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
7304  var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
7305  var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
7306
7307  var ACTIVE_CLASS_NAME = 'ng-click-active';
7308  var lastPreventedTime;
7309  var touchCoordinates;
7310
7311
7312  // TAP EVENTS AND GHOST CLICKS
7313  //
7314  // Why tap events?
7315  // Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
7316  // double-tapping, and then fire a click event.
7317  //
7318  // This delay sucks and makes mobile apps feel unresponsive.
7319  // So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
7320  // the user has tapped on something.
7321  //
7322  // What happens when the browser then generates a click event?
7323  // The browser, of course, also detects the tap and fires a click after a delay. This results in
7324  // tapping/clicking twice. So we do "clickbusting" to prevent it.
7325  //
7326  // How does it work?
7327  // We attach global touchstart and click handlers, that run during the capture (early) phase.
7328  // So the sequence for a tap is:
7329  // - global touchstart: Sets an "allowable region" at the point touched.
7330  // - element's touchstart: Starts a touch
7331  // (- touchmove or touchcancel ends the touch, no click follows)
7332  // - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
7333  //   too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
7334  // - preventGhostClick() removes the allowable region the global touchstart created.
7335  // - The browser generates a click event.
7336  // - The global click handler catches the click, and checks whether it was in an allowable region.
7337  //     - If preventGhostClick was called, the region will have been removed, the click is busted.
7338  //     - If the region is still there, the click proceeds normally. Therefore clicks on links and
7339  //       other elements without ngTap on them work normally.
7340  //
7341  // This is an ugly, terrible hack!
7342  // Yeah, tell me about it. The alternatives are using the slow click events, or making our users
7343  // deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
7344  // encapsulates this ugly logic away from the user.
7345  //
7346  // Why not just put click handlers on the element?
7347  // We do that too, just to be sure. The problem is that the tap event might have caused the DOM
7348  // to change, so that the click fires in the same position but something else is there now. So
7349  // the handlers are global and care only about coordinates and not elements.
7350
7351  // Checks if the coordinates are close enough to be within the region.
7352  function hit(x1, y1, x2, y2) {
7353    return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD;
7354  }
7355
7356  // Checks a list of allowable regions against a click location.
7357  // Returns true if the click should be allowed.
7358  // Splices out the allowable region from the list after it has been used.
7359  function checkAllowableRegions(touchCoordinates, x, y) {
7360    for (var i = 0; i < touchCoordinates.length; i += 2) {
7361      if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) {
7362        touchCoordinates.splice(i, i + 2);
7363        return true; // allowable region
7364      }
7365    }
7366    return false; // No allowable region; bust it.
7367  }
7368
7369  // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
7370  // was called recently.
7371  function onClick(event) {
7372    if (Date.now() - lastPreventedTime > PREVENT_DURATION) {
7373      return; // Too old.
7374    }
7375
7376    var touches = event.touches && event.touches.length ? event.touches : [event];
7377    var x = touches[0].clientX;
7378    var y = touches[0].clientY;
7379    // Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
7380    // and on the input element). Depending on the exact browser, this second click we don't want
7381    // to bust has either (0,0) or negative coordinates.
7382    if (x < 1 && y < 1) {
7383      return; // offscreen
7384    }
7385
7386    // Look for an allowable region containing this click.
7387    // If we find one, that means it was created by touchstart and not removed by
7388    // preventGhostClick, so we don't bust it.
7389    if (checkAllowableRegions(touchCoordinates, x, y)) {
7390      return;
7391    }
7392
7393    // If we didn't find an allowable region, bust the click.
7394    event.stopPropagation();
7395    event.preventDefault();
7396
7397    // Blur focused form elements
7398    event.target && event.target.blur();
7399  }
7400
7401
7402  // Global touchstart handler that creates an allowable region for a click event.
7403  // This allowable region can be removed by preventGhostClick if we want to bust it.
7404  function onTouchStart(event) {
7405    var touches = event.touches && event.touches.length ? event.touches : [event];
7406    var x = touches[0].clientX;
7407    var y = touches[0].clientY;
7408    touchCoordinates.push(x, y);
7409
7410    $timeout(function() {
7411      // Remove the allowable region.
7412      for (var i = 0; i < touchCoordinates.length; i += 2) {
7413        if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) {
7414          touchCoordinates.splice(i, i + 2);
7415          return;
7416        }
7417      }
7418    }, PREVENT_DURATION, false);
7419  }
7420
7421  // On the first call, attaches some event handlers. Then whenever it gets called, it creates a
7422  // zone around the touchstart where clicks will get busted.
7423  function preventGhostClick(x, y) {
7424    if (!touchCoordinates) {
7425      $rootElement[0].addEventListener('click', onClick, true);
7426      $rootElement[0].addEventListener('touchstart', onTouchStart, true);
7427      touchCoordinates = [];
7428    }
7429
7430    lastPreventedTime = Date.now();
7431
7432    checkAllowableRegions(touchCoordinates, x, y);
7433  }
7434
7435  // Actual linking function.
7436  return function(scope, element, attr) {
7437    var clickHandler = $parse(attr.ngClick),
7438        tapping = false,
7439        tapElement,  // Used to blur the element after a tap.
7440        startTime,   // Used to check if the tap was held too long.
7441        touchStartX,
7442        touchStartY;
7443
7444    function resetState() {
7445      tapping = false;
7446      element.removeClass(ACTIVE_CLASS_NAME);
7447    }
7448
7449    element.on('touchstart', function(event) {
7450      tapping = true;
7451      tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
7452      // Hack for Safari, which can target text nodes instead of containers.
7453      if(tapElement.nodeType == 3) {
7454        tapElement = tapElement.parentNode;
7455      }
7456
7457      element.addClass(ACTIVE_CLASS_NAME);
7458
7459      startTime = Date.now();
7460
7461      var touches = event.touches && event.touches.length ? event.touches : [event];
7462      var e = touches[0].originalEvent || touches[0];
7463      touchStartX = e.clientX;
7464      touchStartY = e.clientY;
7465    });
7466
7467    element.on('touchmove', function(event) {
7468      resetState();
7469    });
7470
7471    element.on('touchcancel', function(event) {
7472      resetState();
7473    });
7474
7475    element.on('touchend', function(event) {
7476      var diff = Date.now() - startTime;
7477
7478      var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
7479          ((event.touches && event.touches.length) ? event.touches : [event]);
7480      var e = touches[0].originalEvent || touches[0];
7481      var x = e.clientX;
7482      var y = e.clientY;
7483      var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) );
7484
7485      if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) {
7486        // Call preventGhostClick so the clickbuster will catch the corresponding click.
7487        preventGhostClick(x, y);
7488
7489        // Blur the focused element (the button, probably) before firing the callback.
7490        // This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
7491        // I couldn't get anything to work reliably on Android Chrome.
7492        if (tapElement) {
7493          tapElement.blur();
7494        }
7495
7496        if (!angular.isDefined(attr.disabled) || attr.disabled === false) {
7497          element.triggerHandler('click', [event]);
7498        }
7499      }
7500
7501      resetState();
7502    });
7503
7504    // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
7505    // something else nearby.
7506    element.onclick = function(event) { };
7507
7508    // Actual click handler.
7509    // There are three different kinds of clicks, only two of which reach this point.
7510    // - On desktop browsers without touch events, their clicks will always come here.
7511    // - On mobile browsers, the simulated "fast" click will call this.
7512    // - But the browser's follow-up slow click will be "busted" before it reaches this handler.
7513    // Therefore it's safe to use this directive on both mobile and desktop.
7514    element.on('click', function(event, touchend) {
7515      scope.$apply(function() {
7516        clickHandler(scope, {$event: (touchend || event)});
7517      });
7518    });
7519
7520    element.on('mousedown', function(event) {
7521      element.addClass(ACTIVE_CLASS_NAME);
7522    });
7523
7524    element.on('mousemove mouseup', function(event) {
7525      element.removeClass(ACTIVE_CLASS_NAME);
7526    });
7527
7528  };
7529}]);
7530
7531/* global ngTouch: false */
7532
7533/**
7534 * @ngdoc directive
7535 * @name ngTouch.directive:ngSwipeLeft
7536 *
7537 * @description
7538 * Specify custom behavior when an element is swiped to the left on a touchscreen device.
7539 * A leftward swipe is a quick, right-to-left slide of the finger.
7540 * Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag
7541 * too.
7542 *
7543 * Requires the {@link ngTouch `ngTouch`} module to be installed.
7544 *
7545 * @element ANY
7546 * @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate
7547 * upon left swipe. (Event object is available as `$event`)
7548 *
7549 * @example
7550    <doc:example>
7551      <doc:source>
7552        <div ng-show="!showActions" ng-swipe-left="showActions = true">
7553          Some list content, like an email in the inbox
7554        </div>
7555        <div ng-show="showActions" ng-swipe-right="showActions = false">
7556          <button ng-click="reply()">Reply</button>
7557          <button ng-click="delete()">Delete</button>
7558        </div>
7559      </doc:source>
7560    </doc:example>
7561 */
7562
7563/**
7564 * @ngdoc directive
7565 * @name ngTouch.directive:ngSwipeRight
7566 *
7567 * @description
7568 * Specify custom behavior when an element is swiped to the right on a touchscreen device.
7569 * A rightward swipe is a quick, left-to-right slide of the finger.
7570 * Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag
7571 * too.
7572 *
7573 * Requires the {@link ngTouch `ngTouch`} module to be installed.
7574 *
7575 * @element ANY
7576 * @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate
7577 * upon right swipe. (Event object is available as `$event`)
7578 *
7579 * @example
7580    <doc:example>
7581      <doc:source>
7582        <div ng-show="!showActions" ng-swipe-left="showActions = true">
7583          Some list content, like an email in the inbox
7584        </div>
7585        <div ng-show="showActions" ng-swipe-right="showActions = false">
7586          <button ng-click="reply()">Reply</button>
7587          <button ng-click="delete()">Delete</button>
7588        </div>
7589      </doc:source>
7590    </doc:example>
7591 */
7592
7593function makeSwipeDirective(directiveName, direction, eventName) {
7594  ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) {
7595    // The maximum vertical delta for a swipe should be less than 75px.
7596    var MAX_VERTICAL_DISTANCE = 75;
7597    // Vertical distance should not be more than a fraction of the horizontal distance.
7598    var MAX_VERTICAL_RATIO = 0.3;
7599    // At least a 30px lateral motion is necessary for a swipe.
7600    var MIN_HORIZONTAL_DISTANCE = 30;
7601
7602    return function(scope, element, attr) {
7603      var swipeHandler = $parse(attr[directiveName]);
7604
7605      var startCoords, valid;
7606
7607      function validSwipe(coords) {
7608        // Check that it's within the coordinates.
7609        // Absolute vertical distance must be within tolerances.
7610        // Horizontal distance, we take the current X - the starting X.
7611        // This is negative for leftward swipes and positive for rightward swipes.
7612        // After multiplying by the direction (-1 for left, +1 for right), legal swipes
7613        // (ie. same direction as the directive wants) will have a positive delta and
7614        // illegal ones a negative delta.
7615        // Therefore this delta must be positive, and larger than the minimum.
7616        if (!startCoords) return false;
7617        var deltaY = Math.abs(coords.y - startCoords.y);
7618        var deltaX = (coords.x - startCoords.x) * direction;
7619        return valid && // Short circuit for already-invalidated swipes.
7620            deltaY < MAX_VERTICAL_DISTANCE &&
7621            deltaX > 0 &&
7622            deltaX > MIN_HORIZONTAL_DISTANCE &&
7623            deltaY / deltaX < MAX_VERTICAL_RATIO;
7624      }
7625
7626      $swipe.bind(element, {
7627        'start': function(coords, event) {
7628          startCoords = coords;
7629          valid = true;
7630        },
7631        'cancel': function(event) {
7632          valid = false;
7633        },
7634        'end': function(coords, event) {
7635          if (validSwipe(coords)) {
7636            scope.$apply(function() {
7637              element.triggerHandler(eventName);
7638              swipeHandler(scope, {$event: event});
7639            });
7640          }
7641        }
7642      });
7643    };
7644  }]);
7645}
7646
7647// Left is negative X-coordinate, right is positive.
7648makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft');
7649makeSwipeDirective('ngSwipeRight', 1, 'swiperight');
7650
7651
7652
7653})(window, window.angular);
7654
7655/*!
7656* screenfull
7657* v1.1.1 - 2013-11-20
7658* https://github.com/sindresorhus/screenfull.js
7659* (c) Sindre Sorhus; MIT License
7660*/
7661/*global Element */
7662(function (window, document) {
7663	'use strict';
7664
7665	var keyboardAllowed = typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element, // IE6 throws without typeof check
7666
7667		fn = (function () {
7668			var val, valLength;
7669			var fnMap = [
7670				[
7671					'requestFullscreen',
7672					'exitFullscreen',
7673					'fullscreenElement',
7674					'fullscreenEnabled',
7675					'fullscreenchange',
7676					'fullscreenerror'
7677				],
7678				// new WebKit
7679				[
7680					'webkitRequestFullscreen',
7681					'webkitExitFullscreen',
7682					'webkitFullscreenElement',
7683					'webkitFullscreenEnabled',
7684					'webkitfullscreenchange',
7685					'webkitfullscreenerror'
7686
7687				],
7688				// old WebKit (Safari 5.1)
7689				[
7690					'webkitRequestFullScreen',
7691					'webkitCancelFullScreen',
7692					'webkitCurrentFullScreenElement',
7693					'webkitCancelFullScreen',
7694					'webkitfullscreenchange',
7695					'webkitfullscreenerror'
7696
7697				],
7698				[
7699					'mozRequestFullScreen',
7700					'mozCancelFullScreen',
7701					'mozFullScreenElement',
7702					'mozFullScreenEnabled',
7703					'mozfullscreenchange',
7704					'mozfullscreenerror'
7705				],
7706				[
7707					'msRequestFullscreen',
7708					'msExitFullscreen',
7709					'msFullscreenElement',
7710					'msFullscreenEnabled',
7711					'MSFullscreenChange',
7712					'MSFullscreenError'
7713				]
7714			];
7715			var i = 0;
7716			var l = fnMap.length;
7717			var ret = {};
7718
7719			for (; i < l; i++) {
7720				val = fnMap[i];
7721				if (val && val[1] in document) {
7722					for (i = 0, valLength = val.length; i < valLength; i++) {
7723						ret[fnMap[0][i]] = val[i];
7724					}
7725					return ret;
7726				}
7727			}
7728			return false;
7729		})(),
7730
7731		screenfull = {
7732			request: function (elem) {
7733				var request = fn.requestFullscreen;
7734
7735				elem = elem || document.documentElement;
7736
7737				// Work around Safari 5.1 bug: reports support for
7738				// keyboard in fullscreen even though it doesn't.
7739				// Browser sniffing, since the alternative with
7740				// setTimeout is even worse.
7741				if (/5\.1[\.\d]* Safari/.test(navigator.userAgent)) {
7742					elem[request]();
7743				} else {
7744					elem[request](keyboardAllowed && Element.ALLOW_KEYBOARD_INPUT);
7745				}
7746			},
7747			exit: function () {
7748				document[fn.exitFullscreen]();
7749			},
7750			toggle: function (elem) {
7751				if (this.isFullscreen) {
7752					this.exit();
7753				} else {
7754					this.request(elem);
7755				}
7756			},
7757			onchange: function () {},
7758			onerror: function () {},
7759			raw: fn
7760		};
7761
7762	if (!fn) {
7763		window.screenfull = false;
7764		return;
7765	}
7766
7767	Object.defineProperties(screenfull, {
7768		isFullscreen: {
7769			get: function () {
7770				return !!document[fn.fullscreenElement];
7771			}
7772		},
7773		element: {
7774			enumerable: true,
7775			get: function () {
7776				return document[fn.fullscreenElement];
7777			}
7778		},
7779		enabled: {
7780			enumerable: true,
7781			get: function () {
7782				// Coerce to boolean in case of old WebKit
7783				return !!document[fn.fullscreenEnabled];
7784			}
7785		}
7786	});
7787
7788	document.addEventListener(fn.fullscreenchange, function (e) {
7789		screenfull.onchange.call(screenfull, e);
7790	});
7791
7792	document.addEventListener(fn.fullscreenerror, function (e) {
7793		screenfull.onerror.call(screenfull, e);
7794	});
7795
7796	window.screenfull = screenfull;
7797})(window, document);
7798
7799/*
7800CryptoJS v3.1.2
7801code.google.com/p/crypto-js
vendor: 5,229 bytes, lines 7802-7820
7802(c) 2009-2013 by Jeff Mott. All rights reserved.
7803code.google.com/p/crypto-js/wiki/License
7804*/
7805var CryptoJS=CryptoJS||function(h,s){var f={},g=f.lib={},q=function(){},m=g.Base={extend:function(a){q.prototype=this;var c=new q;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}},
7806r=g.WordArray=m.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=s?c:4*a.length},toString:function(a){return(a||k).stringify(this)},concat:function(a){var c=this.words,d=a.words,b=this.sigBytes;a=a.sigBytes;this.clamp();if(b%4)for(var e=0;e<a;e++)c[b+e>>>2]|=(d[e>>>2]>>>24-8*(e%4)&255)<<24-8*((b+e)%4);else if(65535<d.length)for(e=0;e<a;e+=4)c[b+e>>>2]=d[e>>>2];else c.push.apply(c,d);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<<
780732-8*(c%4);a.length=h.ceil(c/4)},clone:function(){var a=m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],d=0;d<a;d+=4)c.push(4294967296*h.random()|0);return new r.init(c,a)}}),l=f.enc={},k=l.Hex={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++){var e=c[b>>>2]>>>24-8*(b%4)&255;d.push((e>>>4).toString(16));d.push((e&15).toString(16))}return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b+=2)d[b>>>3]|=parseInt(a.substr(b,
78082),16)<<24-4*(b%8);return new r.init(d,c/2)}},n=l.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var d=[],b=0;b<a;b++)d.push(String.fromCharCode(c[b>>>2]>>>24-8*(b%4)&255));return d.join("")},parse:function(a){for(var c=a.length,d=[],b=0;b<c;b++)d[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return new r.init(d,c)}},j=l.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}},
7809u=g.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=new r.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=j.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,d=c.words,b=c.sigBytes,e=this.blockSize,f=b/(4*e),f=a?h.ceil(f):h.max((f|0)-this._minBufferSize,0);a=f*e;b=h.min(4*a,b);if(a){for(var g=0;g<a;g+=e)this._doProcessBlock(d,g);g=d.splice(0,a);c.sigBytes-=b}return new r.init(g,b)},clone:function(){var a=m.clone.call(this);
7810a._data=this._data.clone();return a},_minBufferSize:0});g.Hasher=u.extend({cfg:m.extend(),init:function(a){this.cfg=this.cfg.extend(a);this.reset()},reset:function(){u.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);return this._doFinalize()},blockSize:16,_createHelper:function(a){return function(c,d){return(new a.init(d)).finalize(c)}},_createHmacHelper:function(a){return function(c,d){return(new t.HMAC.init(a,
7811d)).finalize(c)}}});var t=f.algo={};return f}(Math);
7812(function(h){for(var s=CryptoJS,f=s.lib,g=f.WordArray,q=f.Hasher,f=s.algo,m=[],r=[],l=function(a){return 4294967296*(a-(a|0))|0},k=2,n=0;64>n;){var j;a:{j=k;for(var u=h.sqrt(j),t=2;t<=u;t++)if(!(j%t)){j=!1;break a}j=!0}j&&(8>n&&(m[n]=l(h.pow(k,0.5))),r[n]=l(h.pow(k,1/3)),n++);k++}var a=[],f=f.SHA256=q.extend({_doReset:function(){this._hash=new g.init(m.slice(0))},_doProcessBlock:function(c,d){for(var b=this._hash.words,e=b[0],f=b[1],g=b[2],j=b[3],h=b[4],m=b[5],n=b[6],q=b[7],p=0;64>p;p++){if(16>p)a[p]=
7813c[d+p]|0;else{var k=a[p-15],l=a[p-2];a[p]=((k<<25|k>>>7)^(k<<14|k>>>18)^k>>>3)+a[p-7]+((l<<15|l>>>17)^(l<<13|l>>>19)^l>>>10)+a[p-16]}k=q+((h<<26|h>>>6)^(h<<21|h>>>11)^(h<<7|h>>>25))+(h&m^~h&n)+r[p]+a[p];l=((e<<30|e>>>2)^(e<<19|e>>>13)^(e<<10|e>>>22))+(e&f^e&g^f&g);q=n;n=m;m=h;h=j+k|0;j=g;g=f;f=e;e=k+l|0}b[0]=b[0]+e|0;b[1]=b[1]+f|0;b[2]=b[2]+g|0;b[3]=b[3]+j|0;b[4]=b[4]+h|0;b[5]=b[5]+m|0;b[6]=b[6]+n|0;b[7]=b[7]+q|0},_doFinalize:function(){var a=this._data,d=a.words,b=8*this._nDataBytes,e=8*a.sigBytes;
7814d[e>>>5]|=128<<24-e%32;d[(e+64>>>9<<4)+14]=h.floor(b/4294967296);d[(e+64>>>9<<4)+15]=b;a.sigBytes=4*d.length;this._process();return this._hash},clone:function(){var a=q.clone.call(this);a._hash=this._hash.clone();return a}});s.SHA256=q._createHelper(f);s.HmacSHA256=q._createHmacHelper(f)})(Math);
7815(function(){var h=CryptoJS,s=h.enc.Utf8;h.algo.HMAC=h.lib.Base.extend({init:function(f,g){f=this._hasher=new f.init;"string"==typeof g&&(g=s.parse(g));var h=f.blockSize,m=4*h;g.sigBytes>m&&(g=f.finalize(g));g.clamp();for(var r=this._oKey=g.clone(),l=this._iKey=g.clone(),k=r.words,n=l.words,j=0;j<h;j++)k[j]^=1549556828,n[j]^=909522486;r.sigBytes=l.sigBytes=m;this.reset()},reset:function(){var f=this._hasher;f.reset();f.update(this._iKey)},update:function(f){this._hasher.update(f);return this},finalize:function(f){var g=
7816this._hasher;f=g.finalize(f);g.reset();return g.finalize(this._oKey.clone().concat(f))}})})();
7817
7818/*
7819CryptoJS v3.1.2
7820code.google.com/p/crypto-js
7821(c) 2009-2013 by Jeff Mott. All rights reserved.
7822code.google.com/p/crypto-js/wiki/License
7823*/
7824(function () {
7825    // Shortcuts
7826    var C = CryptoJS;
7827    var C_lib = C.lib;
7828    var WordArray = C_lib.WordArray;
7829    var C_enc = C.enc;
7830
7831    /**
7832     * Base64 encoding strategy.
7833     */
7834    var Base64 = C_enc.Base64 = {
7835        /**
7836         * Converts a word array to a Base64 string.
7837         *
7838         * @param {WordArray} wordArray The word array.
7839         *
7840         * @return {string} The Base64 string.
7841         *
7842         * @static
7843         *
7844         * @example
7845         *
7846         *     var base64String = CryptoJS.enc.Base64.stringify(wordArray);
7847         */
7848        stringify: function (wordArray) {
7849            // Shortcuts
7850            var words = wordArray.words;
7851            var sigBytes = wordArray.sigBytes;
7852            var map = this._map;
7853
7854            // Clamp excess bits
7855            wordArray.clamp();
7856
7857            // Convert
7858            var base64Chars = [];
7859            for (var i = 0; i < sigBytes; i += 3) {
7860                var byte1 = (words[i >>> 2]       >>> (24 - (i % 4) * 8))       & 0xff;
7861                var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff;
7862                var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff;
7863
7864                var triplet = (byte1 << 16) | (byte2 << 8) | byte3;
7865
7866                for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) {
7867                    base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f));
7868                }
7869            }
7870
7871            // Add padding
7872            var paddingChar = map.charAt(64);
7873            if (paddingChar) {
7874                while (base64Chars.length % 4) {
7875                    base64Chars.push(paddingChar);
7876                }
7877            }
7878
7879            return base64Chars.join('');
7880        },
7881
7882        /**
7883         * Converts a Base64 string to a word array.
7884         *
7885         * @param {string} base64Str The Base64 string.
7886         *
7887         * @return {WordArray} The word array.
7888         *
7889         * @static
7890         *
7891         * @example
7892         *
7893         *     var wordArray = CryptoJS.enc.Base64.parse(base64String);
7894         */
7895        parse: function (base64Str) {
7896            // Shortcuts
7897            var base64StrLength = base64Str.length;
7898            var map = this._map;
7899
7900            // Ignore padding
7901            var paddingChar = map.charAt(64);
7902            if (paddingChar) {
7903                var paddingIndex = base64Str.indexOf(paddingChar);
7904                if (paddingIndex != -1) {
7905                    base64StrLength = paddingIndex;
7906                }
7907            }
7908
7909            // Convert
7910            var words = [];
7911            var nBytes = 0;
7912            for (var i = 0; i < base64StrLength; i++) {
7913                if (i % 4) {
7914                    var bits1 = map.indexOf(base64Str.charAt(i - 1)) << ((i % 4) * 2);
7915                    var bits2 = map.indexOf(base64Str.charAt(i)) >>> (6 - (i % 4) * 2);
7916                    words[nBytes >>> 2] |= (bits1 | bits2) << (24 - (nBytes % 4) * 8);
7917                    nBytes++;
7918                }
7919            }
7920
7921            return WordArray.create(words, nBytes);
7922        },
7923
7924        _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='
7925    };
7926}());
7927
7928/* ng-infinite-scroll - v1.0.0 - 2013-02-23 */
7929var mod;
7930
7931mod = angular.module('infinite-scroll', []);
7932
7933mod.directive('infiniteScroll', [
7934  '$rootScope', '$window', '$timeout', function($rootScope, $window, $timeout) {
7935    return {
7936      link: function(scope, elem, attrs) {
7937        var checkWhenEnabled, handler, scrollDistance, scrollEnabled;
7938        $window = angular.element($window);
7939        scrollDistance = 0;
7940        if (attrs.infiniteScrollDistance != null) {
7941          scope.$watch(attrs.infiniteScrollDistance, function(value) {
7942            return scrollDistance = parseInt(value, 10);
7943          });
7944        }
7945        scrollEnabled = true;
7946        checkWhenEnabled = false;
7947        if (attrs.infiniteScrollDisabled != null) {
7948          scope.$watch(attrs.infiniteScrollDisabled, function(value) {
7949            scrollEnabled = !value;
7950            if (scrollEnabled && checkWhenEnabled) {
7951              checkWhenEnabled = false;
7952              return handler();
7953            }
7954          });
7955        }
7956        handler = function() {
7957          var elementBottom, remaining, shouldScroll, windowBottom;
7958          windowBottom = $window.height() + $window.scrollTop();
7959          elementBottom = elem.offset().top + elem.height();
7960          remaining = elementBottom - windowBottom;
7961          shouldScroll = remaining <= $window.height() * scrollDistance;
7962          if (shouldScroll && scrollEnabled) {
7963            if ($rootScope.$$phase) {
7964              return scope.$eval(attrs.infiniteScroll);
7965            } else {
7966              return scope.$apply(attrs.infiniteScroll);
7967            }
7968          } else if (shouldScroll) {
7969            return checkWhenEnabled = true;
7970          }
7971        };
7972        $window.on('scroll', handler);
7973        scope.$on('$destroy', function() {
7974          return $window.off('scroll', handler);
7975        });
7976        return $timeout((function() {
7977          if (attrs.infiniteScrollImmediateCheck) {
7978            if (scope.$eval(attrs.infiniteScrollImmediateCheck)) {
7979              return handler();
7980            }
7981          } else {
7982            return handler();
7983          }
7984        }), 0);
7985      }
7986    };
7987  }
7988]);
7989
7990/**
7991 * @license Angulartics v0.8.5
7992 * (c) 2013 Luis Farzati http://luisfarzati.github.io/angulartics
7993 * License: MIT
7994 */
7995(function(angular, analytics) {
7996'use strict';
7997
7998var angulartics = window.angulartics || (window.angulartics = {});
7999angulartics.waitForVendorApi = function (objectName, delay, registerFn) {
8000  if (!Object.prototype.hasOwnProperty.call(window, objectName)) {
8001    setTimeout(function () { angulartics.waitForVendorApi(objectName, delay, registerFn); }, delay);
8002  }
8003  else {
8004    registerFn(window[objectName]);
8005  }
8006};
8007
8008/**
8009 * @ngdoc overview
8010 * @name angulartics
8011 */
8012angular.module('angulartics', [])
8013.provider('$analytics', function () {
8014  var settings = { 
8015    pageTracking: { 
8016      autoTrackFirstPage: true,
8017      autoTrackVirtualPages: true,
8018      basePath: '',
8019      bufferFlushDelay: 1000 
8020    },
8021    eventTracking: {
8022      bufferFlushDelay: 1000
8023    } 
8024  };
8025
8026  var cache = {
8027    pageviews: [],
8028    events: []
8029  };
8030
8031  var bufferedPageTrack = function (path) {
8032    cache.pageviews.push(path);
8033  };
8034  var bufferedEventTrack = function (event, properties) {
8035    cache.events.push({name: event, properties: properties});
8036  };
8037
8038  var api = {
8039    settings: settings,
8040    pageTrack: bufferedPageTrack,
8041    eventTrack: bufferedEventTrack
8042  };
8043
8044  var registerPageTrack = function (fn) {
8045    api.pageTrack = fn;
8046    angular.forEach(cache.pageviews, function (path, index) {
8047      setTimeout(function () { api.pageTrack(path); }, index * settings.pageTracking.bufferFlushDelay);
8048    });
8049  };
8050  var registerEventTrack = function (fn) {
8051    api.eventTrack = fn;
8052    angular.forEach(cache.events, function (event, index) {
8053      setTimeout(function () { api.eventTrack(event.name, event.properties); }, index * settings.eventTracking.bufferFlushDelay);
8054    });
8055  };
8056
8057  return {
8058    $get: function() { return api; },
8059    settings: settings,
8060    virtualPageviews: function (value) { this.settings.pageTracking.autoTrackVirtualPages = value; },
8061    firstPageview: function (value) { this.settings.pageTracking.autoTrackFirstPage = value; },
8062    withBase: function (value) { this.settings.pageTracking.basePath = (value) ? angular.element('base').attr('href').slice(0, -1) : ''; },
8063    registerPageTrack: registerPageTrack,
8064    registerEventTrack: registerEventTrack
8065  };
8066})
8067
8068.run(['$rootScope', '$location', '$analytics', function ($rootScope, $location, $anal
8068ytics) {
8069  if ($analytics.settings.pageTracking.autoTrackFirstPage) {
8070    $analytics.pageTrack($location.absUrl());
8071  }
8072  if ($analytics.settings.pageTracking.autoTrackVirtualPages) {
8073    $rootScope.$on('$routeChangeSuccess', function (event, current) {
8074      if (current && (current.$$route||current).redirectTo) return;
8075      var url = $analytics.settings.pageTracking.basePath + $location.url();
8076      $analytics.pageTrack(url);
8077    });
8078  }
8079}])
8080
8081.directive('analyticsOn', ['$analytics', function ($analytics) {
8082  function isCommand(element) {
8083    return ['a:','button:','button:button','button:submit','input:button','input:submit'].indexOf(
8084      element.tagName.toLowerCase()+':'+(element.type||'')) >= 0;
8085  }
8086
8087  function inferEventType(element) {
8088    if (isCommand(element)) return 'click';
8089    return 'click';
8090  }
8091
8092  function inferEventName(element) {
8093    if (isCommand(element)) return element.innerText || element.value;
8094    return element.id || element.name || element.tagName;
8095  }
8096
8097  function isProperty(name) {
8098    return name.substr(0, 9) === 'analytics' && ['on', 'event'].indexOf(name.substr(10)) === -1;
8099  }
8100
8101  return {
8102    restrict: 'A',
8103    scope: false,
8104    link: function ($scope, $element, $attrs) {
8105      var eventType = $attrs.analyticsOn || inferEventType($element[0]),
8106          eventName = $attrs.analyticsEvent || inferEventName($element[0]);
8107
8108      var properties = {};
8109      angular.forEach($attrs.$attr, function(attr, name) {
8110        if (isProperty(attr)) {
8111          properties[name.slice(9).toLowerCase()] = $attrs[name];
8112        }
8113      });
8114
8115      angular.element($element[0]).bind(eventType, function () {
8116        $analytics.eventTrack(eventName, properties);
8117      });
8118    }
8119  };
8120}]);
8121})(angular);
8122
8123/**
8124 * @license Angulartics v0.8.5
8125 * (c) 2013 Luis Farzati http://luisfarzati.github.io/angulartics
8126 * Universal Analytics update contributed by http://github.com/willmcclellan
8127 * License: MIT
8128 */
8129(function(angular) {
8130'use strict';
8131
8132/**
8133 * @ngdoc overview
8134 * @name angulartics.google.analytics
8135 * Enables analytics support for Google Analytics (http://google.com/analytics)
8136 */
8137angular.module('angulartics.google.analytics', ['angulartics'])
8138.config(['$analyticsProvider', function ($analyticsProvider) {
8139
8140  // GA already supports buffered invocations so we don't need
8141  // to wrap these inside angulartics.waitForVendorApi
8142
8143  $analyticsProvider.registerPageTrack(function (path) {
8144    if (window._gaq) _gaq.push(['_trackPageview', path]);
8145    if (window.ga) ga('send', 'pageview', path);
8146  });
8147
8148  $analyticsProvider.registerEventTrack(function (action, properties) {
8149    if (window._gaq) _gaq.push(['_trackEvent', properties.category, action, properties.label, properties.value]);
8150    if (window.ga) ga('send', 'event', properties.category, action, properties.label, properties.value);
8151  });
8152  
8153}]);
8154})(angular);
8155/*global angular*/
8156angular.module('ezfb', [])
8157
8158.provider('$FB', function () {
8159
8160  var NO_CALLBACK = -1;
8161
8162  /**
8163   * Specify published apis and executable callback argument index
8164   *
8165   * ref: https://developers.facebook.com/docs/reference/javascript/
8166   */
8167  var _publishedApis = {
8168    // core
8169    api: [1, 2, 3],
8170    ui: 1,
8171
8172    // auth
8173    // getAuthResponse: 0,  // deprecated
8174    getLoginStatus: 0,
8175    login: 0,
8176    logout: 0,
8177
8178    // event
8179    'Event.subscribe': 1,
8180    'Event.unsubscribe': 1,  // not quite a callback though
8181
8182    // xfbml
8183    'XFBML.parse': NO_CALLBACK,
8184
8185    // canvas
8186    'Canvas.Prefetcher.addStaticResource': NO_CALLBACK,
8187    'Canvas.Prefetcher.setCollectionMode': NO_CALLBACK,
8188    'Canvas.hideFlashElement': NO_CALLBACK,
8189    'Canvas.scrollTo': NO_CALLBACK,
8190    'Canvas.setAutoGrow': NO_CALLBACK,
8191    'Canvas.setDoneLoading': 0,
8192    'Canvas.setSize': NO_CALLBACK,
8193    'Canvas.setUrlHandler': 0,
8194    'Canvas.showFlashElement': NO_CALLBACK,
8195    'Canvas.startTimer': NO_CALLBACK,
8196    'Canvas.stopTimer': 0
8197  };
8198
8199  // Default locale
8200  var _locale = 'en_US';
8201
8202  // Default init parameters
8203  var _initParams = {
8204    // appId      : '', // App ID from the App Dashboard
8205    // channelUrl : '', // Channel File for x-domain communication
8206    status     : true, // check the login status upon init?
8207    cookie     : true, // set sessions cookies to allow your server to access the session?
8208    xfbml      : true  // parse XFBML tags on this page?
8209  };
8210  
8211  // Default init function
8212  var _defaultInitFunction = ['$window', '$fbInitParams', function ($window, $fbInitParams) {
8213    // Initialize the FB JS SDK
8214    $window.FB.init($fbInitParams);
8215  }];
8216  var _initFunction = _defaultInitFunction;
8217
8218  /**
8219   * Generate namespace route in an object
8220   *
8221   * @param  {object} obj   target object
8222   * @param  {array}  paths ordered path asc
8223   */
8224  function _pathGen(obj, paths) {
8225    if (paths.length === 0) {
8226      return;
8227    }
8228    var path = paths.shift();
8229    if (!obj[path]) {
8230      obj[path] = {};
8231    }
8232    _pathGen(obj, paths);
8233  }
8234
8235  /**
8236   * Getter/setter of a config
8237   *
8238   * @param  {object} target to be configured object
8239   * @param  {object} config configuration(optional)
8240   * @return {*}             copied target if "config" is not given
8241   */
8242  function _config(target, config) {
8243    if (angular.isObject(config)) {
8244      angular.extend(target, config);
8245    }
8246    else {
8247      return angular.copy(target);
8248    }
8249  }
8250
8251  /**
8252   * Context and arguments proxy function
8253   *
8254   * @param  {function} func    the function
8255   * @param  {object}   context the context
8256   * @param  {array}    args    arguments
8257   * @return {function}         proxied function
8258   */
8259  function _proxy(func, context, args) {
8260    return function () {
8261      return func.apply(context, args);
8262    };
8263  }
8264
8265  return {
8266    ////////////////////////////
8267    // provider configuration //
8268    ////////////////////////////
8269    setInitParams: function (params) {
8270      _config(_initParams, params);
8271    },
8272    getInitParams: function () {
8273      return _config(_initParams);
8274    },
8275
8276    setLocale: function(locale) {
8277      _locale = locale;
8278    },
8279    getLocale: function() {
8280      return _locale;
8281    },
8282    
8283    setInitFunction: function (func) {
8284      if (angular.isArray(func) || angular.isFunction(func)) {
8285        _initFunction = func;
8286      }
8287      else {
8288        throw new Error('Init function type error.');
8289      }
8290    },
8291    getInitFunction: function () {
8292      return _initFunction;
8293    },
8294
8295    //////////
8296    // $get //
8297    //////////
8298    $get: [
8299             '$window', '$q', '$document', '$parse', '$rootScope', '$injector',
8300    function ($window,   $q,   $document,   $parse,   $rootScope,   $injector) {
8301      var _initReady, _$FB, 
8302          _savedListeners = {};
8303
8304      var _paramsReady = $q.defer();
8305
8306      if (_initParams.appId || _initFunction !== _defaultInitFunction) {
8307        _paramsReady.resolve();
8308      }
8309
8310      /**
8311       * #fb-root check & create
8312       */
8313      if (!$document[0].getElementById('fb-root')) {
8314        $document.find('body').append('<div id="fb-root"></div>');
8315      }
8316
8317      _initReady = $q.defer();
8318      // Load the SDK's source Asynchronously
8319      (function(d){
8320        var js, id = 'facebook-jssdk', ref = d.getElementsByTagName('script')[0];
8321        if (d.getElementById(id)) {return;}
8322        js = d.createElement('script'); js.id = id; js.async = true;
8323        js.src = "//connect.facebook.net/" + _locale + "/all.js";
8324        ref.parentNode.insertBefore(js, ref);
8325      }($document[0]));
8326
8327      $window.fbAsyncInit = function () {
8328        _paramsReady.promise.then(function() {
8329          // Run init function
8330          $injector.invoke(_initFunction, null, {'$fbInitParams': _initParams});
8331
8332          _$FB.$$ready = true;
8333          _initReady.resolve();
8334        });
8335      };
8336
8337      _$FB = {
8338        $$ready: false,
8339        init: function (params) {
8340          _config(_initParams, params);
8341          _paramsReady.resolve();
8342        }
8343      };
8344
8345      /**
8346       * _$FB initialization
8347       *
8348       * Publish FB APIs with auto-check ready state
8349       */
8350      angular.forEach(_publishedApis, function (cbArgIndex, apiPath) {
8351        _pathGen(_$FB, apiPath.split(/\./));
8352
8353        var getter = $parse(apiPath),
8354            setter = getter.assign;
8355        setter(_$FB, function () {
8356
8357          var args = Array.prototype.slice.call(arguments),
8358              apiCall = _proxy(function (args) {
8359                var dfd = $q.defer(),
8360                    /**
8361                     * Add or replce original callback function with deferred resolve
8362                     * 
8363                     * @param  {number} index expected api callback index
8364                     */
8365                    replaceCallbackAt = function (index) {
8366                      var func = angular.isFunction(args[index]) ? args[index] : angular.noop,
8367                          newFunc = function () {
8368                            var funcArgs = Array.prototype.slice.call(arguments);
8369
8370                            if ($rootScope.$$phase) {
8371                              // already in angularjs context
8372                              func.apply(null, funcArgs);
8373                              dfd.resolve.apply(dfd, funcArgs);
8374                            }
8375                            else {
8376                              // not in angularjs context
8377                              $rootScope.$apply(function () {
8378                                func.apply(null, funcArgs);
8379                                dfd.resolve.apply(dfd, funcArgs);
8380                              });
8381                            }
8382                          };
8383
8384                      while (args.length <= index) {
8385                        args.push(null);
8386                      }
8387
8388                      /**
8389                       * `FB.Event.unsubscribe` requires the original listener function.
8390                       * Save the mapping of original->wrapped on `FB.Event.subscribe` for unsubscribing.
8391                       */
8392                      var eventName;
8393                      if (apiPath === 'Event.subscribe') {
8394                        eventName = args[0];
8395                        if (angular.isUndefined(_savedListeners[eventName])) {
8396                          _savedListeners[eventName] = [];
8397                        }
8398                        _savedListeners[eventName].push({
8399                          original: func,
8400                          wrapped: newFunc
8401                        });
8402                      }
8403                      else if (apiPath === 'Event.unsubscribe') {
8404                        eventName = args[0];
8405                        if (angular.isArray(_savedListeners[eventName])) {
8406                          var i, subscribed, l = _savedListeners[eventName].length;
8407                          for (i = 0; i < l; i++) {
8408                            subscribed = _savedListeners[eventName][i];
8409                            if (subscribed.original === func) {
8410                              newFunc = subscribed.wrapped;
8411                              _savedListeners[eventName].splice(i, 1);
8412                              break;
8413                            }
8414                          }
8415                        }
8416                      }
8417
8418                      // Replace the original one (or null) with newFunc
8419                      args[index] = newFunc;
8420                    };
8421
8422                if (cbArgIndex !== NO_CALLBACK) {
8423                  if (angular.isNumber(cbArgIndex)) {
8424                    /**
8425                     * Constant callback argument index
8426                     */
8427                    replaceCallbackAt(cbArgIndex);
8428                  }
8429                  else if (angular.isArray(cbArgIndex)) {
8430                    /**
8431                     * Multiple possible callback argument index
8432                     */
8433                    var i, c;
8434                    for (i = 0; i < cbArgIndex.length; i++) {
8435                      c = cbArgIndex[i];
8436
8437                      if (args.length == c ||
8438                          args.length == (c + 1) && angular.isFunction(args[c])) {
8439
8440                        replaceCallbackAt(c);
8441
8442                        break;
8443                      }
8444                    }
8445                  }
8446                }
8447
8448                /**
8449                 * Apply back to original FB SDK
8450                 */
8451                var origFBFunc = getter($window.FB);
8452                if (!origFBFunc) {
8453                  throw new Error("Facebook API `FB." + apiPath + "` doesn't exist.");
8454                }
8455                origFBFunc.apply($window.FB, args);
8456
8457                return dfd.promise;
8458              }, null, [args]);
8459
8460          /**
8461           * Wrap the api function with our ready promise
8462           */
8463          if (cbArgIndex === NO_CALLBACK) {
8464            // Do not return promise for no-callback apis
8465            _initReady.promise.then(apiCall); 
8466          }
8467          else {
8468            return _initReady.promise.then(apiCall);
8469          }
8470        });
8471      });
8472
8473      return _$FB;
8474    }]
8475  };
8476})
8477
8478/**
8479 * @ngdoc directive
8480 * @name ng.directive:ezfbXfbml
8481 * @restrict EAC
8482 *
8483 * @description
8484 * Parse XFBML inside the directive
8485 *
8486 * @param {boolean} ezfb-xfbml Reload trigger for inside XFBML,
8487 *                             should keep only XFBML content inside the directive.
8488 * @param {expr}    onrender   Evaluated every time content xfbml gets rendered.
8489 */
8490.directive('ezfbXfbml', [
8491         '$FB', '$parse', '$compile', '$timeout',
8492function ($FB,   $parse,   $compile,   $timeout) {
8493  return {
8494    restrict: 'EAC',
8495    compile: function (tElm, tAttrs) {
8496      var _savedHtml = tElm.html();
8497
8498      return function postLink(scope, iElm, iAttrs) {
8499        var _rendering = true,
8500            onrenderExp = iAttrs.onrender || '',
8501            onrenderHandler = function () {
8502              if (_rendering) {
8503                scope.$eval(onrenderExp);
8504                _rendering = false;
8505              }
8506            },
8507            renderEvent = 'xfbml.render';
8508
8509        /**
8510         * Render event
8511         */
8512        if (onrenderExp) {
8513          // Subscribe
8514          $FB.Event.subscribe(renderEvent, onrenderHandler);
8515
8516          // Unsubscibe on $destroy
8517          iElm.bind('$destroy', function () {
8518            $FB.Event.unsubscribe(renderEvent, onrenderHandler);
8519
8520            iElm.unbind('$destroy');
8521          });
8522        }
8523
8524        $FB.XFBML.parse(iElm[0]);
8525
8526        /**
8527         * The trigger
8528         */
8529        var setter = $parse(iAttrs.ezfbXfbml).assign;
8530        scope.$watch(iAttrs.ezfbXfbml, function (val) {
8531          if (val) {
8532            _rendering = true;
8533            iElm.html(_savedHtml);
8534
8535            $compile(iElm.contents())(scope);
8536            $timeout(function () {
8537              $FB.XFBML.parse(iElm[0]);
8538            });
8539
8540            // Reset the trigger if it's settable
8541            (setter || angular.noop)(scope, false);
8542          }
8543        }, true);
8544
8545      };
8546    }
8547  };
8548}]);
8549
8550/**
8551 * @license AngularJS v1.2.6
8552 * (c) 2010-2014 Google, Inc. http://angularjs.org
8553 * License: MIT
8554 */
8555(function(window, angular, undefined) {'use strict';
8556
8557/**
8558 * @ngdoc overview
8559 * @name ngCookies
8560 * @description
8561 *
8562 * # ngCookies
8563 *
8564 * The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies. 
8565 *
8566 * {@installModule cookies}
8567 *
8568 * <div doc-module-components="ngCookies"></div>
8569 *
8570 * See {@link ngCookies.$cookies `$cookies`} and
8571 * {@link ngCookies.$cookieStore `$cookieStore`} for usage.
8572 */
8573
8574
8575angular.module('ngCookies', ['ng']).
8576  /**
8577   * @ngdoc object
8578   * @name ngCookies.$cookies
8579   * @requires $browser
8580   *
8581   * @description
8582   * Provides read/write access to browser's cookies.
8583   *
8584   * Only a simple Object is exposed and by adding or removing properties to/from
8585   * this object, new cookies are created/deleted at the end of current $eval.
8586   *
8587   * Requires the {@link ngCookies `ngCookies`} module to be installed.
8588   *
8589   * @example
8590   <doc:example>
8591     <doc:source>
8592       <script>
8593         function ExampleController($cookies) {
8594           // Retrieving a cookie
8595           var favoriteCookie = $cookies.myFavorite;
8596           // Setting a cookie
8597           $cookies.myFavorite = 'oatmeal';
8598         }
8599       </script>
8600     </doc:source>
8601   </doc:example>
8602   */
8603   factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) {
8604      var cookies = {},
8605          lastCookies = {},
8606          lastBrowserCookies,
8607          runEval = false,
8608          copy = angular.copy,
8609          isUndefined = angular.isUndefined;
8610
8611      //creates a poller fn that copies all cookies from the $browser to service & inits the service
8612      $browser.addPollFn(function() {
8613        var currentCookies = $browser.cookies();
8614        if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
8615          lastBrowserCookies = currentCookies;
8616          copy(currentCookies, lastCookies);
8617          copy(currentCookies, cookies);
8618          if (runEval) $rootScope.$apply();
8619        }
8620      })();
8621
8622      runEval = true;
8623
8624      //at the end of each eval, push cookies
8625      //TODO: this should happen before the "delayed" watches fire, because if some cookies are not
8626      //      strings or browser refuses to store some cookies, we update the model in the push fn.
8627      $rootScope.$watch(push);
8628
8629      return cookies;
8630
8631
8632      /**
8633       * Pushes all the cookies from the service to the browser and verifies if all cookies were
8634       * stored.
8635       */
8636      function push() {
8637        var name,
8638            value,
8639            browserCookies,
8640            updated;
8641
8642        //delete any cookies deleted in $cookies
8643        for (name in lastCookies) {
8644          if (isUndefined(cookies[name])) {
8645            $browser.cookies(name, undefined);
8646          }
8647        }
8648
8649        //update all cookies updated in $cookies
8650        for(name in cookies) {
8651          value = cookies[name];
8652          if (!angular.isString(value)) {
8653            if (angular.isDefined(lastCookies[name])) {
8654              cookies[name] = lastCookies[name];
8655            } else {
8656              delete cookies[name];
8657            }
8658          } else if (value !== lastCookies[name]) {
8659            $browser.cookies(name, value);
8660            updated = true;
8661          }
8662        }
8663
8664        //verify what was actually stored
8665        if (updated){
8666          updated = false;
8667          browserCookies = $browser.cookies();
8668
8669          for (name in cookies) {
8670            if (cookies[name] !== browserCookies[name]) {
8671              //delete or reset all cookies that the browser dropped from $cookies
8672              if (isUndefined(browserCookies[name])) {
8673                delete cookies[name];
8674              } else {
8675                cookies[name] = browserCookies[name];
8676              }
8677              updated = true;
8678            }
8679          }
8680        }
8681      }
8682    }]).
8683
8684
8685  /**
8686   * @ngdoc object
8687   * @name ngCookies.$cookieStore
8688   * @requires $cookies
8689   *
8690   * @description
8691   * Provides a key-value (string-object) storage, that is backed by session cookies.
8692   * Objects put or retrieved from this storage are automatically serialized or
8693   * deserialized by angular's toJson/fromJson.
8694   *
8695   * Requires the {@link ngCookies `ngCookies`} module to be installed.
8696   *
8697   * @example
8698   */
8699   factory('$cookieStore', ['$cookies', function($cookies) {
8700
8701      return {
8702        /**
8703         * @ngdoc method
8704         * @name ngCookies.$cookieStore#get
8705         * @methodOf ngCookies.$cookieStore
8706         *
8707         * @description
8708         * Returns the value of given cookie key
8709         *
8710         * @param {string} key Id to use for lookup.
8711         * @returns {Object} Deserialized cookie value.
8712         */
8713        get: function(key) {
8714          var value = $cookies[key];
8715          return value ? angular.fromJson(value) : value;
8716        },
8717
8718        /**
8719         * @ngdoc method
8720         * @name ngCookies.$cookieStore#put
8721         * @methodOf ngCookies.$cookieStore
8722         *
8723         * @description
8724         * Sets a value for given cookie key
8725         *
8726         * @param {string} key Id for the `value`.
8727         * @param {Object} value Value to be stored.
8728         */
8729        put: function(key, value) {
8730          $cookies[key] = angular.toJson(value);
8731        },
8732
8733        /**
8734         * @ngdoc method
8735         * @name ngCookies.$cookieStore#remove
8736         * @methodOf ngCookies.$cookieStore
8737         *
8738         * @description
8739         * Remove given cookie
8740         *
8741         * @param {string} key Id of the key-value pair to delete.
8742         */
8743        remove: function(key) {
8744          delete $cookies[key];
8745        }
8746      };
8747
8748    }]);
8749
8750
8751})(window, window.angular);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.