- * but the former is more efficient (in fact `resolve` just calls `study`
- * internally).
- *
- * @param {object} invocables Invocable objects
- * @return {function} a function to pass in locals, parent and self
- */
- this.study = function (invocables) {
- if (!isObject(invocables)) throw new Error("'invocables' must be an object");
- var invocableKeys = objectKeys(invocables || {});
-
- // Perform a topological sort of invocables to build an ordered plan
- var plan = [], cycle = [], visited = {};
- function visit(value, key) {
- if (visited[key] === VISIT_DONE) return;
-
- cycle.push(key);
- if (visited[key] === VISIT_IN_PROGRESS) {
- cycle.splice(0, indexOf(cycle, key));
- throw new Error("Cyclic dependency: " + cycle.join(" -> "));
- }
- visited[key] = VISIT_IN_PROGRESS;
-
- if (isString(value)) {
- plan.push(key, [ function() { return $injector.get(value); }], NO_DEPENDENCIES);
- } else {
- var params = $injector.annotate(value);
- forEach(params, function (param) {
- if (param !== key && invocables.hasOwnProperty(param)) visit(invocables[param], param);
- });
- plan.push(key, value, params);
- }
-
- cycle.pop();
- visited[key] = VISIT_DONE;
- }
- forEach(invocables, visit);
- invocables = cycle = visited = null; // plan is all that's required
-
- function isResolve(value) {
- return isObject(value) && value.then && value.$$promises;
- }
-
- return function (locals, parent, self) {
- if (isResolve(locals) && self === undefined) {
- self = parent; parent = locals; locals = null;
- }
- if (!locals) locals = NO_LOCALS;
- else if (!isObject(locals)) {
- throw new Error("'locals' must be an object");
- }
- if (!parent) parent = NO_PARENT;
- else if (!isResolve(parent)) {
- throw new Error("'parent' must be a promise returned by $resolve.resolve()");
- }
-
- // To complete the overall resolution, we have to wait for the parent
- // promise and for the promise for each invokable in our plan.
- var resolution = $q.defer(),
- result = resolution.promise,
- promises = result.$$promises = {},
- values = extend({}, locals),
- wait = 1 + plan.length/3,
- merged = false;
-
- function done() {
- // Merge parent values we haven't got yet and publish our own $$values
- if (!--wait) {
- if (!merged) merge(values, parent.$$values);
- result.$$values = values;
- result.$$promises = result.$$promises || true; // keep for isResolve()
- delete result.$$inheritedValues;
- resolution.resolve(values);
- }
- }
-
- function fail(reason) {
- result.$$failure = reason;
- resolution.reject(reason);
- }
-
- // Short-circuit if parent has already failed
- if (isDefined(parent.$$failure)) {
- fail(parent.$$failure);
- return result;
- }
-
- if (parent.$$inheritedValues) {
- merge(values, omit(parent.$$inheritedValues, invocableKeys));
- }
-
- // Merge parent values if the parent has already resolved, or merge
- // parent promises and wait if the parent resolve is still in progress.
- extend(promises, parent.$$promises);
- if (parent.$$values) {
- merged = merge(values, omit(parent.$$values, invocableKeys));
- result.$$inheritedValues = omit(parent.$$values, invocableKeys);
- done();
- } else {
- if (parent.$$inheritedValues) {
- result.$$inheritedValues = omit(parent.$$inheritedValues, invocableKeys);
- }
- parent.then(done, fail);
- }
-
- // Process each invocable in the plan, but ignore any where a local of the same name exists.
- for (var i=0, ii=plan.length; i} The template html as a string, or a promise
- * for that string.
- */
- this.fromUrl = function (url, params) {
- if (isFunction(url)) url = url(params);
- if (url == null) return null;
- else return $http
- .get(url, { cache: $templateCache, headers: { Accept: 'text/html' }})
- .then(function(response) { return response.data; });
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$templateFactory#fromProvider
- * @methodOf ui.router.util.$templateFactory
- *
- * @description
- * Creates a template by invoking an injectable provider function.
- *
- * @param {Function} provider Function to invoke via `$injector.invoke`
- * @param {Object} params Parameters for the template.
- * @param {Object} locals Locals to pass to `invoke`. Defaults to
- * `{ params: params }`.
- * @return {string|Promise.} The template html as a string, or a promise
- * for that string.
- */
- this.fromProvider = function (provider, params, locals) {
- return $injector.invoke(provider, null, locals || { params: params });
- };
-}
-
-angular.module('ui.router.util').service('$templateFactory', $TemplateFactory);
-
-var $$UMFP; // reference to $UrlMatcherFactoryProvider
-
-/**
- * @ngdoc object
- * @name ui.router.util.type:UrlMatcher
- *
- * @description
- * Matches URLs against patterns and extracts named parameters from the path or the search
- * part of the URL. A URL pattern consists of a path pattern, optionally followed by '?' and a list
- * of search parameters. Multiple search parameter names are separated by '&'. Search parameters
- * do not influence whether or not a URL is matched, but their values are passed through into
- * the matched parameters returned by {@link ui.router.util.type:UrlMatcher#methods_exec exec}.
- *
- * Path parameter placeholders can be specified using simple colon/catch-all syntax or curly brace
- * syntax, which optionally allows a regular expression for the parameter to be specified:
- *
- * * `':'` name - colon placeholder
- * * `'*'` name - catch-all placeholder
- * * `'{' name '}'` - curly placeholder
- * * `'{' name ':' regexp|type '}'` - curly placeholder with regexp or type name. Should the
- * regexp itself contain curly braces, they must be in matched pairs or escaped with a backslash.
- *
- * Parameter names may contain only word characters (latin letters, digits, and underscore) and
- * must be unique within the pattern (across both path and search parameters). For colon
- * placeholders or curly placeholders without an explicit regexp, a path parameter matches any
- * number of characters other than '/'. For catch-all placeholders the path parameter matches
- * any number of characters.
- *
- * Examples:
- *
- * * `'/hello/'` - Matches only if the path is exactly '/hello/'. There is no special treatment for
- * trailing slashes, and patterns have to match the entire path, not just a prefix.
- * * `'/user/:id'` - Matches '/user/bob' or '/user/1234!!!' or even '/user/' but not '/user' or
- * '/user/bob/details'. The second path segment will be captured as the parameter 'id'.
- * * `'/user/{id}'` - Same as the previous example, but using curly brace syntax.
- * * `'/user/{id:[^/]*}'` - Same as the previous example.
- * * `'/user/{id:[0-9a-fA-F]{1,8}}'` - Similar to the previous example, but only matches if the id
- * parameter consists of 1 to 8 hex digits.
- * * `'/files/{path:.*}'` - Matches any URL starting with '/files/' and captures the rest of the
- * path into the parameter 'path'.
- * * `'/files/*path'` - ditto.
- * * `'/calendar/{start:date}'` - Matches "/calendar/2014-11-12" (because the pattern defined
- * in the built-in `date` Type matches `2014-11-12`) and provides a Date object in $stateParams.start
- *
- * @param {string} pattern The pattern to compile into a matcher.
- * @param {Object} config A configuration object hash:
- * @param {Object=} parentMatcher Used to concatenate the pattern/config onto
- * an existing UrlMatcher
- *
- * * `caseInsensitive` - `true` if URL matching should be case insensitive, otherwise `false`, the default value (for backward compatibility) is `false`.
- * * `strict` - `false` if matching against a URL with a trailing slash should be treated as equivalent to a URL without a trailing slash, the default value is `true`.
- *
- * @property {string} prefix A static prefix of this pattern. The matcher guarantees that any
- * URL matching this matcher (i.e. any string for which {@link ui.router.util.type:UrlMatcher#methods_exec exec()} returns
- * non-null) will start with this prefix.
- *
- * @property {string} source The pattern that was passed into the constructor
- *
- * @property {string} sourcePath The path portion of the source property
- *
- * @property {string} sourceSearch The search portion of the source property
- *
- * @property {string} regex The constructed regex that will be used to match against the url when
- * it is time to determine which url will match.
- *
- * @returns {Object} New `UrlMatcher` object
- */
-function UrlMatcher(pattern, config, parentMatcher) {
- config = extend({ params: {} }, isObject(config) ? config : {});
-
- // Find all placeholders and create a compiled pattern, using either classic or curly syntax:
- // '*' name
- // ':' name
- // '{' name '}'
- // '{' name ':' regexp '}'
- // The regular expression is somewhat complicated due to the need to allow curly braces
- // inside the regular expression. The placeholder regexp breaks down as follows:
- // ([:*])([\w\[\]]+) - classic placeholder ($1 / $2) (search version has - for snake-case)
- // \{([\w\[\]]+)(?:\:( ... ))?\} - curly brace placeholder ($3) with optional regexp/type ... ($4) (search version has - for snake-case
- // (?: ... | ... | ... )+ - the regexp consists of any number of atoms, an atom being either
- // [^{}\\]+ - anything other than curly braces or backslash
- // \\. - a backslash escape
- // \{(?:[^{}\\]+|\\.)*\} - a matched set of curly braces containing other atoms
- var placeholder = /([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
- searchPlaceholder = /([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,
- compiled = '^', last = 0, m,
- segments = this.segments = [],
- parentParams = parentMatcher ? parentMatcher.params : {},
- params = this.params = parentMatcher ? parentMatcher.params.$$new() : new $$UMFP.ParamSet(),
- paramNames = [];
-
- function addParameter(id, type, config, location) {
- paramNames.push(id);
- if (parentParams[id]) return parentParams[id];
- if (!/^\w+(-+\w+)*(?:\[\])?$/.test(id)) throw new Error("Invalid parameter name '" + id + "' in pattern '" + pattern + "'");
- if (params[id]) throw new Error("Duplicate parameter name '" + id + "' in pattern '" + pattern + "'");
- params[id] = new $$UMFP.Param(id, type, config, location);
- return params[id];
- }
-
- function quoteRegExp(string, pattern, squash) {
- var surroundPattern = ['',''], result = string.replace(/[\\\[\]\^$*+?.()|{}]/g, "\\$&");
- if (!pattern) return result;
- switch(squash) {
- case false: surroundPattern = ['(', ')']; break;
- case true: surroundPattern = ['?(', ')?']; break;
- default: surroundPattern = ['(' + squash + "|", ')?']; break;
- }
- return result + surroundPattern[0] + pattern + surroundPattern[1];
- }
-
- this.source = pattern;
-
- // Split into static segments separated by path parameter placeholders.
- // The number of segments is always 1 more than the number of parameters.
- function matchDetails(m, isSearch) {
- var id, regexp, segment, type, cfg, arrayMode;
- id = m[2] || m[3]; // IE[78] returns '' for unmatched groups instead of null
- cfg = config.params[id];
- segment = pattern.substring(last, m.index);
- regexp = isSearch ? m[4] : m[4] || (m[1] == '*' ? '.*' : null);
- type = $$UMFP.type(regexp || "string") || inherit($$UMFP.type("string"), { pattern: new RegExp(regexp) });
- return {
- id: id, regexp: regexp, segment: segment, type: type, cfg: cfg
- };
- }
-
- var p, param, segment;
- while ((m = placeholder.exec(pattern))) {
- p = matchDetails(m, false);
- if (p.segment.indexOf('?') >= 0) break; // we're into the search part
-
- param = addParameter(p.id, p.type, p.cfg, "path");
- compiled += quoteRegExp(p.segment, param.type.pattern.source, param.squash);
- segments.push(p.segment);
- last = placeholder.lastIndex;
- }
- segment = pattern.substring(last);
-
- // Find any search parameter names and remove them from the last segment
- var i = segment.indexOf('?');
-
- if (i >= 0) {
- var search = this.sourceSearch = segment.substring(i);
- segment = segment.substring(0, i);
- this.sourcePath = pattern.substring(0, last + i);
-
- if (search.length > 0) {
- last = 0;
- while ((m = searchPlaceholder.exec(search))) {
- p = matchDetails(m, true);
- param = addParameter(p.id, p.type, p.cfg, "search");
- last = placeholder.lastIndex;
- // check if ?&
- }
- }
- } else {
- this.sourcePath = pattern;
- this.sourceSearch = '';
- }
-
- compiled += quoteRegExp(segment) + (config.strict === false ? '\/?' : '') + '$';
- segments.push(segment);
-
- this.regexp = new RegExp(compiled, config.caseInsensitive ? 'i' : undefined);
- this.prefix = segments[0];
- this.$$paramNames = paramNames;
-}
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:UrlMatcher#concat
- * @methodOf ui.router.util.type:UrlMatcher
- *
- * @description
- * Returns a new matcher for a pattern constructed by appending the path part and adding the
- * search parameters of the specified pattern to this pattern. The current pattern is not
- * modified. This can be understood as creating a pattern for URLs that are relative to (or
- * suffixes of) the current pattern.
- *
- * @example
- * The following two matchers are equivalent:
- *
- * new UrlMatcher('/user/{id}?q').concat('/details?date');
- * new UrlMatcher('/user/{id}/details?q&date');
- *
- *
- * @param {string} pattern The pattern to append.
- * @param {Object} config An object hash of the configuration for the matcher.
- * @returns {UrlMatcher} A matcher for the concatenated pattern.
- */
-UrlMatcher.prototype.concat = function (pattern, config) {
- // Because order of search parameters is irrelevant, we can add our own search
- // parameters to the end of the new pattern. Parse the new pattern by itself
- // and then join the bits together, but it's much easier to do this on a string level.
- var defaultConfig = {
- caseInsensitive: $$UMFP.caseInsensitive(),
- strict: $$UMFP.strictMode(),
- squash: $$UMFP.defaultSquashPolicy()
- };
- return new UrlMatcher(this.sourcePath + pattern + this.sourceSearch, extend(defaultConfig, config), this);
-};
-
-UrlMatcher.prototype.toString = function () {
- return this.source;
-};
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:UrlMatcher#exec
- * @methodOf ui.router.util.type:UrlMatcher
- *
- * @description
- * Tests the specified path against this matcher, and returns an object containing the captured
- * parameter values, or null if the path does not match. The returned object contains the values
- * of any search parameters that are mentioned in the pattern, but their value may be null if
- * they are not present in `searchParams`. This means that search parameters are always treated
- * as optional.
- *
- * @example
- *
- *
- * @property {RegExp} pattern The regular expression pattern used to match values of this type when
- * coming from a substring of a URL.
- *
- * @returns {Object} Returns a new `Type` object.
- */
-function Type(config) {
- extend(this, config);
-}
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:Type#is
- * @methodOf ui.router.util.type:Type
- *
- * @description
- * Detects whether a value is of a particular type. Accepts a native (decoded) value
- * and determines whether it matches the current `Type` object.
- *
- * @param {*} val The value to check.
- * @param {string} key Optional. If the type check is happening in the context of a specific
- * {@link ui.router.util.type:UrlMatcher `UrlMatcher`} object, this is the name of the
- * parameter in which `val` is stored. Can be used for meta-programming of `Type` objects.
- * @returns {Boolean} Returns `true` if the value matches the type, otherwise `false`.
- */
-Type.prototype.is = function(val, key) {
- return true;
-};
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:Type#encode
- * @methodOf ui.router.util.type:Type
- *
- * @description
- * Encodes a custom/native type value to a string that can be embedded in a URL. Note that the
- * return value does *not* need to be URL-safe (i.e. passed through `encodeURIComponent()`), it
- * only needs to be a representation of `val` that has been coerced to a string.
- *
- * @param {*} val The value to encode.
- * @param {string} key The name of the parameter in which `val` is stored. Can be used for
- * meta-programming of `Type` objects.
- * @returns {string} Returns a string representation of `val` that can be encoded in a URL.
- */
-Type.prototype.encode = function(val, key) {
- return val;
-};
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:Type#decode
- * @methodOf ui.router.util.type:Type
- *
- * @description
- * Converts a parameter value (from URL string or transition param) to a custom/native value.
- *
- * @param {string} val The URL parameter value to decode.
- * @param {string} key The name of the parameter in which `val` is stored. Can be used for
- * meta-programming of `Type` objects.
- * @returns {*} Returns a custom representation of the URL parameter value.
- */
-Type.prototype.decode = function(val, key) {
- return val;
-};
-
-/**
- * @ngdoc function
- * @name ui.router.util.type:Type#equals
- * @methodOf ui.router.util.type:Type
- *
- * @description
- * Determines whether two decoded values are equivalent.
- *
- * @param {*} a A value to compare against.
- * @param {*} b A value to compare against.
- * @returns {Boolean} Returns `true` if the values are equivalent/equal, otherwise `false`.
- */
-Type.prototype.equals = function(a, b) {
- return a == b;
-};
-
-Type.prototype.$subPattern = function() {
- var sub = this.pattern.toString();
- return sub.substr(1, sub.length - 2);
-};
-
-Type.prototype.pattern = /.*/;
-
-Type.prototype.toString = function() { return "{Type:" + this.name + "}"; };
-
-/*
- * Wraps an existing custom Type as an array of Type, depending on 'mode'.
- * e.g.:
- * - urlmatcher pattern "/path?{queryParam[]:int}"
- * - url: "/path?queryParam=1&queryParam=2
- * - $stateParams.queryParam will be [1, 2]
- * if `mode` is "auto", then
- * - url: "/path?queryParam=1 will create $stateParams.queryParam: 1
- * - url: "/path?queryParam=1&queryParam=2 will create $stateParams.queryParam: [1, 2]
- */
-Type.prototype.$asArray = function(mode, isSearch) {
- if (!mode) return this;
- if (mode === "auto" && !isSearch) throw new Error("'auto' array mode is for query parameters only");
- return new ArrayType(this, mode);
-
- function ArrayType(type, mode) {
- function bindTo(type, callbackName) {
- return function() {
- return type[callbackName].apply(type, arguments);
- };
- }
-
- // Wrap non-array value as array
- function arrayWrap(val) { return isArray(val) ? val : (isDefined(val) ? [ val ] : []); }
- // Unwrap array value for "auto" mode. Return undefined for empty array.
- function arrayUnwrap(val) {
- switch(val.length) {
- case 0: return undefined;
- case 1: return mode === "auto" ? val[0] : val;
- default: return val;
- }
- }
- function falsey(val) { return !val; }
-
- // Wraps type (.is/.encode/.decode) functions to operate on each value of an array
- function arrayHandler(callback, allTruthyMode) {
- return function handleArray(val) {
- val = arrayWrap(val);
- var result = map(val, callback);
- if (allTruthyMode === true)
- return filter(result, falsey).length === 0;
- return arrayUnwrap(result);
- };
- }
-
- // Wraps type (.equals) functions to operate on each value of an array
- function arrayEqualsHandler(callback) {
- return function handleArray(val1, val2) {
- var left = arrayWrap(val1), right = arrayWrap(val2);
- if (left.length !== right.length) return false;
- for (var i = 0; i < left.length; i++) {
- if (!callback(left[i], right[i])) return false;
- }
- return true;
- };
- }
-
- this.encode = arrayHandler(bindTo(type, 'encode'));
- this.decode = arrayHandler(bindTo(type, 'decode'));
- this.is = arrayHandler(bindTo(type, 'is'), true);
- this.equals = arrayEqualsHandler(bindTo(type, 'equals'));
- this.pattern = type.pattern;
- this.$arrayMode = mode;
- }
-};
-
-
-
-/**
- * @ngdoc object
- * @name ui.router.util.$urlMatcherFactory
- *
- * @description
- * Factory for {@link ui.router.util.type:UrlMatcher `UrlMatcher`} instances. The factory
- * is also available to providers under the name `$urlMatcherFactoryProvider`.
- */
-function $UrlMatcherFactory() {
- $$UMFP = this;
-
- var isCaseInsensitive = false, isStrictMode = true, defaultSquashPolicy = false;
-
- function valToString(val) { return val != null ? val.toString().replace(/\//g, "%2F") : val; }
- function valFromString(val) { return val != null ? val.toString().replace(/%2F/g, "/") : val; }
-// TODO: in 1.0, make string .is() return false if value is undefined by default.
-// function regexpMatches(val) { /*jshint validthis:true */ return isDefined(val) && this.pattern.test(val); }
- function regexpMatches(val) { /*jshint validthis:true */ return this.pattern.test(val); }
-
- var $types = {}, enqueue = true, typeQueue = [], injector, defaultTypes = {
- string: {
- encode: valToString,
- decode: valFromString,
- is: regexpMatches,
- pattern: /[^/]*/
- },
- int: {
- encode: valToString,
- decode: function(val) { return parseInt(val, 10); },
- is: function(val) { return isDefined(val) && this.decode(val.toString()) === val; },
- pattern: /\d+/
- },
- bool: {
- encode: function(val) { return val ? 1 : 0; },
- decode: function(val) { return parseInt(val, 10) !== 0; },
- is: function(val) { return val === true || val === false; },
- pattern: /0|1/
- },
- date: {
- encode: function (val) {
- if (!this.is(val))
- return undefined;
- return [ val.getFullYear(),
- ('0' + (val.getMonth() + 1)).slice(-2),
- ('0' + val.getDate()).slice(-2)
- ].join("-");
- },
- decode: function (val) {
- if (this.is(val)) return val;
- var match = this.capture.exec(val);
- return match ? new Date(match[1], match[2] - 1, match[3]) : undefined;
- },
- is: function(val) { return val instanceof Date && !isNaN(val.valueOf()); },
- equals: function (a, b) { return this.is(a) && this.is(b) && a.toISOString() === b.toISOString(); },
- pattern: /[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,
- capture: /([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/
- },
- json: {
- encode: angular.toJson,
- decode: angular.fromJson,
- is: angular.isObject,
- equals: angular.equals,
- pattern: /[^/]*/
- },
- any: { // does not encode/decode
- encode: angular.identity,
- decode: angular.identity,
- is: angular.identity,
- equals: angular.equals,
- pattern: /.*/
- }
- };
-
- function getDefaultConfig() {
- return {
- strict: isStrictMode,
- caseInsensitive: isCaseInsensitive
- };
- }
-
- function isInjectable(value) {
- return (isFunction(value) || (isArray(value) && isFunction(value[value.length - 1])));
- }
-
- /**
- * [Internal] Get the default value of a parameter, which may be an injectable function.
- */
- $UrlMatcherFactory.$$getDefaultValue = function(config) {
- if (!isInjectable(config.value)) return config.value;
- if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
- return injector.invoke(config.value);
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#caseInsensitive
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Defines whether URL matching should be case sensitive (the default behavior), or not.
- *
- * @param {boolean} value `false` to match URL in a case sensitive manner; otherwise `true`;
- * @returns {boolean} the current value of caseInsensitive
- */
- this.caseInsensitive = function(value) {
- if (isDefined(value))
- isCaseInsensitive = value;
- return isCaseInsensitive;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#strictMode
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Defines whether URLs should match trailing slashes, or not (the default behavior).
- *
- * @param {boolean=} value `false` to match trailing slashes in URLs, otherwise `true`.
- * @returns {boolean} the current value of strictMode
- */
- this.strictMode = function(value) {
- if (isDefined(value))
- isStrictMode = value;
- return isStrictMode;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#defaultSquashPolicy
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Sets the default behavior when generating or matching URLs with default parameter values.
- *
- * @param {string} value A string that defines the default parameter URL squashing behavior.
- * `nosquash`: When generating an href with a default parameter value, do not squash the parameter value from the URL
- * `slash`: When generating an href with a default parameter value, squash (remove) the parameter value, and, if the
- * parameter is surrounded by slashes, squash (remove) one slash from the URL
- * any other string, e.g. "~": When generating an href with a default parameter value, squash (remove)
- * the parameter value from the URL and replace it with this string.
- */
- this.defaultSquashPolicy = function(value) {
- if (!isDefined(value)) return defaultSquashPolicy;
- if (value !== true && value !== false && !isString(value))
- throw new Error("Invalid squash policy: " + value + ". Valid policies: false, true, arbitrary-string");
- defaultSquashPolicy = value;
- return value;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#compile
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Creates a {@link ui.router.util.type:UrlMatcher `UrlMatcher`} for the specified pattern.
- *
- * @param {string} pattern The URL pattern.
- * @param {Object} config The config object hash.
- * @returns {UrlMatcher} The UrlMatcher.
- */
- this.compile = function (pattern, config) {
- return new UrlMatcher(pattern, extend(getDefaultConfig(), config));
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#isMatcher
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Returns true if the specified object is a `UrlMatcher`, or false otherwise.
- *
- * @param {Object} object The object to perform the type check against.
- * @returns {Boolean} Returns `true` if the object matches the `UrlMatcher` interface, by
- * implementing all the same methods.
- */
- this.isMatcher = function (o) {
- if (!isObject(o)) return false;
- var result = true;
-
- forEach(UrlMatcher.prototype, function(val, name) {
- if (isFunction(val)) {
- result = result && (isDefined(o[name]) && isFunction(o[name]));
- }
- });
- return result;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.util.$urlMatcherFactory#type
- * @methodOf ui.router.util.$urlMatcherFactory
- *
- * @description
- * Registers a custom {@link ui.router.util.type:Type `Type`} object that can be used to
- * generate URLs with typed parameters.
- *
- * @param {string} name The type name.
- * @param {Object|Function} definition The type definition. See
- * {@link ui.router.util.type:Type `Type`} for information on the values accepted.
- * @param {Object|Function} definitionFn (optional) A function that is injected before the app
- * runtime starts. The result of this function is merged into the existing `definition`.
- * See {@link ui.router.util.type:Type `Type`} for information on the values accepted.
- *
- * @returns {Object} Returns `$urlMatcherFactoryProvider`.
- *
- * @example
- * This is a simple example of a custom type that encodes and decodes items from an
- * array, using the array index as the URL-encoded value:
- *
- *
- * var list = ['John', 'Paul', 'George', 'Ringo'];
- *
- * $urlMatcherFactoryProvider.type('listItem', {
- * encode: function(item) {
- * // Represent the list item in the URL using its corresponding index
- * return list.indexOf(item);
- * },
- * decode: function(item) {
- * // Look up the list item by index
- * return list[parseInt(item, 10)];
- * },
- * is: function(item) {
- * // Ensure the item is valid by checking to see that it appears
- * // in the list
- * return list.indexOf(item) > -1;
- * }
- * });
- *
- * $stateProvider.state('list', {
- * url: "/list/{item:listItem}",
- * controller: function($scope, $stateParams) {
- * console.log($stateParams.item);
- * }
- * });
- *
- * // ...
- *
- * // Changes URL to '/list/3', logs "Ringo" to the console
- * $state.go('list', { item: "Ringo" });
- *
- *
- * This is a more complex example of a type that relies on dependency injection to
- * interact with services, and uses the parameter name from the URL to infer how to
- * handle encoding and decoding parameter values:
- *
- *
- * // Defines a custom type that gets a value from a service,
- * // where each service gets different types of values from
- * // a backend API:
- * $urlMatcherFactoryProvider.type('dbObject', {}, function(Users, Posts) {
- *
- * // Matches up services to URL parameter names
- * var services = {
- * user: Users,
- * post: Posts
- * };
- *
- * return {
- * encode: function(object) {
- * // Represent the object in the URL using its unique ID
- * return object.id;
- * },
- * decode: function(value, key) {
- * // Look up the object by ID, using the parameter
- * // name (key) to call the correct service
- * return services[key].findById(value);
- * },
- * is: function(object, key) {
- * // Check that object is a valid dbObject
- * return angular.isObject(object) && object.id && services[key];
- * }
- * equals: function(a, b) {
- * // Check the equality of decoded objects by comparing
- * // their unique IDs
- * return a.id === b.id;
- * }
- * };
- * });
- *
- * // In a config() block, you can then attach URLs with
- * // type-annotated parameters:
- * $stateProvider.state('users', {
- * url: "/users",
- * // ...
- * }).state('users.item', {
- * url: "/{user:dbObject}",
- * controller: function($scope, $stateParams) {
- * // $stateParams.user will now be an object returned from
- * // the Users service
- * },
- * // ...
- * });
- *
- */
- this.type = function (name, definition, definitionFn) {
- if (!isDefined(definition)) return $types[name];
- if ($types.hasOwnProperty(name)) throw new Error("A type named '" + name + "' has already been defined.");
-
- $types[name] = new Type(extend({ name: name }, definition));
- if (definitionFn) {
- typeQueue.push({ name: name, def: definitionFn });
- if (!enqueue) flushTypeQueue();
- }
- return this;
- };
-
- // `flushTypeQueue()` waits until `$urlMatcherFactory` is injected before invoking the queued `definitionFn`s
- function flushTypeQueue() {
- while(typeQueue.length) {
- var type = typeQueue.shift();
- if (type.pattern) throw new Error("You cannot override a type's .pattern at runtime.");
- angular.extend($types[type.name], injector.invoke(type.def));
- }
- }
-
- // Register default types. Store them in the prototype of $types.
- forEach(defaultTypes, function(type, name) { $types[name] = new Type(extend({name: name}, type)); });
- $types = inherit($types, {});
-
- /* No need to document $get, since it returns this */
- this.$get = ['$injector', function ($injector) {
- injector = $injector;
- enqueue = false;
- flushTypeQueue();
-
- forEach(defaultTypes, function(type, name) {
- if (!$types[name]) $types[name] = new Type(type);
- });
- return this;
- }];
-
- this.Param = function Param(id, type, config, location) {
- var self = this;
- config = unwrapShorthand(config);
- type = getType(config, type, location);
- var arrayMode = getArrayMode();
- type = arrayMode ? type.$asArray(arrayMode, location === "search") : type;
- if (type.name === "string" && !arrayMode && location === "path" && config.value === undefined)
- config.value = ""; // for 0.2.x; in 0.3.0+ do not automatically default to ""
- var isOptional = config.value !== undefined;
- var squash = getSquashPolicy(config, isOptional);
- var replace = getReplace(config, arrayMode, isOptional, squash);
-
- function unwrapShorthand(config) {
- var keys = isObject(config) ? objectKeys(config) : [];
- var isShorthand = indexOf(keys, "value") === -1 && indexOf(keys, "type") === -1 &&
- indexOf(keys, "squash") === -1 && indexOf(keys, "array") === -1;
- if (isShorthand) config = { value: config };
- config.$$fn = isInjectable(config.value) ? config.value : function () { return config.value; };
- return config;
- }
-
- function getType(config, urlType, location) {
- if (config.type && urlType) throw new Error("Param '"+id+"' has two type configurations.");
- if (urlType) return urlType;
- if (!config.type) return (location === "config" ? $types.any : $types.string);
- return config.type instanceof Type ? config.type : new Type(config.type);
- }
-
- // array config: param name (param[]) overrides default settings. explicit config overrides param name.
- function getArrayMode() {
- var arrayDefaults = { array: (location === "search" ? "auto" : false) };
- var arrayParamNomenclature = id.match(/\[\]$/) ? { array: true } : {};
- return extend(arrayDefaults, arrayParamNomenclature, config).array;
- }
-
- /**
- * returns false, true, or the squash value to indicate the "default parameter url squash policy".
- */
- function getSquashPolicy(config, isOptional) {
- var squash = config.squash;
- if (!isOptional || squash === false) return false;
- if (!isDefined(squash) || squash == null) return defaultSquashPolicy;
- if (squash === true || isString(squash)) return squash;
- throw new Error("Invalid squash policy: '" + squash + "'. Valid policies: false, true, or arbitrary string");
- }
-
- function getReplace(config, arrayMode, isOptional, squash) {
- var replace, configuredKeys, defaultPolicy = [
- { from: "", to: (isOptional || arrayMode ? undefined : "") },
- { from: null, to: (isOptional || arrayMode ? undefined : "") }
- ];
- replace = isArray(config.replace) ? config.replace : [];
- if (isString(squash))
- replace.push({ from: squash, to: undefined });
- configuredKeys = map(replace, function(item) { return item.from; } );
- return filter(defaultPolicy, function(item) { return indexOf(configuredKeys, item.from) === -1; }).concat(replace);
- }
-
- /**
- * [Internal] Get the default value of a parameter, which may be an injectable function.
- */
- function $$getDefaultValue() {
- if (!injector) throw new Error("Injectable functions cannot be called at configuration time");
- return injector.invoke(config.$$fn);
- }
-
- /**
- * [Internal] Gets the decoded representation of a value if the value is defined, otherwise, returns the
- * default value, which may be the result of an injectable function.
- */
- function $value(value) {
- function hasReplaceVal(val) { return function(obj) { return obj.from === val; }; }
- function $replace(value) {
- var replacement = map(filter(self.replace, hasReplaceVal(value)), function(obj) { return obj.to; });
- return replacement.length ? replacement[0] : value;
- }
- value = $replace(value);
- return isDefined(value) ? self.type.decode(value) : $$getDefaultValue();
- }
-
- function toString() { return "{Param:" + id + " " + type + " squash: '" + squash + "' optional: " + isOptional + "}"; }
-
- extend(this, {
- id: id,
- type: type,
- location: location,
- array: arrayMode,
- squash: squash,
- replace: replace,
- isOptional: isOptional,
- value: $value,
- dynamic: undefined,
- config: config,
- toString: toString
- });
- };
-
- function ParamSet(params) {
- extend(this, params || {});
- }
-
- ParamSet.prototype = {
- $$new: function() {
- return inherit(this, extend(new ParamSet(), { $$parent: this}));
- },
- $$keys: function () {
- var keys = [], chain = [], parent = this,
- ignore = objectKeys(ParamSet.prototype);
- while (parent) { chain.push(parent); parent = parent.$$parent; }
- chain.reverse();
- forEach(chain, function(paramset) {
- forEach(objectKeys(paramset), function(key) {
- if (indexOf(keys, key) === -1 && indexOf(ignore, key) === -1) keys.push(key);
- });
- });
- return keys;
- },
- $$values: function(paramValues) {
- var values = {}, self = this;
- forEach(self.$$keys(), function(key) {
- values[key] = self[key].value(paramValues && paramValues[key]);
- });
- return values;
- },
- $$equals: function(paramValues1, paramValues2) {
- var equal = true, self = this;
- forEach(self.$$keys(), function(key) {
- var left = paramValues1 && paramValues1[key], right = paramValues2 && paramValues2[key];
- if (!self[key].type.equals(left, right)) equal = false;
- });
- return equal;
- },
- $$validates: function $$validate(paramValues) {
- var result = true, isOptional, val, param, self = this;
-
- forEach(this.$$keys(), function(key) {
- param = self[key];
- val = paramValues[key];
- isOptional = !val && param.isOptional;
- result = result && (isOptional || !!param.type.is(val));
- });
- return result;
- },
- $$parent: undefined
- };
-
- this.ParamSet = ParamSet;
-}
-
-// Register as a provider so it's available to other providers
-angular.module('ui.router.util').provider('$urlMatcherFactory', $UrlMatcherFactory);
-angular.module('ui.router.util').run(['$urlMatcherFactory', function($urlMatcherFactory) { }]);
-
-/**
- * @ngdoc object
- * @name ui.router.router.$urlRouterProvider
- *
- * @requires ui.router.util.$urlMatcherFactoryProvider
- * @requires $locationProvider
- *
- * @description
- * `$urlRouterProvider` has the responsibility of watching `$location`.
- * When `$location` changes it runs through a list of rules one by one until a
- * match is found. `$urlRouterProvider` is used behind the scenes anytime you specify
- * a url in a state configuration. All urls are compiled into a UrlMatcher object.
- *
- * There are several methods on `$urlRouterProvider` that make it useful to use directly
- * in your module config.
- */
-$UrlRouterProvider.$inject = ['$locationProvider', '$urlMatcherFactoryProvider'];
-function $UrlRouterProvider( $locationProvider, $urlMatcherFactory) {
- var rules = [], otherwise = null, interceptDeferred = false, listener;
-
- // Returns a string that is a prefix of all strings matching the RegExp
- function regExpPrefix(re) {
- var prefix = /^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(re.source);
- return (prefix != null) ? prefix[1].replace(/\\(.)/g, "$1") : '';
- }
-
- // Interpolates matched values into a String.replace()-style pattern
- function interpolate(pattern, match) {
- return pattern.replace(/\$(\$|\d{1,2})/, function (m, what) {
- return match[what === '$' ? 0 : Number(what)];
- });
- }
-
- /**
- * @ngdoc function
- * @name ui.router.router.$urlRouterProvider#rule
- * @methodOf ui.router.router.$urlRouterProvider
- *
- * @description
- * Defines rules that are used by `$urlRouterProvider` to find matches for
- * specific URLs.
- *
- * @example
- *
- * var app = angular.module('app', ['ui.router.router']);
- *
- * app.config(function ($urlRouterProvider) {
- * // Here's an example of how you might allow case insensitive urls
- * $urlRouterProvider.rule(function ($injector, $location) {
- * var path = $location.path(),
- * normalized = path.toLowerCase();
- *
- * if (path !== normalized) {
- * return normalized;
- * }
- * });
- * });
- *
- *
- * @param {object} rule Handler function that takes `$injector` and `$location`
- * services as arguments. You can use them to return a valid path as a string.
- *
- * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
- */
- this.rule = function (rule) {
- if (!isFunction(rule)) throw new Error("'rule' must be a function");
- rules.push(rule);
- return this;
- };
-
- /**
- * @ngdoc object
- * @name ui.router.router.$urlRouterProvider#otherwise
- * @methodOf ui.router.router.$urlRouterProvider
- *
- * @description
- * Defines a path that is used when an invalid route is requested.
- *
- * @example
- *
- * var app = angular.module('app', ['ui.router.router']);
- *
- * app.config(function ($urlRouterProvider) {
- * // if the path doesn't match any of the urls you configured
- * // otherwise will take care of routing the user to the
- * // specified url
- * $urlRouterProvider.otherwise('/index');
- *
- * // Example of using function rule as param
- * $urlRouterProvider.otherwise(function ($injector, $location) {
- * return '/a/valid/url';
- * });
- * });
- *
- *
- * @param {string|object} rule The url path you want to redirect to or a function
- * rule that returns the url path. The function version is passed two params:
- * `$injector` and `$location` services, and must return a url string.
- *
- * @return {object} `$urlRouterProvider` - `$urlRouterProvider` instance
- */
- this.otherwise = function (rule) {
- if (isString(rule)) {
- var redirect = rule;
- rule = function () { return redirect; };
- }
- else if (!isFunction(rule)) throw new Error("'rule' must be a function");
- otherwise = rule;
- return this;
- };
-
-
- function handleIfMatch($injector, handler, match) {
- if (!match) return false;
- var result = $injector.invoke(handler, handler, { $match: match });
- return isDefined(result) ? result : true;
- }
-
- /**
- * @ngdoc function
- * @name ui.router.router.$urlRouterProvider#when
- * @methodOf ui.router.router.$urlRouterProvider
- *
- * @description
- * Registers a handler for a given url matching. if handle is a string, it is
- * treated as a redirect, and is interpolated according to the syntax of match
- * (i.e. like `String.replace()` for `RegExp`, or like a `UrlMatcher` pattern otherwise).
- *
- * If the handler is a function, it is injectable. It gets invoked if `$location`
- * matches. You have the option of inject the match object as `$match`.
- *
- * The handler can return
- *
- * - **falsy** to indicate that the rule didn't match after all, then `$urlRouter`
- * will continue trying to find another one that matches.
- * - **string** which is treated as a redirect and passed to `$location.url()`
- * - **void** or any **truthy** value tells `$urlRouter` that the url was handled.
- *
- * @example
- *
- *
- * @param {string|object} what The incoming path that you want to redirect.
- * @param {string|object} handler The path you want to redirect your user to.
- */
- this.when = function (what, handler) {
- var redirect, handlerIsString = isString(handler);
- if (isString(what)) what = $urlMatcherFactory.compile(what);
-
- if (!handlerIsString && !isFunction(handler) && !isArray(handler))
- throw new Error("invalid 'handler' in when()");
-
- var strategies = {
- matcher: function (what, handler) {
- if (handlerIsString) {
- redirect = $urlMatcherFactory.compile(handler);
- handler = ['$match', function ($match) { return redirect.format($match); }];
- }
- return extend(function ($injector, $location) {
- return handleIfMatch($injector, handler, what.exec($location.path(), $location.search()));
- }, {
- prefix: isString(what.prefix) ? what.prefix : ''
- });
- },
- regex: function (what, handler) {
- if (what.global || what.sticky) throw new Error("when() RegExp must not be global or sticky");
-
- if (handlerIsString) {
- redirect = handler;
- handler = ['$match', function ($match) { return interpolate(redirect, $match); }];
- }
- return extend(function ($injector, $location) {
- return handleIfMatch($injector, handler, what.exec($location.path()));
- }, {
- prefix: regExpPrefix(what)
- });
- }
- };
-
- var check = { matcher: $urlMatcherFactory.isMatcher(what), regex: what instanceof RegExp };
-
- for (var n in check) {
- if (check[n]) return this.rule(strategies[n](what, handler));
- }
-
- throw new Error("invalid 'what' in when()");
- };
-
- /**
- * @ngdoc function
- * @name ui.router.router.$urlRouterProvider#deferIntercept
- * @methodOf ui.router.router.$urlRouterProvider
- *
- * @description
- * Disables (or enables) deferring location change interception.
- *
- * If you wish to customize the behavior of syncing the URL (for example, if you wish to
- * defer a transition but maintain the current URL), call this method at configuration time.
- * Then, at run time, call `$urlRouter.listen()` after you have configured your own
- * `$locationChangeSuccess` event handler.
- *
- * @example
- *
- * var app = angular.module('app', ['ui.router.router']);
- *
- * app.config(function ($urlRouterProvider) {
- *
- * // Prevent $urlRouter from automatically intercepting URL changes;
- * // this allows you to configure custom behavior in between
- * // location changes and route synchronization:
- * $urlRouterProvider.deferIntercept();
- *
- * }).run(function ($rootScope, $urlRouter, UserService) {
- *
- * $rootScope.$on('$locationChangeSuccess', function(e) {
- * // UserService is an example service for managing user state
- * if (UserService.isLoggedIn()) return;
- *
- * // Prevent $urlRouter's default handler from firing
- * e.preventDefault();
- *
- * UserService.handleLogin().then(function() {
- * // Once the user has logged in, sync the current URL
- * // to the router:
- * $urlRouter.sync();
- * });
- * });
- *
- * // Configures $urlRouter's listener *after* your custom listener
- * $urlRouter.listen();
- * });
- *
- *
- * @param {boolean} defer Indicates whether to defer location change interception. Passing
- no parameter is equivalent to `true`.
- */
- this.deferIntercept = function (defer) {
- if (defer === undefined) defer = true;
- interceptDeferred = defer;
- };
-
- /**
- * @ngdoc object
- * @name ui.router.router.$urlRouter
- *
- * @requires $location
- * @requires $rootScope
- * @requires $injector
- * @requires $browser
- *
- * @description
- *
- */
- this.$get = $get;
- $get.$inject = ['$location', '$rootScope', '$injector', '$browser'];
- function $get( $location, $rootScope, $injector, $browser) {
-
- var baseHref = $browser.baseHref(), location = $location.url(), lastPushedUrl;
-
- function appendBasePath(url, isHtml5, absolute) {
- if (baseHref === '/') return url;
- if (isHtml5) return baseHref.slice(0, -1) + url;
- if (absolute) return baseHref.slice(1) + url;
- return url;
- }
-
- // TODO: Optimize groups of rules with non-empty prefix into some sort of decision tree
- function update(evt) {
- if (evt && evt.defaultPrevented) return;
- var ignoreUpdate = lastPushedUrl && $location.url() === lastPushedUrl;
- lastPushedUrl = undefined;
- if (ignoreUpdate) return true;
-
- function check(rule) {
- var handled = rule($injector, $location);
-
- if (!handled) return false;
- if (isString(handled)) $location.replace().url(handled);
- return true;
- }
- var n = rules.length, i;
-
- for (i = 0; i < n; i++) {
- if (check(rules[i])) return;
- }
- // always check otherwise last to allow dynamic updates to the set of rules
- if (otherwise) check(otherwise);
- }
-
- function listen() {
- listener = listener || $rootScope.$on('$locationChangeSuccess', update);
- return listener;
- }
-
- if (!interceptDeferred) listen();
-
- return {
- /**
- * @ngdoc function
- * @name ui.router.router.$urlRouter#sync
- * @methodOf ui.router.router.$urlRouter
- *
- * @description
- * Triggers an update; the same update that happens when the address bar url changes, aka `$locationChangeSuccess`.
- * This method is useful when you need to use `preventDefault()` on the `$locationChangeSuccess` event,
- * perform some custom logic (route protection, auth, config, redirection, etc) and then finally proceed
- * with the transition by calling `$urlRouter.sync()`.
- *
- * @example
- *
- * angular.module('app', ['ui.router'])
- * .run(function($rootScope, $urlRouter) {
- * $rootScope.$on('$locationChangeSuccess', function(evt) {
- * // Halt state change from even starting
- * evt.preventDefault();
- * // Perform custom logic
- * var meetsRequirement = ...
- * // Continue with the update and state transition if logic allows
- * if (meetsRequirement) $urlRouter.sync();
- * });
- * });
- *
- *
- * @param {UrlMatcher} urlMatcher The `UrlMatcher` object which is used as the template of the URL to generate.
- * @param {object=} params An object of parameter values to fill the matcher's required parameters.
- * @param {object=} options Options object. The options are:
- *
- * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
- *
- * @returns {string} Returns the fully compiled URL, or `null` if `params` fail validation against `urlMatcher`
- */
- href: function(urlMatcher, params, options) {
- if (!urlMatcher.validates(params)) return null;
-
- var isHtml5 = $locationProvider.html5Mode();
- if (angular.isObject(isHtml5)) {
- isHtml5 = isHtml5.enabled;
- }
-
- var url = urlMatcher.format(params);
- options = options || {};
-
- if (!isHtml5 && url !== null) {
- url = "#" + $locationProvider.hashPrefix() + url;
- }
- url = appendBasePath(url, isHtml5, options.absolute);
-
- if (!options.absolute || !url) {
- return url;
- }
-
- var slash = (!isHtml5 && url ? '/' : ''), port = $location.port();
- port = (port === 80 || port === 443 ? '' : ':' + port);
-
- return [$location.protocol(), '://', $location.host(), port, slash, url].join('');
- }
- };
- }
-}
-
-angular.module('ui.router.router').provider('$urlRouter', $UrlRouterProvider);
-
-/**
- * @ngdoc object
- * @name ui.router.state.$stateProvider
- *
- * @requires ui.router.router.$urlRouterProvider
- * @requires ui.router.util.$urlMatcherFactoryProvider
- *
- * @description
- * The new `$stateProvider` works similar to Angular's v1 router, but it focuses purely
- * on state.
- *
- * A state corresponds to a "place" in the application in terms of the overall UI and
- * navigation. A state describes (via the controller / template / view properties) what
- * the UI looks like and does at that place.
- *
- * States often have things in common, and the primary way of factoring out these
- * commonalities in this model is via the state hierarchy, i.e. parent/child states aka
- * nested states.
- *
- * The `$stateProvider` provides interfaces to declare these states for your app.
- */
-$StateProvider.$inject = ['$urlRouterProvider', '$urlMatcherFactoryProvider'];
-function $StateProvider( $urlRouterProvider, $urlMatcherFactory) {
-
- var root, states = {}, $state, queue = {}, abstractKey = 'abstract';
-
- // Builds state properties from definition passed to registerState()
- var stateBuilder = {
-
- // Derive parent state from a hierarchical name only if 'parent' is not explicitly defined.
- // state.children = [];
- // if (parent) parent.children.push(state);
- parent: function(state) {
- if (isDefined(state.parent) && state.parent) return findState(state.parent);
- // regex matches any valid composite state name
- // would match "contact.list" but not "contacts"
- var compositeName = /^(.+)\.[^.]+$/.exec(state.name);
- return compositeName ? findState(compositeName[1]) : root;
- },
-
- // inherit 'data' from parent and override by own values (if any)
- data: function(state) {
- if (state.parent && state.parent.data) {
- state.data = state.self.data = extend({}, state.parent.data, state.data);
- }
- return state.data;
- },
-
- // Build a URLMatcher if necessary, either via a relative or absolute URL
- url: function(state) {
- var url = state.url, config = { params: state.params || {} };
-
- if (isString(url)) {
- if (url.charAt(0) == '^') return $urlMatcherFactory.compile(url.substring(1), config);
- return (state.parent.navigable || root).url.concat(url, config);
- }
-
- if (!url || $urlMatcherFactory.isMatcher(url)) return url;
- throw new Error("Invalid url '" + url + "' in state '" + state + "'");
- },
-
- // Keep track of the closest ancestor state that has a URL (i.e. is navigable)
- navigable: function(state) {
- return state.url ? state : (state.parent ? state.parent.navigable : null);
- },
-
- // Own parameters for this state. state.url.params is already built at this point. Create and add non-url params
- ownParams: function(state) {
- var params = state.url && state.url.params || new $$UMFP.ParamSet();
- forEach(state.params || {}, function(config, id) {
- if (!params[id]) params[id] = new $$UMFP.Param(id, null, config, "config");
- });
- return params;
- },
-
- // Derive parameters for this state and ensure they're a super-set of parent's parameters
- params: function(state) {
- return state.parent && state.parent.params ? extend(state.parent.params.$$new(), state.ownParams) : new $$UMFP.ParamSet();
- },
-
- // If there is no explicit multi-view configuration, make one up so we don't have
- // to handle both cases in the view directive later. Note that having an explicit
- // 'views' property will mean the default unnamed view properties are ignored. This
- // is also a good time to resolve view names to absolute names, so everything is a
- // straight lookup at link time.
- views: function(state) {
- var views = {};
-
- forEach(isDefined(state.views) ? state.views : { '': state }, function (view, name) {
- if (name.indexOf('@') < 0) name += '@' + state.parent.name;
- views[name] = view;
- });
- return views;
- },
-
- // Keep a full path from the root down to this state as this is needed for state activation.
- path: function(state) {
- return state.parent ? state.parent.path.concat(state) : []; // exclude root from path
- },
-
- // Speed up $state.contains() as it's used a lot
- includes: function(state) {
- var includes = state.parent ? extend({}, state.parent.includes) : {};
- includes[state.name] = true;
- return includes;
- },
-
- $delegates: {}
- };
-
- function isRelative(stateName) {
- return stateName.indexOf(".") === 0 || stateName.indexOf("^") === 0;
- }
-
- function findState(stateOrName, base) {
- if (!stateOrName) return undefined;
-
- var isStr = isString(stateOrName),
- name = isStr ? stateOrName : stateOrName.name,
- path = isRelative(name);
-
- if (path) {
- if (!base) throw new Error("No reference point given for path '" + name + "'");
- base = findState(base);
-
- var rel = name.split("."), i = 0, pathLength = rel.length, current = base;
-
- for (; i < pathLength; i++) {
- if (rel[i] === "" && i === 0) {
- current = base;
- continue;
- }
- if (rel[i] === "^") {
- if (!current.parent) throw new Error("Path '" + name + "' not valid for state '" + base.name + "'");
- current = current.parent;
- continue;
- }
- break;
- }
- rel = rel.slice(i).join(".");
- name = current.name + (current.name && rel ? "." : "") + rel;
- }
- var state = states[name];
-
- if (state && (isStr || (!isStr && (state === stateOrName || state.self === stateOrName)))) {
- return state;
- }
- return undefined;
- }
-
- function queueState(parentName, state) {
- if (!queue[parentName]) {
- queue[parentName] = [];
- }
- queue[parentName].push(state);
- }
-
- function flushQueuedChildren(parentName) {
- var queued = queue[parentName] || [];
- while(queued.length) {
- registerState(queued.shift());
- }
- }
-
- function registerState(state) {
- // Wrap a new object around the state so we can store our private details easily.
- state = inherit(state, {
- self: state,
- resolve: state.resolve || {},
- toString: function() { return this.name; }
- });
-
- var name = state.name;
- if (!isString(name) || name.indexOf('@') >= 0) throw new Error("State must have a valid name");
- if (states.hasOwnProperty(name)) throw new Error("State '" + name + "'' is already defined");
-
- // Get parent name
- var parentName = (name.indexOf('.') !== -1) ? name.substring(0, name.lastIndexOf('.'))
- : (isString(state.parent)) ? state.parent
- : (isObject(state.parent) && isString(state.parent.name)) ? state.parent.name
- : '';
-
- // If parent is not registered yet, add state to queue and register later
- if (parentName && !states[parentName]) {
- return queueState(parentName, state.self);
- }
-
- for (var key in stateBuilder) {
- if (isFunction(stateBuilder[key])) state[key] = stateBuilder[key](state, stateBuilder.$delegates[key]);
- }
- states[name] = state;
-
- // Register the state in the global state list and with $urlRouter if necessary.
- if (!state[abstractKey] && state.url) {
- $urlRouterProvider.when(state.url, ['$match', '$stateParams', function ($match, $stateParams) {
- if ($state.$current.navigable != state || !equalForKeys($match, $stateParams)) {
- $state.transitionTo(state, $match, { inherit: true, location: false });
- }
- }]);
- }
-
- // Register any queued children
- flushQueuedChildren(name);
-
- return state;
- }
-
- // Checks text to see if it looks like a glob.
- function isGlob (text) {
- return text.indexOf('*') > -1;
- }
-
- // Returns true if glob matches current $state name.
- function doesStateMatchGlob (glob) {
- var globSegments = glob.split('.'),
- segments = $state.$current.name.split('.');
-
- //match greedy starts
- if (globSegments[0] === '**') {
- segments = segments.slice(indexOf(segments, globSegments[1]));
- segments.unshift('**');
- }
- //match greedy ends
- if (globSegments[globSegments.length - 1] === '**') {
- segments.splice(indexOf(segments, globSegments[globSegments.length - 2]) + 1, Number.MAX_VALUE);
- segments.push('**');
- }
-
- if (globSegments.length != segments.length) {
- return false;
- }
-
- //match single stars
- for (var i = 0, l = globSegments.length; i < l; i++) {
- if (globSegments[i] === '*') {
- segments[i] = '*';
- }
- }
-
- return segments.join('') === globSegments.join('');
- }
-
-
- // Implicit root state that is always active
- root = registerState({
- name: '',
- url: '^',
- views: null,
- 'abstract': true
- });
- root.navigable = null;
-
-
- /**
- * @ngdoc function
- * @name ui.router.state.$stateProvider#decorator
- * @methodOf ui.router.state.$stateProvider
- *
- * @description
- * Allows you to extend (carefully) or override (at your own peril) the
- * `stateBuilder` object used internally by `$stateProvider`. This can be used
- * to add custom functionality to ui-router, for example inferring templateUrl
- * based on the state name.
- *
- * When passing only a name, it returns the current (original or decorated) builder
- * function that matches `name`.
- *
- * The builder functions that can be decorated are listed below. Though not all
- * necessarily have a good use case for decoration, that is up to you to decide.
- *
- * In addition, users can attach custom decorators, which will generate new
- * properties within the state's internal definition. There is currently no clear
- * use-case for this beyond accessing internal states (i.e. $state.$current),
- * however, expect this to become increasingly relevant as we introduce additional
- * meta-programming features.
- *
- * **Warning**: Decorators should not be interdependent because the order of
- * execution of the builder functions in non-deterministic. Builder functions
- * should only be dependent on the state definition object and super function.
- *
- *
- * Existing builder functions and current return values:
- *
- * - **parent** `{object}` - returns the parent state object.
- * - **data** `{object}` - returns state data, including any inherited data that is not
- * overridden by own values (if any).
- * - **url** `{object}` - returns a {@link ui.router.util.type:UrlMatcher UrlMatcher}
- * or `null`.
- * - **navigable** `{object}` - returns closest ancestor state that has a URL (aka is
- * navigable).
- * - **params** `{object}` - returns an array of state params that are ensured to
- * be a super-set of parent's params.
- * - **views** `{object}` - returns a views object where each key is an absolute view
- * name (i.e. "viewName@stateName") and each value is the config object
- * (template, controller) for the view. Even when you don't use the views object
- * explicitly on a state config, one is still created for you internally.
- * So by decorating this builder function you have access to decorating template
- * and controller properties.
- * - **ownParams** `{object}` - returns an array of params that belong to the state,
- * not including any params defined by ancestor states.
- * - **path** `{string}` - returns the full path from the root down to this state.
- * Needed for state activation.
- * - **includes** `{object}` - returns an object that includes every state that
- * would pass a `$state.includes()` test.
- *
- * @example
- *
- * // Override the internal 'views' builder with a function that takes the state
- * // definition, and a reference to the internal function being overridden:
- * $stateProvider.decorator('views', function (state, parent) {
- * var result = {},
- * views = parent(state);
- *
- * angular.forEach(views, function (config, name) {
- * var autoName = (state.name + '.' + name).replace('.', '/');
- * config.templateUrl = config.templateUrl || '/partials/' + autoName + '.html';
- * result[name] = config;
- * });
- * return result;
- * });
- *
- * $stateProvider.state('home', {
- * views: {
- * 'contact.list': { controller: 'ListController' },
- * 'contact.item': { controller: 'ItemController' }
- * }
- * });
- *
- * // ...
- *
- * $state.go('home');
- * // Auto-populates list and item views with /partials/home/contact/list.html,
- * // and /partials/home/contact/item.html, respectively.
- *
- *
- * @param {string} name The name of the builder function to decorate.
- * @param {object} func A function that is responsible for decorating the original
- * builder function. The function receives two parameters:
- *
- * - `{object}` - state - The state config object.
- * - `{object}` - super - The original builder function.
- *
- * @return {object} $stateProvider - $stateProvider instance
- */
- this.decorator = decorator;
- function decorator(name, func) {
- /*jshint validthis: true */
- if (isString(name) && !isDefined(func)) {
- return stateBuilder[name];
- }
- if (!isFunction(func) || !isString(name)) {
- return this;
- }
- if (stateBuilder[name] && !stateBuilder.$delegates[name]) {
- stateBuilder.$delegates[name] = stateBuilder[name];
- }
- stateBuilder[name] = func;
- return this;
- }
-
- /**
- * @ngdoc function
- * @name ui.router.state.$stateProvider#state
- * @methodOf ui.router.state.$stateProvider
- *
- * @description
- * Registers a state configuration under a given state name. The stateConfig object
- * has the following acceptable properties.
- *
- * @param {string} name A unique state name, e.g. "home", "about", "contacts".
- * To create a parent/child state use a dot, e.g. "about.sales", "home.newest".
- * @param {object} stateConfig State configuration object.
- * @param {string|function=} stateConfig.template
- *
- * html template as a string or a function that returns
- * an html template as a string which should be used by the uiView directives. This property
- * takes precedence over templateUrl.
- *
- * If `template` is a function, it will be called with the following parameters:
- *
- * - {array.<object>} - state parameters extracted from the current $location.path() by
- * applying the current state
- *
- *
template:
- * "
inline template definition
" +
- * ""
- *
template: function(params) {
- * return "
generated template
"; }
- *
- *
- * @param {string|function=} stateConfig.templateUrl
- *
- *
- * path or function that returns a path to an html
- * template that should be used by uiView.
- *
- * If `templateUrl` is a function, it will be called with the following parameters:
- *
- * - {array.<object>} - state parameters extracted from the current $location.path() by
- * applying the current state
- *
- *
- *
- * @param {string|function=} stateConfig.controller
- *
- *
- * Controller fn that should be associated with newly
- * related scope or the name of a registered controller if passed as a string.
- * Optionally, the ControllerAs may be declared here.
- *
controller: "MyRegisteredController"
- *
controller:
- * "MyRegisteredController as fooCtrl"}
- *
- * @param {string=} stateConfig.controllerAs
- *
- *
- * A controller alias name. If present the controller will be
- * published to scope under the controllerAs name.
- *
controllerAs: "myCtrl"
- *
- * @param {object=} stateConfig.resolve
- *
- *
- * An optional map<string, function> of dependencies which
- * should be injected into the controller. If any of these dependencies are promises,
- * the router will wait for them all to be resolved before the controller is instantiated.
- * If all the promises are resolved successfully, the $stateChangeSuccess event is fired
- * and the values of the resolved promises are injected into any controllers that reference them.
- * If any of the promises are rejected the $stateChangeError event is fired.
- *
- * The map object is:
- *
- * - key - {string}: name of dependency to be injected into controller
- * - factory - {string|function}: If string then it is alias for service. Otherwise if function,
- * it is injected and return value it treated as dependency. If result is a promise, it is
- * resolved before its value is injected into controller.
- *
- *
- *
- * @param {string=} stateConfig.url
- *
- *
- * A url fragment with optional parameters. When a state is navigated or
- * transitioned to, the `$stateParams` service will be populated with any
- * parameters that were passed.
- *
- * examples:
- *
- *
- * @param {boolean=} [stateConfig.abstract=false]
- *
- * An abstract state will never be directly activated,
- * but can provide inherited properties to its common children states.
- *
abstract: true
- *
- * @param {function=} stateConfig.onEnter
- *
- *
- * Callback function for when a state is entered. Good way
- * to trigger an action or dispatch an event, such as opening a dialog.
- * If minifying your scripts, make sure to explictly annotate this function,
- * because it won't be automatically annotated by your build tools.
- *
- *
- *
- * @param {function=} stateConfig.onExit
- *
- *
- * Callback function for when a state is exited. Good way to
- * trigger an action or dispatch an event, such as opening a dialog.
- * If minifying your scripts, make sure to explictly annotate this function,
- * because it won't be automatically annotated by your build tools.
- *
- *
- *
- * @param {boolean=} [stateConfig.reloadOnSearch=true]
- *
- *
- * If `false`, will not retrigger the same state
- * just because a search/query parameter has changed (via $location.search() or $location.hash()).
- * Useful for when you'd like to modify $location.search() without triggering a reload.
- *
reloadOnSearch: false
- *
- * @param {object=} stateConfig.data
- *
- *
- * Arbitrary data object, useful for custom configuration. The parent state's `data` is
- * prototypally inherited. In other words, adding a data property to a state adds it to
- * the entire subtree via prototypal inheritance.
- *
- *
data: {
- * requiredRole: 'foo'
- * }
- *
- * @param {object=} stateConfig.params
- *
- *
- * A map which optionally configures parameters declared in the `url`, or
- * defines additional non-url parameters. For each parameter being
- * configured, add a configuration object keyed to the name of the parameter.
- *
- * Each parameter configuration object may contain the following properties:
- *
- * - ** value ** - {object|function=}: specifies the default value for this
- * parameter. This implicitly sets this parameter as optional.
- *
- * When UI-Router routes to a state and no value is
- * specified for this parameter in the URL or transition, the
- * default value will be used instead. If `value` is a function,
- * it will be injected and invoked, and the return value used.
- *
- * *Note*: `undefined` is treated as "no default value" while `null`
- * is treated as "the default value is `null`".
- *
- * *Shorthand*: If you only need to configure the default value of the
- * parameter, you may use a shorthand syntax. In the **`params`**
- * map, instead mapping the param name to a full parameter configuration
- * object, simply set map it to the default parameter value, e.g.:
- *
- *
- *
- * - ** array ** - {boolean=}: *(default: false)* If true, the param value will be
- * treated as an array of values. If you specified a Type, the value will be
- * treated as an array of the specified Type. Note: query parameter values
- * default to a special `"auto"` mode.
- *
- * For query parameters in `"auto"` mode, if multiple values for a single parameter
- * are present in the URL (e.g.: `/foo?bar=1&bar=2&bar=3`) then the values
- * are mapped to an array (e.g.: `{ foo: [ '1', '2', '3' ] }`). However, if
- * only one value is present (e.g.: `/foo?bar=1`) then the value is treated as single
- * value (e.g.: `{ foo: '1' }`).
- *
- *
params: {
- * param1: { array: true }
- * }
- *
- * - ** squash ** - {bool|string=}: `squash` configures how a default parameter value is represented in the URL when
- * the current parameter value is the same as the default value. If `squash` is not set, it uses the
- * configured default squash policy.
- * (See {@link ui.router.util.$urlMatcherFactory#methods_defaultSquashPolicy `defaultSquashPolicy()`})
- *
- * There are three squash settings:
- *
- * - false: The parameter's default value is not squashed. It is encoded and included in the URL
- * - true: The parameter's default value is omitted from the URL. If the parameter is preceeded and followed
- * by slashes in the state's `url` declaration, then one of those slashes are omitted.
- * This can allow for cleaner looking URLs.
- * - `""`: The parameter's default value is replaced with an arbitrary placeholder of your choice.
- *
- *
- * // Some state name examples
- *
- * // stateName can be a single top-level name (must be unique).
- * $stateProvider.state("home", {});
- *
- * // Or it can be a nested state name. This state is a child of the
- * // above "home" state.
- * $stateProvider.state("home.newest", {});
- *
- * // Nest states as deeply as needed.
- * $stateProvider.state("home.newest.abc.xyz.inception", {});
- *
- * // state() returns $stateProvider, so you can chain state declarations.
- * $stateProvider
- * .state("home", {})
- * .state("about", {})
- * .state("contacts", {});
- *
- *
- */
- this.state = state;
- function state(name, definition) {
- /*jshint validthis: true */
- if (isObject(name)) definition = name;
- else definition.name = name;
- registerState(definition);
- return this;
- }
-
- /**
- * @ngdoc object
- * @name ui.router.state.$state
- *
- * @requires $rootScope
- * @requires $q
- * @requires ui.router.state.$view
- * @requires $injector
- * @requires ui.router.util.$resolve
- * @requires ui.router.state.$stateParams
- * @requires ui.router.router.$urlRouter
- *
- * @property {object} params A param object, e.g. {sectionId: section.id)}, that
- * you'd like to test against the current active state.
- * @property {object} current A reference to the state's config object. However
- * you passed it in. Useful for accessing custom data.
- * @property {object} transition Currently pending transition. A promise that'll
- * resolve or reject.
- *
- * @description
- * `$state` service is responsible for representing states as well as transitioning
- * between them. It also provides interfaces to ask for current state or even states
- * you're coming from.
- */
- this.$get = $get;
- $get.$inject = ['$rootScope', '$q', '$view', '$injector', '$resolve', '$stateParams', '$urlRouter', '$location', '$urlMatcherFactory'];
- function $get( $rootScope, $q, $view, $injector, $resolve, $stateParams, $urlRouter, $location, $urlMatcherFactory) {
-
- var TransitionSuperseded = $q.reject(new Error('transition superseded'));
- var TransitionPrevented = $q.reject(new Error('transition prevented'));
- var TransitionAborted = $q.reject(new Error('transition aborted'));
- var TransitionFailed = $q.reject(new Error('transition failed'));
-
- // Handles the case where a state which is the target of a transition is not found, and the user
- // can optionally retry or defer the transition
- function handleRedirect(redirect, state, params, options) {
- /**
- * @ngdoc event
- * @name ui.router.state.$state#$stateNotFound
- * @eventOf ui.router.state.$state
- * @eventType broadcast on root scope
- * @description
- * Fired when a requested state **cannot be found** using the provided state name during transition.
- * The event is broadcast allowing any handlers a single chance to deal with the error (usually by
- * lazy-loading the unfound state). A special `unfoundState` object is passed to the listener handler,
- * you can see its three properties in the example. You can use `event.preventDefault()` to abort the
- * transition and the promise returned from `go` will be rejected with a `'transition aborted'` value.
- *
- * @param {Object} event Event object.
- * @param {Object} unfoundState Unfound State information. Contains: `to, toParams, options` properties.
- * @param {State} fromState Current state object.
- * @param {Object} fromParams Current state params.
- *
- * @example
- *
- *
- *
- * @returns {promise} A promise representing the state of the new transition. See
- * {@link ui.router.state.$state#methods_go $state.go}.
- */
- $state.reload = function reload() {
- return $state.transitionTo($state.current, $stateParams, { reload: true, inherit: false, notify: true });
- };
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#go
- * @methodOf ui.router.state.$state
- *
- * @description
- * Convenience method for transitioning to a new state. `$state.go` calls
- * `$state.transitionTo` internally but automatically sets options to
- * `{ location: true, inherit: true, relative: $state.$current, notify: true }`.
- * This allows you to easily use an absolute or relative to path and specify
- * only the parameters you'd like to update (while letting unspecified parameters
- * inherit from the currently active ancestor states).
- *
- * @example
- *
- *
- *
- * @param {string} to Absolute state name or relative state path. Some examples:
- *
- * - `$state.go('contact.detail')` - will go to the `contact.detail` state
- * - `$state.go('^')` - will go to a parent state
- * - `$state.go('^.sibling')` - will go to a sibling state
- * - `$state.go('.child.grandchild')` - will go to grandchild state
- *
- * @param {object=} params A map of the parameters that will be sent to the state,
- * will populate $stateParams. Any parameters that are not specified will be inherited from currently
- * defined parameters. This allows, for example, going to a sibling state that shares parameters
- * specified in a parent state. Parameter inheritance only works between common ancestor states, I.e.
- * transitioning to a sibling will get you the parameters for all parents, transitioning to a child
- * will get you all current parameters, etc.
- * @param {object=} options Options object. The options are:
- *
- * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
- * will not. If string, must be `"replace"`, which will update url and also replace last history record.
- * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
- * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
- * defines which state to be relative from.
- * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
- * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
- * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
- * use this when you want to force a reload when *everything* is the same, including search params.
- *
- * @returns {promise} A promise representing the state of the new transition.
- *
- * Possible success values:
- *
- * - $state.current
- *
- * Possible rejection values:
- *
- * - 'transition superseded' - when a newer transition has been started after this one
- * - 'transition prevented' - when `event.preventDefault()` has been called in a `$stateChangeStart` listener
- * - 'transition aborted' - when `event.preventDefault()` has been called in a `$stateNotFound` listener or
- * when a `$stateNotFound` `event.retry` promise errors.
- * - 'transition failed' - when a state has been unsuccessfully found after 2 tries.
- * - *resolve error* - when an error has occurred with a `resolve`
- *
- */
- $state.go = function go(to, params, options) {
- return $state.transitionTo(to, params, extend({ inherit: true, relative: $state.$current }, options));
- };
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#transitionTo
- * @methodOf ui.router.state.$state
- *
- * @description
- * Low-level method for transitioning to a new state. {@link ui.router.state.$state#methods_go $state.go}
- * uses `transitionTo` internally. `$state.go` is recommended in most situations.
- *
- * @example
- *
- *
- * @param {string} to State name.
- * @param {object=} toParams A map of the parameters that will be sent to the state,
- * will populate $stateParams.
- * @param {object=} options Options object. The options are:
- *
- * - **`location`** - {boolean=true|string=} - If `true` will update the url in the location bar, if `false`
- * will not. If string, must be `"replace"`, which will update url and also replace last history record.
- * - **`inherit`** - {boolean=false}, If `true` will inherit url parameters from current url.
- * - **`relative`** - {object=}, When transitioning with relative path (e.g '^'),
- * defines which state to be relative from.
- * - **`notify`** - {boolean=true}, If `true` will broadcast $stateChangeStart and $stateChangeSuccess events.
- * - **`reload`** (v0.2.5) - {boolean=false}, If `true` will force transition even if the state or params
- * have not changed, aka a reload of the same state. It differs from reloadOnSearch because you'd
- * use this when you want to force a reload when *everything* is the same, including search params.
- *
- * @returns {promise} A promise representing the state of the new transition. See
- * {@link ui.router.state.$state#methods_go $state.go}.
- */
- $state.transitionTo = function transitionTo(to, toParams, options) {
- toParams = toParams || {};
- options = extend({
- location: true, inherit: false, relative: null, notify: true, reload: false, $retry: false
- }, options || {});
-
- var from = $state.$current, fromParams = $state.params, fromPath = from.path;
- var evt, toState = findState(to, options.relative);
-
- if (!isDefined(toState)) {
- var redirect = { to: to, toParams: toParams, options: options };
- var redirectResult = handleRedirect(redirect, from.self, fromParams, options);
-
- if (redirectResult) {
- return redirectResult;
- }
-
- // Always retry once if the $stateNotFound was not prevented
- // (handles either redirect changed or state lazy-definition)
- to = redirect.to;
- toParams = redirect.toParams;
- options = redirect.options;
- toState = findState(to, options.relative);
-
- if (!isDefined(toState)) {
- if (!options.relative) throw new Error("No such state '" + to + "'");
- throw new Error("Could not resolve '" + to + "' from state '" + options.relative + "'");
- }
- }
- if (toState[abstractKey]) throw new Error("Cannot transition to abstract state '" + to + "'");
- if (options.inherit) toParams = inheritParams($stateParams, toParams || {}, $state.$current, toState);
- if (!toState.params.$$validates(toParams)) return TransitionFailed;
-
- toParams = toState.params.$$values(toParams);
- to = toState;
-
- var toPath = to.path;
-
- // Starting from the root of the path, keep all levels that haven't changed
- var keep = 0, state = toPath[keep], locals = root.locals, toLocals = [];
-
- if (!options.reload) {
- while (state && state === fromPath[keep] && state.ownParams.$$equals(toParams, fromParams)) {
- locals = toLocals[keep] = state.locals;
- keep++;
- state = toPath[keep];
- }
- }
-
- // If we're going to the same state and all locals are kept, we've got nothing to do.
- // But clear 'transition', as we still want to cancel any other pending transitions.
- // TODO: We may not want to bump 'transition' if we're called from a location change
- // that we've initiated ourselves, because we might accidentally abort a legitimate
- // transition initiated from code?
- if (shouldTriggerReload(to, from, locals, options)) {
- if (to.self.reloadOnSearch !== false) $urlRouter.update();
- $state.transition = null;
- return $q.when($state.current);
- }
-
- // Filter parameters before we pass them to event handlers etc.
- toParams = filterByKeys(to.params.$$keys(), toParams || {});
-
- // Broadcast start event and cancel the transition if requested
- if (options.notify) {
- /**
- * @ngdoc event
- * @name ui.router.state.$state#$stateChangeStart
- * @eventOf ui.router.state.$state
- * @eventType broadcast on root scope
- * @description
- * Fired when the state transition **begins**. You can use `event.preventDefault()`
- * to prevent the transition from happening and then the transition promise will be
- * rejected with a `'transition prevented'` value.
- *
- * @param {Object} event Event object.
- * @param {State} toState The state being transitioned to.
- * @param {Object} toParams The params supplied to the `toState`.
- * @param {State} fromState The current state, pre-transition.
- * @param {Object} fromParams The params supplied to the `fromState`.
- *
- * @example
- *
- *
- * $rootScope.$on('$stateChangeStart',
- * function(event, toState, toParams, fromState, fromParams){
- * event.preventDefault();
- * // transitionTo() promise will be rejected with
- * // a 'transition prevented' error
- * })
- *
- */
- if ($rootScope.$broadcast('$stateChangeStart', to.self, toParams, from.self, fromParams).defaultPrevented) {
- $urlRouter.update();
- return TransitionPrevented;
- }
- }
-
- // Resolve locals for the remaining states, but don't update any global state just
- // yet -- if anything fails to resolve the current state needs to remain untouched.
- // We also set up an inheritance chain for the locals here. This allows the view directive
- // to quickly look up the correct definition for each view in the current state. Even
- // though we create the locals object itself outside resolveState(), it is initially
- // empty and gets filled asynchronously. We need to keep track of the promise for the
- // (fully resolved) current locals, and pass this down the chain.
- var resolved = $q.when(locals);
-
- for (var l = keep; l < toPath.length; l++, state = toPath[l]) {
- locals = toLocals[l] = inherit(locals);
- resolved = resolveState(state, toParams, state === to, resolved, locals, options);
- }
-
- // Once everything is resolved, we are ready to perform the actual transition
- // and return a promise for the new state. We also keep track of what the
- // current promise is, so that we can detect overlapping transitions and
- // keep only the outcome of the last transition.
- var transition = $state.transition = resolved.then(function () {
- var l, entering, exiting;
-
- if ($state.transition !== transition) return TransitionSuperseded;
-
- // Exit 'from' states not kept
- for (l = fromPath.length - 1; l >= keep; l--) {
- exiting = fromPath[l];
- if (exiting.self.onExit) {
- $injector.invoke(exiting.self.onExit, exiting.self, exiting.locals.globals);
- }
- exiting.locals = null;
- }
-
- // Enter 'to' states not kept
- for (l = keep; l < toPath.length; l++) {
- entering = toPath[l];
- entering.locals = toLocals[l];
- if (entering.self.onEnter) {
- $injector.invoke(entering.self.onEnter, entering.self, entering.locals.globals);
- }
- }
-
- // Run it again, to catch any transitions in callbacks
- if ($state.transition !== transition) return TransitionSuperseded;
-
- // Update globals in $state
- $state.$current = to;
- $state.current = to.self;
- $state.params = toParams;
- copy($state.params, $stateParams);
- $state.transition = null;
-
- if (options.location && to.navigable) {
- $urlRouter.push(to.navigable.url, to.navigable.locals.globals.$stateParams, {
- $$avoidResync: true, replace: options.location === 'replace'
- });
- }
-
- if (options.notify) {
- /**
- * @ngdoc event
- * @name ui.router.state.$state#$stateChangeSuccess
- * @eventOf ui.router.state.$state
- * @eventType broadcast on root scope
- * @description
- * Fired once the state transition is **complete**.
- *
- * @param {Object} event Event object.
- * @param {State} toState The state being transitioned to.
- * @param {Object} toParams The params supplied to the `toState`.
- * @param {State} fromState The current state, pre-transition.
- * @param {Object} fromParams The params supplied to the `fromState`.
- */
- $rootScope.$broadcast('$stateChangeSuccess', to.self, toParams, from.self, fromParams);
- }
- $urlRouter.update(true);
-
- return $state.current;
- }, function (error) {
- if ($state.transition !== transition) return TransitionSuperseded;
-
- $state.transition = null;
- /**
- * @ngdoc event
- * @name ui.router.state.$state#$stateChangeError
- * @eventOf ui.router.state.$state
- * @eventType broadcast on root scope
- * @description
- * Fired when an **error occurs** during transition. It's important to note that if you
- * have any errors in your resolve functions (javascript errors, non-existent services, etc)
- * they will not throw traditionally. You must listen for this $stateChangeError event to
- * catch **ALL** errors.
- *
- * @param {Object} event Event object.
- * @param {State} toState The state being transitioned to.
- * @param {Object} toParams The params supplied to the `toState`.
- * @param {State} fromState The current state, pre-transition.
- * @param {Object} fromParams The params supplied to the `fromState`.
- * @param {Error} error The resolve error object.
- */
- evt = $rootScope.$broadcast('$stateChangeError', to.self, toParams, from.self, fromParams, error);
-
- if (!evt.defaultPrevented) {
- $urlRouter.update();
- }
-
- return $q.reject(error);
- });
-
- return transition;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#is
- * @methodOf ui.router.state.$state
- *
- * @description
- * Similar to {@link ui.router.state.$state#methods_includes $state.includes},
- * but only checks for the full state name. If params is supplied then it will be
- * tested for strict equality against the current active params object, so all params
- * must match with none missing and no extras.
- *
- * @example
- *
- * $state.$current.name = 'contacts.details.item';
- *
- * // absolute name
- * $state.is('contact.details.item'); // returns true
- * $state.is(contactDetailItemStateObject); // returns true
- *
- * // relative name (. and ^), typically from a template
- * // E.g. from the 'contacts.details' template
- *
Item
- *
- *
- * @param {string|object} stateOrName The state name (absolute or relative) or state object you'd like to check.
- * @param {object=} params A param object, e.g. `{sectionId: section.id}`, that you'd like
- * to test against the current active state.
- * @param {object=} options An options object. The options are:
- *
- * - **`relative`** - {string|object} - If `stateOrName` is a relative state name and `options.relative` is set, .is will
- * test relative to `options.relative` state (or name).
- *
- * @returns {boolean} Returns true if it is the state.
- */
- $state.is = function is(stateOrName, params, options) {
- options = extend({ relative: $state.$current }, options || {});
- var state = findState(stateOrName, options.relative);
-
- if (!isDefined(state)) { return undefined; }
- if ($state.$current !== state) { return false; }
- return params ? equalForKeys(state.params.$$values(params), $stateParams) : true;
- };
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#includes
- * @methodOf ui.router.state.$state
- *
- * @description
- * A method to determine if the current active state is equal to or is the child of the
- * state stateName. If any params are passed then they will be tested for a match as well.
- * Not all the parameters need to be passed, just the ones you'd like to test for equality.
- *
- * @example
- * Partial and relative names
- *
- * $state.$current.name = 'contacts.details.item';
- *
- * // Using partial names
- * $state.includes("contacts"); // returns true
- * $state.includes("contacts.details"); // returns true
- * $state.includes("contacts.details.item"); // returns true
- * $state.includes("contacts.list"); // returns false
- * $state.includes("about"); // returns false
- *
- * // Using relative names (. and ^), typically from a template
- * // E.g. from the 'contacts.details' template
- *
- *
- * @param {string} stateOrName A partial name, relative name, or glob pattern
- * to be searched for within the current state name.
- * @param {object=} params A param object, e.g. `{sectionId: section.id}`,
- * that you'd like to test against the current active state.
- * @param {object=} options An options object. The options are:
- *
- * - **`relative`** - {string|object=} - If `stateOrName` is a relative state reference and `options.relative` is set,
- * .includes will test relative to `options.relative` state (or name).
- *
- * @returns {boolean} Returns true if it does include the state
- */
- $state.includes = function includes(stateOrName, params, options) {
- options = extend({ relative: $state.$current }, options || {});
- if (isString(stateOrName) && isGlob(stateOrName)) {
- if (!doesStateMatchGlob(stateOrName)) {
- return false;
- }
- stateOrName = $state.$current.name;
- }
-
- var state = findState(stateOrName, options.relative);
- if (!isDefined(state)) { return undefined; }
- if (!isDefined($state.$current.includes[state.name])) { return false; }
- return params ? equalForKeys(state.params.$$values(params), $stateParams, objectKeys(params)) : true;
- };
-
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#href
- * @methodOf ui.router.state.$state
- *
- * @description
- * A url generation method that returns the compiled url for the given state populated with the given params.
- *
- * @example
- *
- *
- * @param {string|object} stateOrName The state name or state object you'd like to generate a url from.
- * @param {object=} params An object of parameter values to fill the state's required parameters.
- * @param {object=} options Options object. The options are:
- *
- * - **`lossy`** - {boolean=true} - If true, and if there is no url associated with the state provided in the
- * first parameter, then the constructed href url will be built from the first navigable ancestor (aka
- * ancestor with a valid url).
- * - **`inherit`** - {boolean=true}, If `true` will inherit url parameters from current url.
- * - **`relative`** - {object=$state.$current}, When transitioning with relative path (e.g '^'),
- * defines which state to be relative from.
- * - **`absolute`** - {boolean=false}, If true will generate an absolute url, e.g. "http://www.example.com/fullurl".
- *
- * @returns {string} compiled state url
- */
- $state.href = function href(stateOrName, params, options) {
- options = extend({
- lossy: true,
- inherit: true,
- absolute: false,
- relative: $state.$current
- }, options || {});
-
- var state = findState(stateOrName, options.relative);
-
- if (!isDefined(state)) return null;
- if (options.inherit) params = inheritParams($stateParams, params || {}, $state.$current, state);
-
- var nav = (state && options.lossy) ? state.navigable : state;
-
- if (!nav || nav.url === undefined || nav.url === null) {
- return null;
- }
- return $urlRouter.href(nav.url, filterByKeys(state.params.$$keys(), params || {}), {
- absolute: options.absolute
- });
- };
-
- /**
- * @ngdoc function
- * @name ui.router.state.$state#get
- * @methodOf ui.router.state.$state
- *
- * @description
- * Returns the state configuration object for any specific state or all states.
- *
- * @param {string|object=} stateOrName (absolute or relative) If provided, will only get the config for
- * the requested state. If not provided, returns an array of ALL state configs.
- * @param {string|object=} context When stateOrName is a relative state reference, the state will be retrieved relative to context.
- * @returns {Object|Array} State configuration object or array of all objects.
- */
- $state.get = function (stateOrName, context) {
- if (arguments.length === 0) return map(objectKeys(states), function(name) { return states[name].self; });
- var state = findState(stateOrName, context || $state.$current);
- return (state && state.self) ? state.self : null;
- };
-
- function resolveState(state, params, paramsAreFiltered, inherited, dst, options) {
- // Make a restricted $stateParams with only the parameters that apply to this state if
- // necessary. In addition to being available to the controller and onEnter/onExit callbacks,
- // we also need $stateParams to be available for any $injector calls we make during the
- // dependency resolution process.
- var $stateParams = (paramsAreFiltered) ? params : filterByKeys(state.params.$$keys(), params);
- var locals = { $stateParams: $stateParams };
-
- // Resolve 'global' dependencies for the state, i.e. those not specific to a view.
- // We're also including $stateParams in this; that way the parameters are restricted
- // to the set that should be visible to the state, and are independent of when we update
- // the global $state and $stateParams values.
- dst.resolve = $resolve.resolve(state.resolve, locals, dst.resolve, state);
- var promises = [dst.resolve.then(function (globals) {
- dst.globals = globals;
- })];
- if (inherited) promises.push(inherited);
-
- // Resolve template and dependencies for all views.
- forEach(state.views, function (view, name) {
- var injectables = (view.resolve && view.resolve !== state.resolve ? view.resolve : {});
- injectables.$template = [ function () {
- return $view.load(name, { view: view, locals: locals, params: $stateParams, notify: options.notify }) || '';
- }];
-
- promises.push($resolve.resolve(injectables, locals, dst.resolve, state).then(function (result) {
- // References to the controller (only instantiated at link time)
- if (isFunction(view.controllerProvider) || isArray(view.controllerProvider)) {
- var injectLocals = angular.extend({}, injectables, locals);
- result.$$controller = $injector.invoke(view.controllerProvider, null, injectLocals);
- } else {
- result.$$controller = view.controller;
- }
- // Provide access to the state itself for internal use
- result.$$state = state;
- result.$$controllerAs = view.controllerAs;
- dst[name] = result;
- }));
- });
-
- // Wait for all the promises and then return the activation object
- return $q.all(promises).then(function (values) {
- return dst;
- });
- }
-
- return $state;
- }
-
- function shouldTriggerReload(to, from, locals, options) {
- if (to === from && ((locals === from.locals && !options.reload) || (to.self.reloadOnSearch === false))) {
- return true;
- }
- }
-}
-
-angular.module('ui.router.state')
- .value('$stateParams', {})
- .provider('$state', $StateProvider);
-
-
-$ViewProvider.$inject = [];
-function $ViewProvider() {
-
- this.$get = $get;
- /**
- * @ngdoc object
- * @name ui.router.state.$view
- *
- * @requires ui.router.util.$templateFactory
- * @requires $rootScope
- *
- * @description
- *
- */
- $get.$inject = ['$rootScope', '$templateFactory'];
- function $get( $rootScope, $templateFactory) {
- return {
- // $view.load('full.viewName', { template: ..., controller: ..., resolve: ..., async: false, params: ... })
- /**
- * @ngdoc function
- * @name ui.router.state.$view#load
- * @methodOf ui.router.state.$view
- *
- * @description
- *
- * @param {string} name name
- * @param {object} options option object.
- */
- load: function load(name, options) {
- var result, defaults = {
- template: null, controller: null, view: null, locals: null, notify: true, async: true, params: {}
- };
- options = extend(defaults, options);
-
- if (options.view) {
- result = $templateFactory.fromConfig(options.view, options.params, options.locals);
- }
- if (result && options.notify) {
- /**
- * @ngdoc event
- * @name ui.router.state.$state#$viewContentLoading
- * @eventOf ui.router.state.$view
- * @eventType broadcast on root scope
- * @description
- *
- * Fired once the view **begins loading**, *before* the DOM is rendered.
- *
- * @param {Object} event Event object.
- * @param {Object} viewConfig The view config properties (template, controller, etc).
- *
- * @example
- *
- *
- * $scope.$on('$viewContentLoading',
- * function(event, viewConfig){
- * // Access to all the view config properties.
- * // and one special property 'targetView'
- * // viewConfig.targetView
- * });
- *
- */
- $rootScope.$broadcast('$viewContentLoading', options);
- }
- return result;
- }
- };
- }
-}
-
-angular.module('ui.router.state').provider('$view', $ViewProvider);
-
-/**
- * @ngdoc object
- * @name ui.router.state.$uiViewScrollProvider
- *
- * @description
- * Provider that returns the {@link ui.router.state.$uiViewScroll} service function.
- */
-function $ViewScrollProvider() {
-
- var useAnchorScroll = false;
-
- /**
- * @ngdoc function
- * @name ui.router.state.$uiViewScrollProvider#useAnchorScroll
- * @methodOf ui.router.state.$uiViewScrollProvider
- *
- * @description
- * Reverts back to using the core [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll) service for
- * scrolling based on the url anchor.
- */
- this.useAnchorScroll = function () {
- useAnchorScroll = true;
- };
-
- /**
- * @ngdoc object
- * @name ui.router.state.$uiViewScroll
- *
- * @requires $anchorScroll
- * @requires $timeout
- *
- * @description
- * When called with a jqLite element, it scrolls the element into view (after a
- * `$timeout` so the DOM has time to refresh).
- *
- * If you prefer to rely on `$anchorScroll` to scroll the view to the anchor,
- * this can be enabled by calling {@link ui.router.state.$uiViewScrollProvider#methods_useAnchorScroll `$uiViewScrollProvider.useAnchorScroll()`}.
- */
- this.$get = ['$anchorScroll', '$timeout', function ($anchorScroll, $timeout) {
- if (useAnchorScroll) {
- return $anchorScroll;
- }
-
- return function ($element) {
- $timeout(function () {
- $element[0].scrollIntoView();
- }, 0, false);
- };
- }];
-}
-
-angular.module('ui.router.state').provider('$uiViewScroll', $ViewScrollProvider);
-
-/**
- * @ngdoc directive
- * @name ui.router.state.directive:ui-view
- *
- * @requires ui.router.state.$state
- * @requires $compile
- * @requires $controller
- * @requires $injector
- * @requires ui.router.state.$uiViewScroll
- * @requires $document
- *
- * @restrict ECA
- *
- * @description
- * The ui-view directive tells $state where to place your templates.
- *
- * @param {string=} name A view name. The name should be unique amongst the other views in the
- * same state. You can have views of the same name that live in different states.
- *
- * @param {string=} autoscroll It allows you to set the scroll behavior of the browser window
- * when a view is populated. By default, $anchorScroll is overridden by ui-router's custom scroll
- * service, {@link ui.router.state.$uiViewScroll}. This custom service let's you
- * scroll ui-view elements into view when they are populated during a state activation.
- *
- * *Note: To revert back to old [`$anchorScroll`](http://docs.angularjs.org/api/ng.$anchorScroll)
- * functionality, call `$uiViewScrollProvider.useAnchorScroll()`.*
- *
- * @param {string=} onload Expression to evaluate whenever the view updates.
- *
- * @example
- * A view can be unnamed or named.
- *
- *
- *
- *
- *
- *
- *
- *
- * You can only have one unnamed view within any template (or root html). If you are only using a
- * single view and it is unnamed then you can populate it like so:
- *
- *
- * The above is a convenient shortcut equivalent to specifying your view explicitly with the {@link ui.router.state.$stateProvider#views `views`}
- * config property, by name, in this case an empty name:
- *
- *
- * But typically you'll only use the views property if you name your view or have more than one view
- * in the same template. There's not really a compelling reason to name a view if its the only one,
- * but you could if you wanted, like so:
- *
- *
- *
- *
- * When the app state is "app.user" (or any children states), and contains the state parameter "user" with value "bilbobaggins",
- * the resulting HTML will appear as (note the 'active' class):
- *
- *
- *
- * The class name is interpolated **once** during the directives link time (any further changes to the
- * interpolated value are ignored).
- *
- * Multiple classes may be specified in a space-separated format:
- *
- *
- */
-
-/**
- * @ngdoc directive
- * @name ui.router.state.directive:ui-sref-active-eq
- *
- * @requires ui.router.state.$state
- * @requires ui.router.state.$stateParams
- * @requires $interpolate
- *
- * @restrict A
- *
- * @description
- * The same as {@link ui.router.state.directive:ui-sref-active ui-sref-active} but will only activate
- * when the exact target state used in the `ui-sref` is active; no child states.
- *
- */
-$StateRefActiveDirective.$inject = ['$state', '$stateParams', '$interpolate'];
-function $StateRefActiveDirective($state, $stateParams, $interpolate) {
- return {
- restrict: "A",
- controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
- var state, params, activeClass;
-
- // There probably isn't much point in $observing this
- // uiSrefActive and uiSrefActiveEq share the same directive object with some
- // slight difference in logic routing
- activeClass = $interpolate($attrs.uiSrefActiveEq || $attrs.uiSrefActive || '', false)($scope);
-
- // Allow uiSref to communicate with uiSrefActive[Equals]
- this.$$setStateInfo = function (newState, newParams) {
- state = $state.get(newState, stateContext($element));
- params = newParams;
- update();
- };
-
- $scope.$on('$stateChangeSuccess', update);
-
- // Update route state
- function update() {
- if (isMatch()) {
- $element.addClass(activeClass);
- } else {
- $element.removeClass(activeClass);
- }
- }
-
- function isMatch() {
- if (typeof $attrs.uiSrefActiveEq !== 'undefined') {
- return state && $state.is(state.name, params);
- } else {
- return state && $state.includes(state.name, params);
- }
- }
- }]
- };
-}
-
-angular.module('ui.router.state')
- .directive('uiSref', $StateRefDirective)
- .directive('uiSrefActive', $StateRefActiveDirective)
- .directive('uiSrefActiveEq', $StateRefActiveDirective);
-
-/**
- * @ngdoc filter
- * @name ui.router.state.filter:isState
- *
- * @requires ui.router.state.$state
- *
- * @description
- * Translates to {@link ui.router.state.$state#methods_is $state.is("stateName")}.
- */
-$IsStateFilter.$inject = ['$state'];
-function $IsStateFilter($state) {
- var isFilter = function (state) {
- return $state.is(state);
- };
- isFilter.$stateful = true;
- return isFilter;
-}
-
-/**
- * @ngdoc filter
- * @name ui.router.state.filter:includedByState
- *
- * @requires ui.router.state.$state
- *
- * @description
- * Translates to {@link ui.router.state.$state#methods_includes $state.includes('fullOrPartialStateName')}.
- */
-$IncludedByStateFilter.$inject = ['$state'];
-function $IncludedByStateFilter($state) {
- var includesFilter = function (state) {
- return $state.includes(state);
- };
- includesFilter.$stateful = true;
- return includesFilter;
-}
-
-angular.module('ui.router.state')
- .filter('isState', $IsStateFilter)
- .filter('includedByState', $IncludedByStateFilter);
-})(window, window.angular);
\ No newline at end of file
diff --git a/app/www/lib/ionic/js/angular-ui/angular-ui-router.min.js b/app/www/lib/ionic/js/angular-ui/angular-ui-router.min.js
deleted file mode 100644
index be06fb5..0000000
--- a/app/www/lib/ionic/js/angular-ui/angular-ui-router.min.js
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * State-based routing for AngularJS
- * @version v0.2.13
- * @link http://angular-ui.github.com/
- * @license MIT License, http://www.opensource.org/licenses/MIT
- */
-"undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return M(new(M(function(){},{prototype:a})),b)}function e(a){return L(arguments,function(b){b!==a&&L(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path){if(a.path[d]!==b.path[d])break;c.push(a.path[d])}return c}function g(a){if(Object.keys)return Object.keys(a);var c=[];return b.forEach(a,function(a,b){c.push(b)}),c}function h(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function i(a,b,c,d){var e,i=f(c,d),j={},k=[];for(var l in i)if(i[l].params&&(e=g(i[l].params),e.length))for(var m in e)h(k,e[m])>=0||(k.push(e[m]),j[e[m]]=a[e[m]]);return M({},j,b)}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e "));if(s[c]=d,I(a))q.push(c,[function(){return b.get(a)}],j);else{var e=b.annotate(a);L(e,function(a){a!==c&&i.hasOwnProperty(a)&&n(i[a],a)}),q.push(c,a,e)}r.pop(),s[c]=f}}function o(a){return J(a)&&a.then&&a.$$promises}if(!J(i))throw new Error("'invocables' must be an object");var p=g(i||{}),q=[],r=[],s={};return L(i,n),i=r=s=null,function(d,f,g){function h(){--u||(v||e(t,f.$$values),r.$$values=t,r.$$promises=r.$$promises||!0,delete r.$$inheritedValues,n.resolve(t))}function i(a){r.$$failure=a,n.reject(a)}function j(c,e,f){function j(a){l.reject(a),i(a)}function k(){if(!G(r.$$failure))try{l.resolve(b.invoke(e,g,t)),l.promise.then(function(a){t[c]=a,h()},j)}catch(a){j(a)}}var l=a.defer(),m=0;L(f,function(a){s.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,s[a].then(function(b){t[a]=b,--m||k()},j))}),m||k(),s[c]=l.promise}if(o(d)&&g===c&&(g=f,f=d,d=null),d){if(!J(d))throw new Error("'locals' must be an object")}else d=k;if(f){if(!o(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=m;var n=a.defer(),r=n.promise,s=r.$$promises={},t=M({},d),u=1+q.length/3,v=!1;if(G(f.$$failure))return i(f.$$failure),r;f.$$inheritedValues&&e(t,l(f.$$inheritedValues,p)),M(s,f.$$promises),f.$$values?(v=e(t,l(f.$$values,p)),r.$$inheritedValues=l(f.$$values,p),h()):(f.$$inheritedValues&&(r.$$inheritedValues=l(f.$$inheritedValues,p)),f.then(h,i));for(var w=0,x=q.length;x>w;w+=3)d.hasOwnProperty(q[w])?h():j(q[w],q[w+1],q[w+2]);return r}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function p(a,b,c){this.fromConfig=function(a,b,c){return G(a.template)?this.fromString(a.template,b):G(a.templateUrl)?this.fromUrl(a.templateUrl,b):G(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return H(a)?a(b):a},this.fromUrl=function(c,d){return H(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b,headers:{Accept:"text/html"}}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function q(a,b,e){function f(b,c,d,e){if(q.push(b),o[b])return o[b];if(!/^\w+(-+\w+)*(?:\[\])?$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(p[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");return p[b]=new O.Param(b,c,d,e),p[b]}function g(a,b,c){var d=["",""],e=a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&");if(!b)return e;switch(c){case!1:d=["(",")"];break;case!0:d=["?(",")?"];break;default:d=["("+c+"|",")?"]}return e+d[0]+b+d[1]}function h(c,e){var f,g,h,i,j;return f=c[2]||c[3],j=b.params[f],h=a.substring(m,c.index),g=e?c[4]:c[4]||("*"==c[1]?".*":null),i=O.type(g||"string")||d(O.type("string"),{pattern:new RegExp(g)}),{id:f,regexp:g,segment:h,type:i,cfg:j}}b=M({params:{}},J(b)?b:{});var i,j=/([:*])([\w\[\]]+)|\{([\w\[\]]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,k=/([:]?)([\w\[\]-]+)|\{([\w\[\]-]+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,l="^",m=0,n=this.segments=[],o=e?e.params:{},p=this.params=e?e.params.$$new():new O.ParamSet,q=[];this.source=a;for(var r,s,t;(i=j.exec(a))&&(r=h(i,!1),!(r.segment.indexOf("?")>=0));)s=f(r.id,r.type,r.cfg,"path"),l+=g(r.segment,s.type.pattern.source,s.squash),n.push(r.segment),m=j.lastIndex;t=a.substring(m);var u=t.indexOf("?");if(u>=0){var v=this.sourceSearch=t.substring(u);if(t=t.substring(0,u),this.sourcePath=a.substring(0,m+u),v.length>0)for(m=0;i=k.exec(v);)r=h(i,!0),s=f(r.id,r.type,r.cfg,"search"),m=j.lastIndex}else this.sourcePath=a,this.sourceSearch="";l+=g(t)+(b.strict===!1?"/?":"")+"$",n.push(t),this.regexp=new RegExp(l,b.caseInsensitive?"i":c),this.prefix=n[0],this.$$paramNames=q}function r(a){M(this,a)}function s(){function a(a){return null!=a?a.toString().replace(/\//g,"%2F"):a}function e(a){return null!=a?a.toString().replace(/%2F/g,"/"):a}function f(a){return this.pattern.test(a)}function i(){return{strict:t,caseInsensitive:p}}function j(a){return H(a)||K(a)&&H(a[a.length-1])}function k(){for(;x.length;){var a=x.shift();if(a.pattern)throw new Error("You cannot override a type's .pattern at runtime.");b.extend(v[a.name],o.invoke(a.def))}}function l(a){M(this,a||{})}O=this;var o,p=!1,t=!0,u=!1,v={},w=!0,x=[],y={string:{encode:a,decode:e,is:f,pattern:/[^/]*/},"int":{encode:a,decode:function(a){return parseInt(a,10)},is:function(a){return G(a)&&this.decode(a.toString())===a},pattern:/\d+/},bool:{encode:function(a){return a?1:0},decode:function(a){return 0!==parseInt(a,10)},is:function(a){return a===!0||a===!1},pattern:/0|1/},date:{encode:function(a){return this.is(a)?[a.getFullYear(),("0"+(a.getMonth()+1)).slice(-2),("0"+a.getDate()).slice(-2)].join("-"):c},decode:function(a){if(this.is(a))return a;var b=this.capture.exec(a);return b?new Date(b[1],b[2]-1,b[3]):c},is:function(a){return a instanceof Date&&!isNaN(a.valueOf())},equals:function(a,b){return this.is(a)&&this.is(b)&&a.toISOString()===b.toISOString()},pattern:/[0-9]{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[1-2][0-9]|3[0-1])/,capture:/([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])/},json:{encode:b.toJson,decode:b.fromJson,is:b.isObject,equals:b.equals,pattern:/[^/]*/},any:{encode:b.identity,decode:b.identity,is:b.identity,equals:b.equals,pattern:/.*/}};s.$$getDefaultValue=function(a){if(!j(a.value))return a.value;if(!o)throw new Error("Injectable functions cannot be called at configuration time");return o.invoke(a.value)},this.caseInsensitive=function(a){return G(a)&&(p=a),p},this.strictMode=function(a){return G(a)&&(t=a),t},this.defaultSquashPolicy=function(a){if(!G(a))return u;if(a!==!0&&a!==!1&&!I(a))throw new Error("Invalid squash policy: "+a+". Valid policies: false, true, arbitrary-string");return u=a,a},this.compile=function(a,b){return new q(a,M(i(),b))},this.isMatcher=function(a){if(!J(a))return!1;var b=!0;return L(q.prototype,function(c,d){H(c)&&(b=b&&G(a[d])&&H(a[d]))}),b},this.type=function(a,b,c){if(!G(b))return v[a];if(v.hasOwnProperty(a))throw new Error("A type named '"+a+"' has already been defined.");return v[a]=new r(M({name:a},b)),c&&(x.push({name:a,def:c}),w||k()),this},L(y,function(a,b){v[b]=new r(M({name:b},a))}),v=d(v,{}),this.$get=["$injector",function(a){return o=a,w=!1,k(),L(y,function(a,b){v[b]||(v[b]=new r(a))}),this}],this.Param=function(a,b,d,e){function f(a){var b=J(a)?g(a):[],c=-1===h(b,"value")&&-1===h(b,"type")&&-1===h(b,"squash")&&-1===h(b,"array");return c&&(a={value:a}),a.$$fn=j(a.value)?a.value:function(){return a.value},a}function i(b,c,d){if(b.type&&c)throw new Error("Param '"+a+"' has two type configurations.");return c?c:b.type?b.type instanceof r?b.type:new r(b.type):"config"===d?v.any:v.string}function k(){var b={array:"search"===e?"auto":!1},c=a.match(/\[\]$/)?{array:!0}:{};return M(b,c,d).array}function l(a,b){var c=a.squash;if(!b||c===!1)return!1;if(!G(c)||null==c)return u;if(c===!0||I(c))return c;throw new Error("Invalid squash policy: '"+c+"'. Valid policies: false, true, or arbitrary string")}function p(a,b,d,e){var f,g,i=[{from:"",to:d||b?c:""},{from:null,to:d||b?c:""}];return f=K(a.replace)?a.replace:[],I(e)&&f.push({from:e,to:c}),g=n(f,function(a){return a.from}),m(i,function(a){return-1===h(g,a.from)}).concat(f)}function q(){if(!o)throw new Error("Injectable functions cannot be called at configuration time");return o.invoke(d.$$fn)}function s(a){function b(a){return function(b){return b.from===a}}function c(a){var c=n(m(w.replace,b(a)),function(a){return a.to});return c.length?c[0]:a}return a=c(a),G(a)?w.type.decode(a):q()}function t(){return"{Param:"+a+" "+b+" squash: '"+z+"' optional: "+y+"}"}var w=this;d=f(d),b=i(d,b,e);var x=k();b=x?b.$asArray(x,"search"===e):b,"string"!==b.name||x||"path"!==e||d.value!==c||(d.value="");var y=d.value!==c,z=l(d,y),A=p(d,x,y,z);M(this,{id:a,type:b,location:e,array:x,squash:z,replace:A,isOptional:y,value:s,dynamic:c,config:d,toString:t})},l.prototype={$$new:function(){return d(this,M(new l,{$$parent:this}))},$$keys:function(){for(var a=[],b=[],c=this,d=g(l.prototype);c;)b.push(c),c=c.$$parent;return b.reverse(),L(b,function(b){L(g(b),function(b){-1===h(a,b)&&-1===h(d,b)&&a.push(b)})}),a},$$values:function(a){var b={},c=this;return L(c.$$keys(),function(d){b[d]=c[d].value(a&&a[d])}),b},$$equals:function(a,b){var c=!0,d=this;return L(d.$$keys(),function(e){var f=a&&a[e],g=b&&b[e];d[e].type.equals(f,g)||(c=!1)}),c},$$validates:function(a){var b,c,d,e=!0,f=this;return L(this.$$keys(),function(g){d=f[g],c=a[g],b=!c&&d.isOptional,e=e&&(b||!!d.type.is(c))}),e},$$parent:c},this.ParamSet=l}function t(a,d){function e(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function f(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function g(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return G(d)?d:!0}function h(d,e,f,g){function h(a,b,c){return"/"===p?a:b?p.slice(0,-1)+a:c?p.slice(1)+a:a}function m(a){function b(a){var b=a(f,d);return b?(I(b)&&d.replace().url(b),!0):!1}if(!a||!a.defaultPrevented){var e=o&&d.url()===o;if(o=c,e)return!0;var g,h=j.length;for(g=0;h>g;g++)if(b(j[g]))return;k&&b(k)}}function n(){return i=i||e.$on("$locationChangeSuccess",m)}var o,p=g.baseHref(),q=d.url();return l||n(),{sync:function(){m()},listen:function(){return n()},update:function(a){return a?void(q=d.url()):void(d.url()!==q&&(d.url(q),d.replace()))},push:function(a,b,e){d.url(a.format(b||{})),o=e&&e.$$avoidResync?d.url():c,e&&e.replace&&d.replace()},href:function(c,e,f){if(!c.validates(e))return null;var g=a.html5Mode();b.isObject(g)&&(g=g.enabled);var i=c.format(e);if(f=f||{},g||null===i||(i="#"+a.hashPrefix()+i),i=h(i,g,f.absolute),!f.absolute||!i)return i;var j=!g&&i?"/":"",k=d.port();return k=80===k||443===k?"":":"+k,[d.protocol(),"://",d.host(),k,j,i].join("")}}}var i,j=[],k=null,l=!1;this.rule=function(a){if(!H(a))throw new Error("'rule' must be a function");return j.push(a),this},this.otherwise=function(a){if(I(a)){var b=a;a=function(){return b}}else if(!H(a))throw new Error("'rule' must be a function");return k=a,this},this.when=function(a,b){var c,h=I(b);if(I(a)&&(a=d.compile(a)),!h&&!H(b)&&!K(b))throw new Error("invalid 'handler' in when()");var i={matcher:function(a,b){return h&&(c=d.compile(b),b=["$match",function(a){return c.format(a)}]),M(function(c,d){return g(c,b,a.exec(d.path(),d.search()))},{prefix:I(a.prefix)?a.prefix:""})},regex:function(a,b){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(c=b,b=["$match",function(a){return f(c,a)}]),M(function(c,d){return g(c,b,a.exec(d.path()))},{prefix:e(a)})}},j={matcher:d.isMatcher(a),regex:a instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](a,b));throw new Error("invalid 'what' in when()")},this.deferIntercept=function(a){a===c&&(a=!0),l=a},this.$get=h,h.$inject=["$location","$rootScope","$injector","$browser"]}function u(a,e){function f(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){if(!a)return c;var d=I(a),e=d?a:a.name,g=f(e);if(g){if(!b)throw new Error("No reference point given for path '"+e+"'");b=l(b);for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var m=y[e];return!m||!d&&(d||m!==a&&m.self!==a)?c:m}function m(a,b){z[a]||(z[a]=[]),z[a].push(b)}function o(a){for(var b=z[a]||[];b.length;)p(b.shift())}function p(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!I(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(y.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):I(b.parent)?b.parent:J(b.parent)&&I(b.parent.name)?b.parent.name:"";if(e&&!y[e])return m(e,b.self);for(var f in B)H(B[f])&&(b[f]=B[f](b,B.$delegates[f]));return y[c]=b,!b[A]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){x.$current.navigable==b&&j(a,c)||x.transitionTo(b,a,{inherit:!0,location:!1})}]),o(c),b}function q(a){return a.indexOf("*")>-1}function r(a){var b=a.split("."),c=x.$current.name.split(".");if("**"===b[0]&&(c=c.slice(h(c,b[1])),c.unshift("**")),"**"===b[b.length-1]&&(c.splice(h(c,b[b.length-2])+1,Number.MAX_VALUE),c.push("**")),b.length!=c.length)return!1;for(var d=0,e=b.length;e>d;d++)"*"===b[d]&&(c[d]="*");return c.join("")===b.join("")}function s(a,b){return I(a)&&!G(b)?B[a]:H(b)&&I(a)?(B[a]&&!B.$delegates[a]&&(B.$delegates[a]=B[a]),B[a]=b,this):this}function t(a,b){return J(a)?b=a:b.name=a,p(b),this}function u(a,e,f,h,m,o,p){function s(b,c,d,f){var g=a.$broadcast("$stateNotFound",b,c,d);if(g.defaultPrevented)return p.update(),B;if(!g.retry)return null;if(f.$retry)return p.update(),C;var h=x.transition=e.when(g.retry);return h.then(function(){return h!==x.transition?u:(b.options.$retry=!0,x.transitionTo(b.to,b.toParams,b.options))},function(){return B}),p.update(),h}function t(a,c,d,g,i,j){var l=d?c:k(a.params.$$keys(),c),n={$stateParams:l};i.resolve=m.resolve(a.resolve,n,i.resolve,a);var o=[i.resolve.then(function(a){i.globals=a})];return g&&o.push(g),L(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return f.load(d,{view:c,locals:n,params:l,notify:j.notify})||""}],o.push(m.resolve(e,n,i.resolve,a).then(function(f){if(H(c.controllerProvider)||K(c.controllerProvider)){var g=b.extend({},e,n);f.$$controller=h.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,f.$$controllerAs=c.controllerAs,i[d]=f}))}),e.all(o).then(function(){return i})}var u=e.reject(new Error("transition superseded")),z=e.reject(new Error("transition prevented")),B=e.reject(new Error("transition aborted")),C=e.reject(new Error("transition failed"));return w.locals={resolve:null,globals:{$stateParams:{}}},x={params:{},current:w.self,$current:w,transition:null},x.reload=function(){return x.transitionTo(x.current,o,{reload:!0,inherit:!1,notify:!0})},x.go=function(a,b,c){return x.transitionTo(a,b,M({inherit:!0,relative:x.$current},c))},x.transitionTo=function(b,c,f){c=c||{},f=M({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,j=x.$current,m=x.params,n=j.path,q=l(b,f.relative);if(!G(q)){var r={to:b,toParams:c,options:f},y=s(r,j.self,m,f);if(y)return y;if(b=r.to,c=r.toParams,f=r.options,q=l(b,f.relative),!G(q)){if(!f.relative)throw new Error("No such state '"+b+"'");throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'")}}if(q[A])throw new Error("Cannot transition to abstract state '"+b+"'");if(f.inherit&&(c=i(o,c||{},x.$current,q)),!q.params.$$validates(c))return C;c=q.params.$$values(c),b=q;var B=b.path,D=0,E=B[D],F=w.locals,H=[];if(!f.reload)for(;E&&E===n[D]&&E.ownParams.$$equals(c,m);)F=H[D]=E.locals,D++,E=B[D];if(v(b,j,F,f))return b.self.reloadOnSearch!==!1&&p.update(),x.transition=null,e.when(x.current);if(c=k(b.params.$$keys(),c||{}),f.notify&&a.$broadcast("$stateChangeStart",b.self,c,j.self,m).defaultPrevented)return p.update(),z;for(var I=e.when(F),J=D;J=D;d--)g=n[d],g.self.onExit&&h.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=D;d=0?e:e+"@"+(f?f.state.name:"")}function A(a,b){var c,d=a.match(/^\s*({[^}]*})\s*$/);if(d&&(a=b+"("+d[1]+")"),c=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/),!c||4!==c.length)throw new Error("Invalid state ref '"+a+"'");return{state:c[1],paramExpr:c[3]||null}}function B(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function C(a,c){var d=["location","inherit","reload"];return{restrict:"A",require:["?^uiSrefActive","?^uiSrefActiveEq"],link:function(e,f,g,h){var i=A(g.uiSref,a.current.name),j=null,k=B(f)||a.$current,l=null,m="A"===f.prop("tagName"),n="FORM"===f[0].nodeName,o=n?"action":"href",p=!0,q={relative:k,inherit:!0},r=e.$eval(g.uiSrefOpts)||{};b.forEach(d,function(a){a in r&&(q[a]=r[a])});var s=function(c){if(c&&(j=b.copy(c)),p){l=a.href(i.state,j,q);var d=h[1]||h[0];return d&&d.$$setStateInfo(i.state,j),null===l?(p=!1,!1):void g.$set(o,l)}};i.paramExpr&&(e.$watch(i.paramExpr,function(a){a!==j&&s(a)},!0),j=b.copy(e.$eval(i.paramExpr))),s(),n||f.bind("click",function(b){var d=b.which||b.button;if(!(d>1||b.ctrlKey||b.metaKey||b.shiftKey||f.attr("target"))){var e=c(function(){a.go(i.state,j,q)});b.preventDefault();var g=m&&!l?1:0;b.preventDefault=function(){g--<=0&&c.cancel(e)}}})}}}function D(a,b,c){return{restrict:"A",controller:["$scope","$element","$attrs",function(b,d,e){function f(){g()?d.addClass(j):d.removeClass(j)}function g(){return"undefined"!=typeof e.uiSrefActiveEq?h&&a.is(h.name,i):h&&a.includes(h.name,i)}var h,i,j;j=c(e.uiSrefActiveEq||e.uiSrefActive||"",!1)(b),this.$$setStateInfo=function(b,c){h=a.get(b,B(d)),i=c,f()},b.$on("$stateChangeSuccess",f)}]}}function E(a){var b=function(b){return a.is(b)};return b.$stateful=!0,b}function F(a){var b=function(b){return a.includes(b)};return b.$stateful=!0,b}var G=b.isDefined,H=b.isFunction,I=b.isString,J=b.isObject,K=b.isArray,L=b.forEach,M=b.extend,N=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),o.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",o),p.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",p);var O;q.prototype.concat=function(a,b){var c={caseInsensitive:O.caseInsensitive(),strict:O.strictMode(),squash:O.defaultSquashPolicy()};return new q(this.sourcePath+a+this.sourceSearch,M(c,b),this)},q.prototype.toString=function(){return this.source},q.prototype.exec=function(a,b){function c(a){function b(a){return a.split("").reverse().join("")}function c(a){return a.replace(/\\-/,"-")}var d=b(a).split(/-(?!\\)/),e=n(d,b);return n(e,c).reverse()}var d=this.regexp.exec(a);if(!d)return null;b=b||{};var e,f,g,h=this.parameters(),i=h.length,j=this.segments.length-1,k={};if(j!==d.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(e=0;j>e;e++){g=h[e];var l=this.params[g],m=d[e+1];for(f=0;fe;e++)g=h[e],k[g]=this.params[g].value(b[g]);return k},q.prototype.parameters=function(a){return G(a)?this.params[a]||null:this.$$paramNames},q.prototype.validates=function(a){return this.params.$$validates(a)},q.prototype.format=function(a){function b(a){return encodeURIComponent(a).replace(/-/g,function(a){return"%5C%"+a.charCodeAt(0).toString(16).toUpperCase()})}a=a||{};var c=this.segments,d=this.parameters(),e=this.params;if(!this.validates(a))return null;var f,g=!1,h=c.length-1,i=d.length,j=c[0];for(f=0;i>f;f++){var k=h>f,l=d[f],m=e[l],o=m.value(a[l]),p=m.isOptional&&m.type.equals(m.value(),o),q=p?m.squash:!1,r=m.type.encode(o);if(k){var s=c[f+1];if(q===!1)null!=r&&(j+=K(r)?n(r,b).join("-"):encodeURIComponent(r)),j+=s;else if(q===!0){var t=j.match(/\/$/)?/\/?(.*)/:/(.*)/;j+=s.match(t)[1]}else I(q)&&(j+=q+s)}else{if(null==r||p&&q!==!1)continue;K(r)||(r=[r]),r=n(r,encodeURIComponent).join("&"+l+"="),j+=(g?"&":"?")+(l+"="+r),g=!0}}return j},r.prototype.is=function(){return!0},r.prototype.encode=function(a){return a},r.prototype.decode=function(a){return a},r.prototype.equals=function(a,b){return a==b},r.prototype.$subPattern=function(){var a=this.pattern.toString();return a.substr(1,a.length-2)},r.prototype.pattern=/.*/,r.prototype.toString=function(){return"{Type:"+this.name+"}"},r.prototype.$asArray=function(a,b){function d(a,b){function d(a,b){return function(){return a[b].apply(a,arguments)}}function e(a){return K(a)?a:G(a)?[a]:[]}function f(a){switch(a.length){case 0:return c;case 1:return"auto"===b?a[0]:a;default:return a}}function g(a){return!a}function h(a,b){return function(c){c=e(c);var d=n(c,a);return b===!0?0===m(d,g).length:f(d)}}function i(a){return function(b,c){var d=e(b),f=e(c);if(d.length!==f.length)return!1;for(var g=0;g 0) {
- className += (i > 0) ? ' ' : '';
- className += isPrefix ? fix + klass
- : klass + fix;
- }
- });
- return className;
-}
-
-function removeFromArray(arr, val) {
- var index = arr.indexOf(val);
- if (val >= 0) {
- arr.splice(index, 1);
- }
-}
-
-function stripCommentsFromElement(element) {
- if (element instanceof jqLite) {
- switch (element.length) {
- case 0:
- return [];
- break;
-
- case 1:
- // there is no point of stripping anything if the element
- // is the only element within the jqLite wrapper.
- // (it's important that we retain the element instance.)
- if (element[0].nodeType === ELEMENT_NODE) {
- return element;
- }
- break;
-
- default:
- return jqLite(extractElementNode(element));
- break;
- }
- }
-
- if (element.nodeType === ELEMENT_NODE) {
- return jqLite(element);
- }
-}
-
-function extractElementNode(element) {
- if (!element[0]) return element;
- for (var i = 0; i < element.length; i++) {
- var elm = element[i];
- if (elm.nodeType == ELEMENT_NODE) {
- return elm;
- }
- }
-}
-
-function $$addClass($$jqLite, element, className) {
- forEach(element, function(elm) {
- $$jqLite.addClass(elm, className);
- });
-}
-
-function $$removeClass($$jqLite, element, className) {
- forEach(element, function(elm) {
- $$jqLite.removeClass(elm, className);
- });
-}
-
-function applyAnimationClassesFactory($$jqLite) {
- return function(element, options) {
- if (options.addClass) {
- $$addClass($$jqLite, element, options.addClass);
- options.addClass = null;
- }
- if (options.removeClass) {
- $$removeClass($$jqLite, element, options.removeClass);
- options.removeClass = null;
- }
- }
-}
-
-function prepareAnimationOptions(options) {
- options = options || {};
- if (!options.$$prepared) {
- var domOperation = options.domOperation || noop;
- options.domOperation = function() {
- options.$$domOperationFired = true;
- domOperation();
- domOperation = noop;
- };
- options.$$prepared = true;
- }
- return options;
-}
-
-function applyAnimationStyles(element, options) {
- applyAnimationFromStyles(element, options);
- applyAnimationToStyles(element, options);
-}
-
-function applyAnimationFromStyles(element, options) {
- if (options.from) {
- element.css(options.from);
- options.from = null;
- }
-}
-
-function applyAnimationToStyles(element, options) {
- if (options.to) {
- element.css(options.to);
- options.to = null;
- }
-}
-
-function mergeAnimationDetails(element, oldAnimation, newAnimation) {
- var target = oldAnimation.options || {};
- var newOptions = newAnimation.options || {};
-
- var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || '');
- var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || '');
- var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove);
-
- if (newOptions.preparationClasses) {
- target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses);
- delete newOptions.preparationClasses;
- }
-
- // noop is basically when there is no callback; otherwise something has been set
- var realDomOperation = target.domOperation !== noop ? target.domOperation : null;
-
- extend(target, newOptions);
-
- // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this.
- if (realDomOperation) {
- target.domOperation = realDomOperation;
- }
-
- if (classes.addClass) {
- target.addClass = classes.addClass;
- } else {
- target.addClass = null;
- }
-
- if (classes.removeClass) {
- target.removeClass = classes.removeClass;
- } else {
- target.removeClass = null;
- }
-
- oldAnimation.addClass = target.addClass;
- oldAnimation.removeClass = target.removeClass;
-
- return target;
-}
-
-function resolveElementClasses(existing, toAdd, toRemove) {
- var ADD_CLASS = 1;
- var REMOVE_CLASS = -1;
-
- var flags = {};
- existing = splitClassesToLookup(existing);
-
- toAdd = splitClassesToLookup(toAdd);
- forEach(toAdd, function(value, key) {
- flags[key] = ADD_CLASS;
- });
-
- toRemove = splitClassesToLookup(toRemove);
- forEach(toRemove, function(value, key) {
- flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS;
- });
-
- var classes = {
- addClass: '',
- removeClass: ''
- };
-
- forEach(flags, function(val, klass) {
- var prop, allow;
- if (val === ADD_CLASS) {
- prop = 'addClass';
- allow = !existing[klass];
- } else if (val === REMOVE_CLASS) {
- prop = 'removeClass';
- allow = existing[klass];
- }
- if (allow) {
- if (classes[prop].length) {
- classes[prop] += ' ';
- }
- classes[prop] += klass;
- }
- });
-
- function splitClassesToLookup(classes) {
- if (isString(classes)) {
- classes = classes.split(' ');
- }
-
- var obj = {};
- forEach(classes, function(klass) {
- // sometimes the split leaves empty string values
- // incase extra spaces were applied to the options
- if (klass.length) {
- obj[klass] = true;
- }
- });
- return obj;
- }
-
- return classes;
-}
-
-function getDomNode(element) {
- return (element instanceof angular.element) ? element[0] : element;
-}
-
-function applyGeneratedPreparationClasses(element, event, options) {
- var classes = '';
- if (event) {
- classes = pendClasses(event, EVENT_CLASS_PREFIX, true);
- }
- if (options.addClass) {
- classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX));
- }
- if (options.removeClass) {
- classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX));
- }
- if (classes.length) {
- options.preparationClasses = classes;
- element.addClass(classes);
- }
-}
-
-function clearGeneratedClasses(element, options) {
- if (options.preparationClasses) {
- element.removeClass(options.preparationClasses);
- options.preparationClasses = null;
- }
- if (options.activeClasses) {
- element.removeClass(options.activeClasses);
- options.activeClasses = null;
- }
-}
-
-function blockTransitions(node, duration) {
- // we use a negative delay value since it performs blocking
- // yet it doesn't kill any existing transitions running on the
- // same element which makes this safe for class-based animations
- var value = duration ? '-' + duration + 's' : '';
- applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]);
- return [TRANSITION_DELAY_PROP, value];
-}
-
-function blockKeyframeAnimations(node, applyBlock) {
- var value = applyBlock ? 'paused' : '';
- var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY;
- applyInlineStyle(node, [key, value]);
- return [key, value];
-}
-
-function applyInlineStyle(node, styleTuple) {
- var prop = styleTuple[0];
- var value = styleTuple[1];
- node.style[prop] = value;
-}
-
-function concatWithSpace(a,b) {
- if (!a) return b;
- if (!b) return a;
- return a + ' ' + b;
-}
-
-var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) {
- var queue, cancelFn;
-
- function scheduler(tasks) {
- // we make a copy since RAFScheduler mutates the state
- // of the passed in array variable and this would be difficult
- // to track down on the outside code
- queue = queue.concat(tasks);
- nextTick();
- }
-
- queue = scheduler.queue = [];
-
- /* waitUntilQuiet does two things:
- * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through
- * 2. It will delay the next wave of tasks from running until the quiet `fn` has run.
- *
- * The motivation here is that animation code can request more time from the scheduler
- * before the next wave runs. This allows for certain DOM properties such as classes to
- * be resolved in time for the next animation to run.
- */
- scheduler.waitUntilQuiet = function(fn) {
- if (cancelFn) cancelFn();
-
- cancelFn = $$rAF(function() {
- cancelFn = null;
- fn();
- nextTick();
- });
- };
-
- return scheduler;
-
- function nextTick() {
- if (!queue.length) return;
-
- var items = queue.shift();
- for (var i = 0; i < items.length; i++) {
- items[i]();
- }
-
- if (!cancelFn) {
- $$rAF(function() {
- if (!cancelFn) nextTick();
- });
- }
- }
-}];
-
-/**
- * @ngdoc directive
- * @name ngAnimateChildren
- * @restrict AE
- * @element ANY
- *
- * @description
- *
- * ngAnimateChildren allows you to specify that children of this element should animate even if any
- * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move`
- * (structural) animation, child elements that also have an active structural animation are not animated.
- *
- * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation).
- *
- *
- * @param {string} ngAnimateChildren If the value is empty, `true` or `on`,
- * then child animations are allowed. If the value is `false`, child animations are not allowed.
- *
- * @example
- *
-
-
-
-
-
-
-
- List of items:
-
Item {{item}}
-
-
-
-
-
-
- .container.ng-enter,
- .container.ng-leave {
- transition: all ease 1.5s;
- }
-
- .container.ng-enter,
- .container.ng-leave-active {
- opacity: 0;
- }
-
- .container.ng-leave,
- .container.ng-enter-active {
- opacity: 1;
- }
-
- .item {
- background: firebrick;
- color: #FFF;
- margin-bottom: 10px;
- }
-
- .item.ng-enter,
- .item.ng-leave {
- transition: transform 1.5s ease;
- }
-
- .item.ng-enter {
- transform: translateX(50px);
- }
-
- .item.ng-enter-active {
- transform: translateX(0);
- }
-
-
- angular.module('ngAnimateChildren', ['ngAnimate'])
- .controller('mainController', function() {
- this.animateChildren = false;
- this.enterElement = false;
- });
-
-
- */
-var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) {
- return {
- link: function(scope, element, attrs) {
- var val = attrs.ngAnimateChildren;
- if (angular.isString(val) && val.length === 0) { //empty attribute
- element.data(NG_ANIMATE_CHILDREN_DATA, true);
- } else {
- // Interpolate and set the value, so that it is available to
- // animations that run right after compilation
- setData($interpolate(val)(scope));
- attrs.$observe('ngAnimateChildren', setData);
- }
-
- function setData(value) {
- value = value === 'on' || value === 'true';
- element.data(NG_ANIMATE_CHILDREN_DATA, value);
- }
- }
- };
-}];
-
-var ANIMATE_TIMER_KEY = '$$animateCss';
-
-/**
- * @ngdoc service
- * @name $animateCss
- * @kind object
- *
- * @description
- * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes
- * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT
- * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or
- * directives to create more complex animations that can be purely driven using CSS code.
- *
- * Note that only browsers that support CSS transitions and/or keyframe animations are capable of
- * rendering animations triggered via `$animateCss` (bad news for IE9 and lower).
- *
- * ## Usage
- * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that
- * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however,
- * any automatic control over cancelling animations and/or preventing animations from being run on
- * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to
- * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger
- * the CSS animation.
- *
- * The example below shows how we can create a folding animation on an element using `ng-if`:
- *
- * ```html
- *
- *
- * This element will go BOOM
- *
- *
- * ```
- *
- * Now we create the **JavaScript animation** that will trigger the CSS transition:
- *
- * ```js
- * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
- * return {
- * enter: function(element, doneFn) {
- * var height = element[0].offsetHeight;
- * return $animateCss(element, {
- * from: { height:'0px' },
- * to: { height:height + 'px' },
- * duration: 1 // one second
- * });
- * }
- * }
- * }]);
- * ```
- *
- * ## More Advanced Uses
- *
- * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks
- * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code.
- *
- * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation,
- * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with
- * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order
- * to provide a working animation that will run in CSS.
- *
- * The example below showcases a more advanced version of the `.fold-animation` from the example above:
- *
- * ```js
- * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) {
- * return {
- * enter: function(element, doneFn) {
- * var height = element[0].offsetHeight;
- * return $animateCss(element, {
- * addClass: 'red large-text pulse-twice',
- * easing: 'ease-out',
- * from: { height:'0px' },
- * to: { height:height + 'px' },
- * duration: 1 // one second
- * });
- * }
- * }
- * }]);
- * ```
- *
- * Since we're adding/removing CSS classes then the CSS transition will also pick those up:
- *
- * ```css
- * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code,
- * the CSS classes below will be transitioned despite them being defined as regular CSS classes */
- * .red { background:red; }
- * .large-text { font-size:20px; }
- *
- * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */
- * .pulse-twice {
- * animation: 0.5s pulse linear 2;
- * -webkit-animation: 0.5s pulse linear 2;
- * }
- *
- * @keyframes pulse {
- * from { transform: scale(0.5); }
- * to { transform: scale(1.5); }
- * }
- *
- * @-webkit-keyframes pulse {
- * from { -webkit-transform: scale(0.5); }
- * to { -webkit-transform: scale(1.5); }
- * }
- * ```
- *
- * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen.
- *
- * ## How the Options are handled
- *
- * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation
- * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline
- * styles using the `from` and `to` properties.
- *
- * ```js
- * var animator = $animateCss(element, {
- * from: { background:'red' },
- * to: { background:'blue' }
- * });
- * animator.start();
- * ```
- *
- * ```css
- * .rotating-animation {
- * animation:0.5s rotate linear;
- * -webkit-animation:0.5s rotate linear;
- * }
- *
- * @keyframes rotate {
- * from { transform: rotate(0deg); }
- * to { transform: rotate(360deg); }
- * }
- *
- * @-webkit-keyframes rotate {
- * from { -webkit-transform: rotate(0deg); }
- * to { -webkit-transform: rotate(360deg); }
- * }
- * ```
- *
- * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is
- * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition
- * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition
- * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied
- * and spread across the transition and keyframe animation.
- *
- * ## What is returned
- *
- * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually
- * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are
- * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties:
- *
- * ```js
- * var animator = $animateCss(element, { ... });
- * ```
- *
- * Now what do the contents of our `animator` variable look like:
- *
- * ```js
- * {
- * // starts the animation
- * start: Function,
- *
- * // ends (aborts) the animation
- * end: Function
- * }
- * ```
- *
- * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends.
- * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been
- * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties
- * and that changing them will not reconfigure the parameters of the animation.
- *
- * ### runner.done() vs runner.then()
- * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the
- * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**.
- * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()`
- * unless you really need a digest to kick off afterwards.
- *
- * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss
- * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code).
- * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works.
- *
- * @param {DOMElement} element the element that will be animated
- * @param {object} options the animation-related options that will be applied during the animation
- *
- * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied
- * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.)
- * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and
- * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted.
- * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both).
- * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`).
- * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`).
- * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation.
- * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition.
- * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation.
- * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation.
- * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0`
- * is provided then the animation will be skipped entirely.
- * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is
- * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value
- * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same
- * CSS delay value.
- * * `stagger` - A numeric time value representing the delay between successively animated elements
- * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.})
- * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a
- * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`)
- * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.)
- * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once
- * the animation is closed. This is useful for when the styles are used purely for the sake of
- * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation).
- * By default this value is set to `false`.
- *
- * @return {object} an object with start and end methods and details about the animation.
- *
- * * `start` - The method to start the animation. This will return a `Promise` when called.
- * * `end` - This method will cancel the animation and remove all applied CSS classes and styles.
- */
-var ONE_SECOND = 1000;
-var BASE_TEN = 10;
-
-var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3;
-var CLOSING_TIME_BUFFER = 1.5;
-
-var DETECT_CSS_PROPERTIES = {
- transitionDuration: TRANSITION_DURATION_PROP,
- transitionDelay: TRANSITION_DELAY_PROP,
- transitionProperty: TRANSITION_PROP + PROPERTY_KEY,
- animationDuration: ANIMATION_DURATION_PROP,
- animationDelay: ANIMATION_DELAY_PROP,
- animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY
-};
-
-var DETECT_STAGGER_CSS_PROPERTIES = {
- transitionDuration: TRANSITION_DURATION_PROP,
- transitionDelay: TRANSITION_DELAY_PROP,
- animationDuration: ANIMATION_DURATION_PROP,
- animationDelay: ANIMATION_DELAY_PROP
-};
-
-function getCssKeyframeDurationStyle(duration) {
- return [ANIMATION_DURATION_PROP, duration + 's'];
-}
-
-function getCssDelayStyle(delay, isKeyframeAnimation) {
- var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP;
- return [prop, delay + 's'];
-}
-
-function computeCssStyles($window, element, properties) {
- var styles = Object.create(null);
- var detectedStyles = $window.getComputedStyle(element) || {};
- forEach(properties, function(formalStyleName, actualStyleName) {
- var val = detectedStyles[formalStyleName];
- if (val) {
- var c = val.charAt(0);
-
- // only numerical-based values have a negative sign or digit as the first value
- if (c === '-' || c === '+' || c >= 0) {
- val = parseMaxTime(val);
- }
-
- // by setting this to null in the event that the delay is not set or is set directly as 0
- // then we can still allow for negative values to be used later on and not mistake this
- // value for being greater than any other negative value.
- if (val === 0) {
- val = null;
- }
- styles[actualStyleName] = val;
- }
- });
-
- return styles;
-}
-
-function parseMaxTime(str) {
- var maxValue = 0;
- var values = str.split(/\s*,\s*/);
- forEach(values, function(value) {
- // it's always safe to consider only second values and omit `ms` values since
- // getComputedStyle will always handle the conversion for us
- if (value.charAt(value.length - 1) == 's') {
- value = value.substring(0, value.length - 1);
- }
- value = parseFloat(value) || 0;
- maxValue = maxValue ? Math.max(value, maxValue) : value;
- });
- return maxValue;
-}
-
-function truthyTimingValue(val) {
- return val === 0 || val != null;
-}
-
-function getCssTransitionDurationStyle(duration, applyOnlyDuration) {
- var style = TRANSITION_PROP;
- var value = duration + 's';
- if (applyOnlyDuration) {
- style += DURATION_KEY;
- } else {
- value += ' linear all';
- }
- return [style, value];
-}
-
-function createLocalCacheLookup() {
- var cache = Object.create(null);
- return {
- flush: function() {
- cache = Object.create(null);
- },
-
- count: function(key) {
- var entry = cache[key];
- return entry ? entry.total : 0;
- },
-
- get: function(key) {
- var entry = cache[key];
- return entry && entry.value;
- },
-
- put: function(key, value) {
- if (!cache[key]) {
- cache[key] = { total: 1, value: value };
- } else {
- cache[key].total++;
- }
- }
- };
-}
-
-// we do not reassign an already present style value since
-// if we detect the style property value again we may be
-// detecting styles that were added via the `from` styles.
-// We make use of `isDefined` here since an empty string
-// or null value (which is what getPropertyValue will return
-// for a non-existing style) will still be marked as a valid
-// value for the style (a falsy value implies that the style
-// is to be removed at the end of the animation). If we had a simple
-// "OR" statement then it would not be enough to catch that.
-function registerRestorableStyles(backup, node, properties) {
- forEach(properties, function(prop) {
- backup[prop] = isDefined(backup[prop])
- ? backup[prop]
- : node.style.getPropertyValue(prop);
- });
-}
-
-var $AnimateCssProvider = ['$animateProvider', function($animateProvider) {
- var gcsLookup = createLocalCacheLookup();
- var gcsStaggerLookup = createLocalCacheLookup();
-
- this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout',
- '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue',
- function($window, $$jqLite, $$AnimateRunner, $timeout,
- $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) {
-
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
-
- var parentCounter = 0;
- function gcsHashFn(node, extraClasses) {
- var KEY = "$$ngAnimateParentKey";
- var parentNode = node.parentNode;
- var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter);
- return parentID + '-' + node.getAttribute('class') + '-' + extraClasses;
- }
-
- function computeCachedCssStyles(node, className, cacheKey, properties) {
- var timings = gcsLookup.get(cacheKey);
-
- if (!timings) {
- timings = computeCssStyles($window, node, properties);
- if (timings.animationIterationCount === 'infinite') {
- timings.animationIterationCount = 1;
- }
- }
-
- // we keep putting this in multiple times even though the value and the cacheKey are the same
- // because we're keeping an internal tally of how many duplicate animations are detected.
- gcsLookup.put(cacheKey, timings);
- return timings;
- }
-
- function computeCachedCssStaggerStyles(node, className, cacheKey, properties) {
- var stagger;
-
- // if we have one or more existing matches of matching elements
- // containing the same parent + CSS styles (which is how cacheKey works)
- // then staggering is possible
- if (gcsLookup.count(cacheKey) > 0) {
- stagger = gcsStaggerLookup.get(cacheKey);
-
- if (!stagger) {
- var staggerClassName = pendClasses(className, '-stagger');
-
- $$jqLite.addClass(node, staggerClassName);
-
- stagger = computeCssStyles($window, node, properties);
-
- // force the conversion of a null value to zero incase not set
- stagger.animationDuration = Math.max(stagger.animationDuration, 0);
- stagger.transitionDuration = Math.max(stagger.transitionDuration, 0);
-
- $$jqLite.removeClass(node, staggerClassName);
-
- gcsStaggerLookup.put(cacheKey, stagger);
- }
- }
-
- return stagger || {};
- }
-
- var cancelLastRAFRequest;
- var rafWaitQueue = [];
- function waitUntilQuiet(callback) {
- rafWaitQueue.push(callback);
- $$rAFScheduler.waitUntilQuiet(function() {
- gcsLookup.flush();
- gcsStaggerLookup.flush();
-
- // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable.
- // PLEASE EXAMINE THE `$$forceReflow` service to understand why.
- var pageWidth = $$forceReflow();
-
- // we use a for loop to ensure that if the queue is changed
- // during this looping then it will consider new requests
- for (var i = 0; i < rafWaitQueue.length; i++) {
- rafWaitQueue[i](pageWidth);
- }
- rafWaitQueue.length = 0;
- });
- }
-
- function computeTimings(node, className, cacheKey) {
- var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES);
- var aD = timings.animationDelay;
- var tD = timings.transitionDelay;
- timings.maxDelay = aD && tD
- ? Math.max(aD, tD)
- : (aD || tD);
- timings.maxDuration = Math.max(
- timings.animationDuration * timings.animationIterationCount,
- timings.transitionDuration);
-
- return timings;
- }
-
- return function init(element, initialOptions) {
- // all of the animation functions should create
- // a copy of the options data, however, if a
- // parent service has already created a copy then
- // we should stick to using that
- var options = initialOptions || {};
- if (!options.$$prepared) {
- options = prepareAnimationOptions(copy(options));
- }
-
- var restoreStyles = {};
- var node = getDomNode(element);
- if (!node
- || !node.parentNode
- || !$$animateQueue.enabled()) {
- return closeAndReturnNoopAnimator();
- }
-
- var temporaryStyles = [];
- var classes = element.attr('class');
- var styles = packageStyles(options);
- var animationClosed;
- var animationPaused;
- var animationCompleted;
- var runner;
- var runnerHost;
- var maxDelay;
- var maxDelayTime;
- var maxDuration;
- var maxDurationTime;
- var startTime;
- var events = [];
-
- if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) {
- return closeAndReturnNoopAnimator();
- }
-
- var method = options.event && isArray(options.event)
- ? options.event.join(' ')
- : options.event;
-
- var isStructural = method && options.structural;
- var structuralClassName = '';
- var addRemoveClassName = '';
-
- if (isStructural) {
- structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true);
- } else if (method) {
- structuralClassName = method;
- }
-
- if (options.addClass) {
- addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX);
- }
-
- if (options.removeClass) {
- if (addRemoveClassName.length) {
- addRemoveClassName += ' ';
- }
- addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX);
- }
-
- // there may be a situation where a structural animation is combined together
- // with CSS classes that need to resolve before the animation is computed.
- // However this means that there is no explicit CSS code to block the animation
- // from happening (by setting 0s none in the class name). If this is the case
- // we need to apply the classes before the first rAF so we know to continue if
- // there actually is a detected transition or keyframe animation
- if (options.applyClassesEarly && addRemoveClassName.length) {
- applyAnimationClasses(element, options);
- }
-
- var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim();
- var fullClassName = classes + ' ' + preparationClasses;
- var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX);
- var hasToStyles = styles.to && Object.keys(styles.to).length > 0;
- var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0;
-
- // there is no way we can trigger an animation if no styles and
- // no classes are being applied which would then trigger a transition,
- // unless there a is raw keyframe value that is applied to the element.
- if (!containsKeyframeAnimation
- && !hasToStyles
- && !preparationClasses) {
- return closeAndReturnNoopAnimator();
- }
-
- var cacheKey, stagger;
- if (options.stagger > 0) {
- var staggerVal = parseFloat(options.stagger);
- stagger = {
- transitionDelay: staggerVal,
- animationDelay: staggerVal,
- transitionDuration: 0,
- animationDuration: 0
- };
- } else {
- cacheKey = gcsHashFn(node, fullClassName);
- stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES);
- }
-
- if (!options.$$skipPreparationClasses) {
- $$jqLite.addClass(element, preparationClasses);
- }
-
- var applyOnlyDuration;
-
- if (options.transitionStyle) {
- var transitionStyle = [TRANSITION_PROP, options.transitionStyle];
- applyInlineStyle(node, transitionStyle);
- temporaryStyles.push(transitionStyle);
- }
-
- if (options.duration >= 0) {
- applyOnlyDuration = node.style[TRANSITION_PROP].length > 0;
- var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration);
-
- // we set the duration so that it will be picked up by getComputedStyle later
- applyInlineStyle(node, durationStyle);
- temporaryStyles.push(durationStyle);
- }
-
- if (options.keyframeStyle) {
- var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle];
- applyInlineStyle(node, keyframeStyle);
- temporaryStyles.push(keyframeStyle);
- }
-
- var itemIndex = stagger
- ? options.staggerIndex >= 0
- ? options.staggerIndex
- : gcsLookup.count(cacheKey)
- : 0;
-
- var isFirst = itemIndex === 0;
-
- // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY
- // without causing any combination of transitions to kick in. By adding a negative delay value
- // it forces the setup class' transition to end immediately. We later then remove the negative
- // transition delay to allow for the transition to naturally do it's thing. The beauty here is
- // that if there is no transition defined then nothing will happen and this will also allow
- // other transitions to be stacked on top of each other without any chopping them out.
- if (isFirst && !options.skipBlocking) {
- blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE);
- }
-
- var timings = computeTimings(node, fullClassName, cacheKey);
- var relativeDelay = timings.maxDelay;
- maxDelay = Math.max(relativeDelay, 0);
- maxDuration = timings.maxDuration;
-
- var flags = {};
- flags.hasTransitions = timings.transitionDuration > 0;
- flags.hasAnimations = timings.animationDuration > 0;
- flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty == 'all';
- flags.applyTransitionDuration = hasToStyles && (
- (flags.hasTransitions && !flags.hasTransitionAll)
- || (flags.hasAnimations && !flags.hasTransitions));
- flags.applyAnimationDuration = options.duration && flags.hasAnimations;
- flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions);
- flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations;
- flags.recalculateTimingStyles = addRemoveClassName.length > 0;
-
- if (flags.applyTransitionDuration || flags.applyAnimationDuration) {
- maxDuration = options.duration ? parseFloat(options.duration) : maxDuration;
-
- if (flags.applyTransitionDuration) {
- flags.hasTransitions = true;
- timings.transitionDuration = maxDuration;
- applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0;
- temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration));
- }
-
- if (flags.applyAnimationDuration) {
- flags.hasAnimations = true;
- timings.animationDuration = maxDuration;
- temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration));
- }
- }
-
- if (maxDuration === 0 && !flags.recalculateTimingStyles) {
- return closeAndReturnNoopAnimator();
- }
-
- if (options.delay != null) {
- var delayStyle;
- if (typeof options.delay !== "boolean") {
- delayStyle = parseFloat(options.delay);
- // number in options.delay means we have to recalculate the delay for the closing timeout
- maxDelay = Math.max(delayStyle, 0);
- }
-
- if (flags.applyTransitionDelay) {
- temporaryStyles.push(getCssDelayStyle(delayStyle));
- }
-
- if (flags.applyAnimationDelay) {
- temporaryStyles.push(getCssDelayStyle(delayStyle, true));
- }
- }
-
- // we need to recalculate the delay value since we used a pre-emptive negative
- // delay value and the delay value is required for the final event checking. This
- // property will ensure that this will happen after the RAF phase has passed.
- if (options.duration == null && timings.transitionDuration > 0) {
- flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst;
- }
-
- maxDelayTime = maxDelay * ONE_SECOND;
- maxDurationTime = maxDuration * ONE_SECOND;
- if (!options.skipBlocking) {
- flags.blockTransition = timings.transitionDuration > 0;
- flags.blockKeyframeAnimation = timings.animationDuration > 0 &&
- stagger.animationDelay > 0 &&
- stagger.animationDuration === 0;
- }
-
- if (options.from) {
- if (options.cleanupStyles) {
- registerRestorableStyles(restoreStyles, node, Object.keys(options.from));
- }
- applyAnimationFromStyles(element, options);
- }
-
- if (flags.blockTransition || flags.blockKeyframeAnimation) {
- applyBlocking(maxDuration);
- } else if (!options.skipBlocking) {
- blockTransitions(node, false);
- }
-
- // TODO(matsko): for 1.5 change this code to have an animator object for better debugging
- return {
- $$willAnimate: true,
- end: endFn,
- start: function() {
- if (animationClosed) return;
-
- runnerHost = {
- end: endFn,
- cancel: cancelFn,
- resume: null, //this will be set during the start() phase
- pause: null
- };
-
- runner = new $$AnimateRunner(runnerHost);
-
- waitUntilQuiet(start);
-
- // we don't have access to pause/resume the animation
- // since it hasn't run yet. AnimateRunner will therefore
- // set noop functions for resume and pause and they will
- // later be overridden once the animation is triggered
- return runner;
- }
- };
-
- function endFn() {
- close();
- }
-
- function cancelFn() {
- close(true);
- }
-
- function close(rejected) { // jshint ignore:line
- // if the promise has been called already then we shouldn't close
- // the animation again
- if (animationClosed || (animationCompleted && animationPaused)) return;
- animationClosed = true;
- animationPaused = false;
-
- if (!options.$$skipPreparationClasses) {
- $$jqLite.removeClass(element, preparationClasses);
- }
- $$jqLite.removeClass(element, activeClasses);
-
- blockKeyframeAnimations(node, false);
- blockTransitions(node, false);
-
- forEach(temporaryStyles, function(entry) {
- // There is only one way to remove inline style properties entirely from elements.
- // By using `removeProperty` this works, but we need to convert camel-cased CSS
- // styles down to hyphenated values.
- node.style[entry[0]] = '';
- });
-
- applyAnimationClasses(element, options);
- applyAnimationStyles(element, options);
-
- if (Object.keys(restoreStyles).length) {
- forEach(restoreStyles, function(value, prop) {
- value ? node.style.setProperty(prop, value)
- : node.style.removeProperty(prop);
- });
- }
-
- // the reason why we have this option is to allow a synchronous closing callback
- // that is fired as SOON as the animation ends (when the CSS is removed) or if
- // the animation never takes off at all. A good example is a leave animation since
- // the element must be removed just after the animation is over or else the element
- // will appear on screen for one animation frame causing an overbearing flicker.
- if (options.onDone) {
- options.onDone();
- }
-
- if (events && events.length) {
- // Remove the transitionend / animationend listener(s)
- element.off(events.join(' '), onAnimationProgress);
- }
-
- //Cancel the fallback closing timeout and remove the timer data
- var animationTimerData = element.data(ANIMATE_TIMER_KEY);
- if (animationTimerData) {
- $timeout.cancel(animationTimerData[0].timer);
- element.removeData(ANIMATE_TIMER_KEY);
- }
-
- // if the preparation function fails then the promise is not setup
- if (runner) {
- runner.complete(!rejected);
- }
- }
-
- function applyBlocking(duration) {
- if (flags.blockTransition) {
- blockTransitions(node, duration);
- }
-
- if (flags.blockKeyframeAnimation) {
- blockKeyframeAnimations(node, !!duration);
- }
- }
-
- function closeAndReturnNoopAnimator() {
- runner = new $$AnimateRunner({
- end: endFn,
- cancel: cancelFn
- });
-
- // should flush the cache animation
- waitUntilQuiet(noop);
- close();
-
- return {
- $$willAnimate: false,
- start: function() {
- return runner;
- },
- end: endFn
- };
- }
-
- function onAnimationProgress(event) {
- event.stopPropagation();
- var ev = event.originalEvent || event;
-
- // we now always use `Date.now()` due to the recent changes with
- // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info)
- var timeStamp = ev.$manualTimeStamp || Date.now();
-
- /* Firefox (or possibly just Gecko) likes to not round values up
- * when a ms measurement is used for the animation */
- var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES));
-
- /* $manualTimeStamp is a mocked timeStamp value which is set
- * within browserTrigger(). This is only here so that tests can
- * mock animations properly. Real events fallback to event.timeStamp,
- * or, if they don't, then a timeStamp is automatically created for them.
- * We're checking to see if the timeStamp surpasses the expected delay,
- * but we're using elapsedTime instead of the timeStamp on the 2nd
- * pre-condition since animationPauseds sometimes close off early */
- if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) {
- // we set this flag to ensure that if the transition is paused then, when resumed,
- // the animation will automatically close itself since transitions cannot be paused.
- animationCompleted = true;
- close();
- }
- }
-
- function start() {
- if (animationClosed) return;
- if (!node.parentNode) {
- close();
- return;
- }
-
- // even though we only pause keyframe animations here the pause flag
- // will still happen when transitions are used. Only the transition will
- // not be paused since that is not possible. If the animation ends when
- // paused then it will not complete until unpaused or cancelled.
- var playPause = function(playAnimation) {
- if (!animationCompleted) {
- animationPaused = !playAnimation;
- if (timings.animationDuration) {
- var value = blockKeyframeAnimations(node, animationPaused);
- animationPaused
- ? temporaryStyles.push(value)
- : removeFromArray(temporaryStyles, value);
- }
- } else if (animationPaused && playAnimation) {
- animationPaused = false;
- close();
- }
- };
-
- // checking the stagger duration prevents an accidentally cascade of the CSS delay style
- // being inherited from the parent. If the transition duration is zero then we can safely
- // rely that the delay value is an intentional stagger delay style.
- var maxStagger = itemIndex > 0
- && ((timings.transitionDuration && stagger.transitionDuration === 0) ||
- (timings.animationDuration && stagger.animationDuration === 0))
- && Math.max(stagger.animationDelay, stagger.transitionDelay);
- if (maxStagger) {
- $timeout(triggerAnimationStart,
- Math.floor(maxStagger * itemIndex * ONE_SECOND),
- false);
- } else {
- triggerAnimationStart();
- }
-
- // this will decorate the existing promise runner with pause/resume methods
- runnerHost.resume = function() {
- playPause(true);
- };
-
- runnerHost.pause = function() {
- playPause(false);
- };
-
- function triggerAnimationStart() {
- // just incase a stagger animation kicks in when the animation
- // itself was cancelled entirely
- if (animationClosed) return;
-
- applyBlocking(false);
-
- forEach(temporaryStyles, function(entry) {
- var key = entry[0];
- var value = entry[1];
- node.style[key] = value;
- });
-
- applyAnimationClasses(element, options);
- $$jqLite.addClass(element, activeClasses);
-
- if (flags.recalculateTimingStyles) {
- fullClassName = node.className + ' ' + preparationClasses;
- cacheKey = gcsHashFn(node, fullClassName);
-
- timings = computeTimings(node, fullClassName, cacheKey);
- relativeDelay = timings.maxDelay;
- maxDelay = Math.max(relativeDelay, 0);
- maxDuration = timings.maxDuration;
-
- if (maxDuration === 0) {
- close();
- return;
- }
-
- flags.hasTransitions = timings.transitionDuration > 0;
- flags.hasAnimations = timings.animationDuration > 0;
- }
-
- if (flags.applyAnimationDelay) {
- relativeDelay = typeof options.delay !== "boolean" && truthyTimingValue(options.delay)
- ? parseFloat(options.delay)
- : relativeDelay;
-
- maxDelay = Math.max(relativeDelay, 0);
- timings.animationDelay = relativeDelay;
- delayStyle = getCssDelayStyle(relativeDelay, true);
- temporaryStyles.push(delayStyle);
- node.style[delayStyle[0]] = delayStyle[1];
- }
-
- maxDelayTime = maxDelay * ONE_SECOND;
- maxDurationTime = maxDuration * ONE_SECOND;
-
- if (options.easing) {
- var easeProp, easeVal = options.easing;
- if (flags.hasTransitions) {
- easeProp = TRANSITION_PROP + TIMING_KEY;
- temporaryStyles.push([easeProp, easeVal]);
- node.style[easeProp] = easeVal;
- }
- if (flags.hasAnimations) {
- easeProp = ANIMATION_PROP + TIMING_KEY;
- temporaryStyles.push([easeProp, easeVal]);
- node.style[easeProp] = easeVal;
- }
- }
-
- if (timings.transitionDuration) {
- events.push(TRANSITIONEND_EVENT);
- }
-
- if (timings.animationDuration) {
- events.push(ANIMATIONEND_EVENT);
- }
-
- startTime = Date.now();
- var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime;
- var endTime = startTime + timerTime;
-
- var animationsData = element.data(ANIMATE_TIMER_KEY) || [];
- var setupFallbackTimer = true;
- if (animationsData.length) {
- var currentTimerData = animationsData[0];
- setupFallbackTimer = endTime > currentTimerData.expectedEndTime;
- if (setupFallbackTimer) {
- $timeout.cancel(currentTimerData.timer);
- } else {
- animationsData.push(close);
- }
- }
-
- if (setupFallbackTimer) {
- var timer = $timeout(onAnimationExpired, timerTime, false);
- animationsData[0] = {
- timer: timer,
- expectedEndTime: endTime
- };
- animationsData.push(close);
- element.data(ANIMATE_TIMER_KEY, animationsData);
- }
-
- if (events.length) {
- element.on(events.join(' '), onAnimationProgress);
- }
-
- if (options.to) {
- if (options.cleanupStyles) {
- registerRestorableStyles(restoreStyles, node, Object.keys(options.to));
- }
- applyAnimationToStyles(element, options);
- }
- }
-
- function onAnimationExpired() {
- var animationsData = element.data(ANIMATE_TIMER_KEY);
-
- // this will be false in the event that the element was
- // removed from the DOM (via a leave animation or something
- // similar)
- if (animationsData) {
- for (var i = 1; i < animationsData.length; i++) {
- animationsData[i]();
- }
- element.removeData(ANIMATE_TIMER_KEY);
- }
- }
- }
- };
- }];
-}];
-
-var $$AnimateCssDriverProvider = ['$$animationProvider', function($$animationProvider) {
- $$animationProvider.drivers.push('$$animateCssDriver');
-
- var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim';
- var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor';
-
- var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out';
- var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in';
-
- function isDocumentFragment(node) {
- return node.parentNode && node.parentNode.nodeType === 11;
- }
-
- this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document',
- function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) {
-
- // only browsers that support these properties can render animations
- if (!$sniffer.animations && !$sniffer.transitions) return noop;
-
- var bodyNode = $document[0].body;
- var rootNode = getDomNode($rootElement);
-
- var rootBodyElement = jqLite(
- // this is to avoid using something that exists outside of the body
- // we also special case the doc fragment case because our unit test code
- // appends the $rootElement to the body after the app has been bootstrapped
- isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode
- );
-
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
-
- return function initDriverFn(animationDetails) {
- return animationDetails.from && animationDetails.to
- ? prepareFromToAnchorAnimation(animationDetails.from,
- animationDetails.to,
- animationDetails.classes,
- animationDetails.anchors)
- : prepareRegularAnimation(animationDetails);
- };
-
- function filterCssClasses(classes) {
- //remove all the `ng-` stuff
- return classes.replace(/\bng-\S+\b/g, '');
- }
-
- function getUniqueValues(a, b) {
- if (isString(a)) a = a.split(' ');
- if (isString(b)) b = b.split(' ');
- return a.filter(function(val) {
- return b.indexOf(val) === -1;
- }).join(' ');
- }
-
- function prepareAnchoredAnimation(classes, outAnchor, inAnchor) {
- var clone = jqLite(getDomNode(outAnchor).cloneNode(true));
- var startingClasses = filterCssClasses(getClassVal(clone));
-
- outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
- inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME);
-
- clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME);
-
- rootBodyElement.append(clone);
-
- var animatorIn, animatorOut = prepareOutAnimation();
-
- // the user may not end up using the `out` animation and
- // only making use of the `in` animation or vice-versa.
- // In either case we should allow this and not assume the
- // animation is over unless both animations are not used.
- if (!animatorOut) {
- animatorIn = prepareInAnimation();
- if (!animatorIn) {
- return end();
- }
- }
-
- var startingAnimator = animatorOut || animatorIn;
-
- return {
- start: function() {
- var runner;
-
- var currentAnimation = startingAnimator.start();
- currentAnimation.done(function() {
- currentAnimation = null;
- if (!animatorIn) {
- animatorIn = prepareInAnimation();
- if (animatorIn) {
- currentAnimation = animatorIn.start();
- currentAnimation.done(function() {
- currentAnimation = null;
- end();
- runner.complete();
- });
- return currentAnimation;
- }
- }
- // in the event that there is no `in` animation
- end();
- runner.complete();
- });
-
- runner = new $$AnimateRunner({
- end: endFn,
- cancel: endFn
- });
-
- return runner;
-
- function endFn() {
- if (currentAnimation) {
- currentAnimation.end();
- }
- }
- }
- };
-
- function calculateAnchorStyles(anchor) {
- var styles = {};
-
- var coords = getDomNode(anchor).getBoundingClientRect();
-
- // we iterate directly since safari messes up and doesn't return
- // all the keys for the coords object when iterated
- forEach(['width','height','top','left'], function(key) {
- var value = coords[key];
- switch (key) {
- case 'top':
- value += bodyNode.scrollTop;
- break;
- case 'left':
- value += bodyNode.scrollLeft;
- break;
- }
- styles[key] = Math.floor(value) + 'px';
- });
- return styles;
- }
-
- function prepareOutAnimation() {
- var animator = $animateCss(clone, {
- addClass: NG_OUT_ANCHOR_CLASS_NAME,
- delay: true,
- from: calculateAnchorStyles(outAnchor)
- });
-
- // read the comment within `prepareRegularAnimation` to understand
- // why this check is necessary
- return animator.$$willAnimate ? animator : null;
- }
-
- function getClassVal(element) {
- return element.attr('class') || '';
- }
-
- function prepareInAnimation() {
- var endingClasses = filterCssClasses(getClassVal(inAnchor));
- var toAdd = getUniqueValues(endingClasses, startingClasses);
- var toRemove = getUniqueValues(startingClasses, endingClasses);
-
- var animator = $animateCss(clone, {
- to: calculateAnchorStyles(inAnchor),
- addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd,
- removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove,
- delay: true
- });
-
- // read the comment within `prepareRegularAnimation` to understand
- // why this check is necessary
- return animator.$$willAnimate ? animator : null;
- }
-
- function end() {
- clone.remove();
- outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
- inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME);
- }
- }
-
- function prepareFromToAnchorAnimation(from, to, classes, anchors) {
- var fromAnimation = prepareRegularAnimation(from, noop);
- var toAnimation = prepareRegularAnimation(to, noop);
-
- var anchorAnimations = [];
- forEach(anchors, function(anchor) {
- var outElement = anchor['out'];
- var inElement = anchor['in'];
- var animator = prepareAnchoredAnimation(classes, outElement, inElement);
- if (animator) {
- anchorAnimations.push(animator);
- }
- });
-
- // no point in doing anything when there are no elements to animate
- if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return;
-
- return {
- start: function() {
- var animationRunners = [];
-
- if (fromAnimation) {
- animationRunners.push(fromAnimation.start());
- }
-
- if (toAnimation) {
- animationRunners.push(toAnimation.start());
- }
-
- forEach(anchorAnimations, function(animation) {
- animationRunners.push(animation.start());
- });
-
- var runner = new $$AnimateRunner({
- end: endFn,
- cancel: endFn // CSS-driven animations cannot be cancelled, only ended
- });
-
- $$AnimateRunner.all(animationRunners, function(status) {
- runner.complete(status);
- });
-
- return runner;
-
- function endFn() {
- forEach(animationRunners, function(runner) {
- runner.end();
- });
- }
- }
- };
- }
-
- function prepareRegularAnimation(animationDetails) {
- var element = animationDetails.element;
- var options = animationDetails.options || {};
-
- if (animationDetails.structural) {
- options.event = animationDetails.event;
- options.structural = true;
- options.applyClassesEarly = true;
-
- // we special case the leave animation since we want to ensure that
- // the element is removed as soon as the animation is over. Otherwise
- // a flicker might appear or the element may not be removed at all
- if (animationDetails.event === 'leave') {
- options.onDone = options.domOperation;
- }
- }
-
- // We assign the preparationClasses as the actual animation event since
- // the internals of $animateCss will just suffix the event token values
- // with `-active` to trigger the animation.
- if (options.preparationClasses) {
- options.event = concatWithSpace(options.event, options.preparationClasses);
- }
-
- var animator = $animateCss(element, options);
-
- // the driver lookup code inside of $$animation attempts to spawn a
- // driver one by one until a driver returns a.$$willAnimate animator object.
- // $animateCss will always return an object, however, it will pass in
- // a flag as a hint as to whether an animation was detected or not
- return animator.$$willAnimate ? animator : null;
- }
- }];
-}];
-
-// TODO(matsko): use caching here to speed things up for detection
-// TODO(matsko): add documentation
-// by the time...
-
-var $$AnimateJsProvider = ['$animateProvider', function($animateProvider) {
- this.$get = ['$injector', '$$AnimateRunner', '$$jqLite',
- function($injector, $$AnimateRunner, $$jqLite) {
-
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
- // $animateJs(element, 'enter');
- return function(element, event, classes, options) {
- var animationClosed = false;
-
- // the `classes` argument is optional and if it is not used
- // then the classes will be resolved from the element's className
- // property as well as options.addClass/options.removeClass.
- if (arguments.length === 3 && isObject(classes)) {
- options = classes;
- classes = null;
- }
-
- options = prepareAnimationOptions(options);
- if (!classes) {
- classes = element.attr('class') || '';
- if (options.addClass) {
- classes += ' ' + options.addClass;
- }
- if (options.removeClass) {
- classes += ' ' + options.removeClass;
- }
- }
-
- var classesToAdd = options.addClass;
- var classesToRemove = options.removeClass;
-
- // the lookupAnimations function returns a series of animation objects that are
- // matched up with one or more of the CSS classes. These animation objects are
- // defined via the module.animation factory function. If nothing is detected then
- // we don't return anything which then makes $animation query the next driver.
- var animations = lookupAnimations(classes);
- var before, after;
- if (animations.length) {
- var afterFn, beforeFn;
- if (event == 'leave') {
- beforeFn = 'leave';
- afterFn = 'afterLeave'; // TODO(matsko): get rid of this
- } else {
- beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1);
- afterFn = event;
- }
-
- if (event !== 'enter' && event !== 'move') {
- before = packageAnimations(element, event, options, animations, beforeFn);
- }
- after = packageAnimations(element, event, options, animations, afterFn);
- }
-
- // no matching animations
- if (!before && !after) return;
-
- function applyOptions() {
- options.domOperation();
- applyAnimationClasses(element, options);
- }
-
- function close() {
- animationClosed = true;
- applyOptions();
- applyAnimationStyles(element, options);
- }
-
- var runner;
-
- return {
- $$willAnimate: true,
- end: function() {
- if (runner) {
- runner.end();
- } else {
- close();
- runner = new $$AnimateRunner();
- runner.complete(true);
- }
- return runner;
- },
- start: function() {
- if (runner) {
- return runner;
- }
-
- runner = new $$AnimateRunner();
- var closeActiveAnimations;
- var chain = [];
-
- if (before) {
- chain.push(function(fn) {
- closeActiveAnimations = before(fn);
- });
- }
-
- if (chain.length) {
- chain.push(function(fn) {
- applyOptions();
- fn(true);
- });
- } else {
- applyOptions();
- }
-
- if (after) {
- chain.push(function(fn) {
- closeActiveAnimations = after(fn);
- });
- }
-
- runner.setHost({
- end: function() {
- endAnimations();
- },
- cancel: function() {
- endAnimations(true);
- }
- });
-
- $$AnimateRunner.chain(chain, onComplete);
- return runner;
-
- function onComplete(success) {
- close(success);
- runner.complete(success);
- }
-
- function endAnimations(cancelled) {
- if (!animationClosed) {
- (closeActiveAnimations || noop)(cancelled);
- onComplete(cancelled);
- }
- }
- }
- };
-
- function executeAnimationFn(fn, element, event, options, onDone) {
- var args;
- switch (event) {
- case 'animate':
- args = [element, options.from, options.to, onDone];
- break;
-
- case 'setClass':
- args = [element, classesToAdd, classesToRemove, onDone];
- break;
-
- case 'addClass':
- args = [element, classesToAdd, onDone];
- break;
-
- case 'removeClass':
- args = [element, classesToRemove, onDone];
- break;
-
- default:
- args = [element, onDone];
- break;
- }
-
- args.push(options);
-
- var value = fn.apply(fn, args);
- if (value) {
- if (isFunction(value.start)) {
- value = value.start();
- }
-
- if (value instanceof $$AnimateRunner) {
- value.done(onDone);
- } else if (isFunction(value)) {
- // optional onEnd / onCancel callback
- return value;
- }
- }
-
- return noop;
- }
-
- function groupEventedAnimations(element, event, options, animations, fnName) {
- var operations = [];
- forEach(animations, function(ani) {
- var animation = ani[fnName];
- if (!animation) return;
-
- // note that all of these animations will run in parallel
- operations.push(function() {
- var runner;
- var endProgressCb;
-
- var resolved = false;
- var onAnimationComplete = function(rejected) {
- if (!resolved) {
- resolved = true;
- (endProgressCb || noop)(rejected);
- runner.complete(!rejected);
- }
- };
-
- runner = new $$AnimateRunner({
- end: function() {
- onAnimationComplete();
- },
- cancel: function() {
- onAnimationComplete(true);
- }
- });
-
- endProgressCb = executeAnimationFn(animation, element, event, options, function(result) {
- var cancelled = result === false;
- onAnimationComplete(cancelled);
- });
-
- return runner;
- });
- });
-
- return operations;
- }
-
- function packageAnimations(element, event, options, animations, fnName) {
- var operations = groupEventedAnimations(element, event, options, animations, fnName);
- if (operations.length === 0) {
- var a,b;
- if (fnName === 'beforeSetClass') {
- a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass');
- b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass');
- } else if (fnName === 'setClass') {
- a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass');
- b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass');
- }
-
- if (a) {
- operations = operations.concat(a);
- }
- if (b) {
- operations = operations.concat(b);
- }
- }
-
- if (operations.length === 0) return;
-
- // TODO(matsko): add documentation
- return function startAnimation(callback) {
- var runners = [];
- if (operations.length) {
- forEach(operations, function(animateFn) {
- runners.push(animateFn());
- });
- }
-
- runners.length ? $$AnimateRunner.all(runners, callback) : callback();
-
- return function endFn(reject) {
- forEach(runners, function(runner) {
- reject ? runner.cancel() : runner.end();
- });
- };
- };
- }
- };
-
- function lookupAnimations(classes) {
- classes = isArray(classes) ? classes : classes.split(' ');
- var matches = [], flagMap = {};
- for (var i=0; i < classes.length; i++) {
- var klass = classes[i],
- animationFactory = $animateProvider.$$registeredAnimations[klass];
- if (animationFactory && !flagMap[klass]) {
- matches.push($injector.get(animationFactory));
- flagMap[klass] = true;
- }
- }
- return matches;
- }
- }];
-}];
-
-var $$AnimateJsDriverProvider = ['$$animationProvider', function($$animationProvider) {
- $$animationProvider.drivers.push('$$animateJsDriver');
- this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) {
- return function initDriverFn(animationDetails) {
- if (animationDetails.from && animationDetails.to) {
- var fromAnimation = prepareAnimation(animationDetails.from);
- var toAnimation = prepareAnimation(animationDetails.to);
- if (!fromAnimation && !toAnimation) return;
-
- return {
- start: function() {
- var animationRunners = [];
-
- if (fromAnimation) {
- animationRunners.push(fromAnimation.start());
- }
-
- if (toAnimation) {
- animationRunners.push(toAnimation.start());
- }
-
- $$AnimateRunner.all(animationRunners, done);
-
- var runner = new $$AnimateRunner({
- end: endFnFactory(),
- cancel: endFnFactory()
- });
-
- return runner;
-
- function endFnFactory() {
- return function() {
- forEach(animationRunners, function(runner) {
- // at this point we cannot cancel animations for groups just yet. 1.5+
- runner.end();
- });
- };
- }
-
- function done(status) {
- runner.complete(status);
- }
- }
- };
- } else {
- return prepareAnimation(animationDetails);
- }
- };
-
- function prepareAnimation(animationDetails) {
- // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations
- var element = animationDetails.element;
- var event = animationDetails.event;
- var options = animationDetails.options;
- var classes = animationDetails.classes;
- return $$animateJs(element, event, classes, options);
- }
- }];
-}];
-
-var NG_ANIMATE_ATTR_NAME = 'data-ng-animate';
-var NG_ANIMATE_PIN_DATA = '$ngAnimatePin';
-var $$AnimateQueueProvider = ['$animateProvider', function($animateProvider) {
- var PRE_DIGEST_STATE = 1;
- var RUNNING_STATE = 2;
- var ONE_SPACE = ' ';
-
- var rules = this.rules = {
- skip: [],
- cancel: [],
- join: []
- };
-
- function makeTruthyCssClassMap(classString) {
- if (!classString) {
- return null;
- }
-
- var keys = classString.split(ONE_SPACE);
- var map = Object.create(null);
-
- forEach(keys, function(key) {
- map[key] = true;
- });
- return map;
- }
-
- function hasMatchingClasses(newClassString, currentClassString) {
- if (newClassString && currentClassString) {
- var currentClassMap = makeTruthyCssClassMap(currentClassString);
- return newClassString.split(ONE_SPACE).some(function(className) {
- return currentClassMap[className];
- });
- }
- }
-
- function isAllowed(ruleType, element, currentAnimation, previousAnimation) {
- return rules[ruleType].some(function(fn) {
- return fn(element, currentAnimation, previousAnimation);
- });
- }
-
- function hasAnimationClasses(animation, and) {
- var a = (animation.addClass || '').length > 0;
- var b = (animation.removeClass || '').length > 0;
- return and ? a && b : a || b;
- }
-
- rules.join.push(function(element, newAnimation, currentAnimation) {
- // if the new animation is class-based then we can just tack that on
- return !newAnimation.structural && hasAnimationClasses(newAnimation);
- });
-
- rules.skip.push(function(element, newAnimation, currentAnimation) {
- // there is no need to animate anything if no classes are being added and
- // there is no structural animation that will be triggered
- return !newAnimation.structural && !hasAnimationClasses(newAnimation);
- });
-
- rules.skip.push(function(element, newAnimation, currentAnimation) {
- // why should we trigger a new structural animation if the element will
- // be removed from the DOM anyway?
- return currentAnimation.event == 'leave' && newAnimation.structural;
- });
-
- rules.skip.push(function(element, newAnimation, currentAnimation) {
- // if there is an ongoing current animation then don't even bother running the class-based animation
- return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural;
- });
-
- rules.cancel.push(function(element, newAnimation, currentAnimation) {
- // there can never be two structural animations running at the same time
- return currentAnimation.structural && newAnimation.structural;
- });
-
- rules.cancel.push(function(element, newAnimation, currentAnimation) {
- // if the previous animation is already running, but the new animation will
- // be triggered, but the new animation is structural
- return currentAnimation.state === RUNNING_STATE && newAnimation.structural;
- });
-
- rules.cancel.push(function(element, newAnimation, currentAnimation) {
- // cancel the animation if classes added / removed in both animation cancel each other out,
- // but only if the current animation isn't structural
-
- if (currentAnimation.structural) return false;
-
- var nA = newAnimation.addClass;
- var nR = newAnimation.removeClass;
- var cA = currentAnimation.addClass;
- var cR = currentAnimation.removeClass;
-
- // early detection to save the global CPU shortage :)
- if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) {
- return false;
- }
-
- return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA);
- });
-
- this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap',
- '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow',
- function($$rAF, $rootScope, $rootElement, $document, $$HashMap,
- $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) {
-
- var activeAnimationsLookup = new $$HashMap();
- var disabledElementsLookup = new $$HashMap();
- var animationsEnabled = null;
-
- function postDigestTaskFactory() {
- var postDigestCalled = false;
- return function(fn) {
- // we only issue a call to postDigest before
- // it has first passed. This prevents any callbacks
- // from not firing once the animation has completed
- // since it will be out of the digest cycle.
- if (postDigestCalled) {
- fn();
- } else {
- $rootScope.$$postDigest(function() {
- postDigestCalled = true;
- fn();
- });
- }
- };
- }
-
- // Wait until all directive and route-related templates are downloaded and
- // compiled. The $templateRequest.totalPendingRequests variable keeps track of
- // all of the remote templates being currently downloaded. If there are no
- // templates currently downloading then the watcher will still fire anyway.
- var deregisterWatch = $rootScope.$watch(
- function() { return $templateRequest.totalPendingRequests === 0; },
- function(isEmpty) {
- if (!isEmpty) return;
- deregisterWatch();
-
- // Now that all templates have been downloaded, $animate will wait until
- // the post digest queue is empty before enabling animations. By having two
- // calls to $postDigest calls we can ensure that the flag is enabled at the
- // very end of the post digest queue. Since all of the animations in $animate
- // use $postDigest, it's important that the code below executes at the end.
- // This basically means that the page is fully downloaded and compiled before
- // any animations are triggered.
- $rootScope.$$postDigest(function() {
- $rootScope.$$postDigest(function() {
- // we check for null directly in the event that the application already called
- // .enabled() with whatever arguments that it provided it with
- if (animationsEnabled === null) {
- animationsEnabled = true;
- }
- });
- });
- }
- );
-
- var callbackRegistry = {};
-
- // remember that the classNameFilter is set during the provider/config
- // stage therefore we can optimize here and setup a helper function
- var classNameFilter = $animateProvider.classNameFilter();
- var isAnimatableClassName = !classNameFilter
- ? function() { return true; }
- : function(className) {
- return classNameFilter.test(className);
- };
-
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
-
- function normalizeAnimationDetails(element, animation) {
- return mergeAnimationDetails(element, animation, {});
- }
-
- // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259.
- var contains = Node.prototype.contains || function(arg) {
- // jshint bitwise: false
- return this === arg || !!(this.compareDocumentPosition(arg) & 16);
- // jshint bitwise: true
- };
-
- function findCallbacks(parent, element, event) {
- var targetNode = getDomNode(element);
- var targetParentNode = getDomNode(parent);
-
- var matches = [];
- var entries = callbackRegistry[event];
- if (entries) {
- forEach(entries, function(entry) {
- if (contains.call(entry.node, targetNode)) {
- matches.push(entry.callback);
- } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) {
- matches.push(entry.callback);
- }
- });
- }
-
- return matches;
- }
-
- var $animate = {
- on: function(event, container, callback) {
- var node = extractElementNode(container);
- callbackRegistry[event] = callbackRegistry[event] || [];
- callbackRegistry[event].push({
- node: node,
- callback: callback
- });
-
- // Remove the callback when the element is removed from the DOM
- jqLite(container).on('$destroy', function() {
- $animate.off(event, container, callback);
- });
- },
-
- off: function(event, container, callback) {
- var entries = callbackRegistry[event];
- if (!entries) return;
-
- callbackRegistry[event] = arguments.length === 1
- ? null
- : filterFromRegistry(entries, container, callback);
-
- function filterFromRegistry(list, matchContainer, matchCallback) {
- var containerNode = extractElementNode(matchContainer);
- return list.filter(function(entry) {
- var isMatch = entry.node === containerNode &&
- (!matchCallback || entry.callback === matchCallback);
- return !isMatch;
- });
- }
- },
-
- pin: function(element, parentElement) {
- assertArg(isElement(element), 'element', 'not an element');
- assertArg(isElement(parentElement), 'parentElement', 'not an element');
- element.data(NG_ANIMATE_PIN_DATA, parentElement);
- },
-
- push: function(element, event, options, domOperation) {
- options = options || {};
- options.domOperation = domOperation;
- return queueAnimation(element, event, options);
- },
-
- // this method has four signatures:
- // () - global getter
- // (bool) - global setter
- // (element) - element getter
- // (element, bool) - element setter
- enabled: function(element, bool) {
- var argCount = arguments.length;
-
- if (argCount === 0) {
- // () - Global getter
- bool = !!animationsEnabled;
- } else {
- var hasElement = isElement(element);
-
- if (!hasElement) {
- // (bool) - Global setter
- bool = animationsEnabled = !!element;
- } else {
- var node = getDomNode(element);
- var recordExists = disabledElementsLookup.get(node);
-
- if (argCount === 1) {
- // (element) - Element getter
- bool = !recordExists;
- } else {
- // (element, bool) - Element setter
- disabledElementsLookup.put(node, !bool);
- }
- }
- }
-
- return bool;
- }
- };
-
- return $animate;
-
- function queueAnimation(element, event, initialOptions) {
- // we always make a copy of the options since
- // there should never be any side effects on
- // the input data when running `$animateCss`.
- var options = copy(initialOptions);
-
- var node, parent;
- element = stripCommentsFromElement(element);
- if (element) {
- node = getDomNode(element);
- parent = element.parent();
- }
-
- options = prepareAnimationOptions(options);
-
- // we create a fake runner with a working promise.
- // These methods will become available after the digest has passed
- var runner = new $$AnimateRunner();
-
- // this is used to trigger callbacks in postDigest mode
- var runInNextPostDigestOrNow = postDigestTaskFactory();
-
- if (isArray(options.addClass)) {
- options.addClass = options.addClass.join(' ');
- }
-
- if (options.addClass && !isString(options.addClass)) {
- options.addClass = null;
- }
-
- if (isArray(options.removeClass)) {
- options.removeClass = options.removeClass.join(' ');
- }
-
- if (options.removeClass && !isString(options.removeClass)) {
- options.removeClass = null;
- }
-
- if (options.from && !isObject(options.from)) {
- options.from = null;
- }
-
- if (options.to && !isObject(options.to)) {
- options.to = null;
- }
-
- // there are situations where a directive issues an animation for
- // a jqLite wrapper that contains only comment nodes... If this
- // happens then there is no way we can perform an animation
- if (!node) {
- close();
- return runner;
- }
-
- var className = [node.className, options.addClass, options.removeClass].join(' ');
- if (!isAnimatableClassName(className)) {
- close();
- return runner;
- }
-
- var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
-
- // this is a hard disable of all animations for the application or on
- // the element itself, therefore there is no need to continue further
- // past this point if not enabled
- // Animations are also disabled if the document is currently hidden (page is not visible
- // to the user), because browsers slow down or do not flush calls to requestAnimationFrame
- var skipAnimations = !animationsEnabled || $document[0].hidden || disabledElementsLookup.get(node);
- var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {};
- var hasExistingAnimation = !!existingAnimation.state;
-
- // there is no point in traversing the same collection of parent ancestors if a followup
- // animation will be run on the same element that already did all that checking work
- if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state != PRE_DIGEST_STATE)) {
- skipAnimations = !areAnimationsAllowed(element, parent, event);
- }
-
- if (skipAnimations) {
- close();
- return runner;
- }
-
- if (isStructural) {
- closeChildAnimations(element);
- }
-
- var newAnimation = {
- structural: isStructural,
- element: element,
- event: event,
- addClass: options.addClass,
- removeClass: options.removeClass,
- close: close,
- options: options,
- runner: runner
- };
-
- if (hasExistingAnimation) {
- var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation);
- if (skipAnimationFlag) {
- if (existingAnimation.state === RUNNING_STATE) {
- close();
- return runner;
- } else {
- mergeAnimationDetails(element, existingAnimation, newAnimation);
- return existingAnimation.runner;
- }
- }
- var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation);
- if (cancelAnimationFlag) {
- if (existingAnimation.state === RUNNING_STATE) {
- // this will end the animation right away and it is safe
- // to do so since the animation is already running and the
- // runner callback code will run in async
- existingAnimation.runner.end();
- } else if (existingAnimation.structural) {
- // this means that the animation is queued into a digest, but
- // hasn't started yet. Therefore it is safe to run the close
- // method which will call the runner methods in async.
- existingAnimation.close();
- } else {
- // this will merge the new animation options into existing animation options
- mergeAnimationDetails(element, existingAnimation, newAnimation);
-
- return existingAnimation.runner;
- }
- } else {
- // a joined animation means that this animation will take over the existing one
- // so an example would involve a leave animation taking over an enter. Then when
- // the postDigest kicks in the enter will be ignored.
- var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation);
- if (joinAnimationFlag) {
- if (existingAnimation.state === RUNNING_STATE) {
- normalizeAnimationDetails(element, newAnimation);
- } else {
- applyGeneratedPreparationClasses(element, isStructural ? event : null, options);
-
- event = newAnimation.event = existingAnimation.event;
- options = mergeAnimationDetails(element, existingAnimation, newAnimation);
-
- //we return the same runner since only the option values of this animation will
- //be fed into the `existingAnimation`.
- return existingAnimation.runner;
- }
- }
- }
- } else {
- // normalization in this case means that it removes redundant CSS classes that
- // already exist (addClass) or do not exist (removeClass) on the element
- normalizeAnimationDetails(element, newAnimation);
- }
-
- // when the options are merged and cleaned up we may end up not having to do
- // an animation at all, therefore we should check this before issuing a post
- // digest callback. Structural animations will always run no matter what.
- var isValidAnimation = newAnimation.structural;
- if (!isValidAnimation) {
- // animate (from/to) can be quickly checked first, otherwise we check if any classes are present
- isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0)
- || hasAnimationClasses(newAnimation);
- }
-
- if (!isValidAnimation) {
- close();
- clearElementAnimationState(element);
- return runner;
- }
-
- // the counter keeps track of cancelled animations
- var counter = (existingAnimation.counter || 0) + 1;
- newAnimation.counter = counter;
-
- markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation);
-
- $rootScope.$$postDigest(function() {
- var animationDetails = activeAnimationsLookup.get(node);
- var animationCancelled = !animationDetails;
- animationDetails = animationDetails || {};
-
- // if addClass/removeClass is called before something like enter then the
- // registered parent element may not be present. The code below will ensure
- // that a final value for parent element is obtained
- var parentElement = element.parent() || [];
-
- // animate/structural/class-based animations all have requirements. Otherwise there
- // is no point in performing an animation. The parent node must also be set.
- var isValidAnimation = parentElement.length > 0
- && (animationDetails.event === 'animate'
- || animationDetails.structural
- || hasAnimationClasses(animationDetails));
-
- // this means that the previous animation was cancelled
- // even if the follow-up animation is the same event
- if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) {
- // if another animation did not take over then we need
- // to make sure that the domOperation and options are
- // handled accordingly
- if (animationCancelled) {
- applyAnimationClasses(element, options);
- applyAnimationStyles(element, options);
- }
-
- // if the event changed from something like enter to leave then we do
- // it, otherwise if it's the same then the end result will be the same too
- if (animationCancelled || (isStructural && animationDetails.event !== event)) {
- options.domOperation();
- runner.end();
- }
-
- // in the event that the element animation was not cancelled or a follow-up animation
- // isn't allowed to animate from here then we need to clear the state of the element
- // so that any future animations won't read the expired animation data.
- if (!isValidAnimation) {
- clearElementAnimationState(element);
- }
-
- return;
- }
-
- // this combined multiple class to addClass / removeClass into a setClass event
- // so long as a structural event did not take over the animation
- event = !animationDetails.structural && hasAnimationClasses(animationDetails, true)
- ? 'setClass'
- : animationDetails.event;
-
- markElementAnimationState(element, RUNNING_STATE);
- var realRunner = $$animation(element, event, animationDetails.options);
-
- realRunner.done(function(status) {
- close(!status);
- var animationDetails = activeAnimationsLookup.get(node);
- if (animationDetails && animationDetails.counter === counter) {
- clearElementAnimationState(getDomNode(element));
- }
- notifyProgress(runner, event, 'close', {});
- });
-
- // this will update the runner's flow-control events based on
- // the `realRunner` object.
- runner.setHost(realRunner);
- notifyProgress(runner, event, 'start', {});
- });
-
- return runner;
-
- function notifyProgress(runner, event, phase, data) {
- runInNextPostDigestOrNow(function() {
- var callbacks = findCallbacks(parent, element, event);
- if (callbacks.length) {
- // do not optimize this call here to RAF because
- // we don't know how heavy the callback code here will
- // be and if this code is buffered then this can
- // lead to a performance regression.
- $$rAF(function() {
- forEach(callbacks, function(callback) {
- callback(element, phase, data);
- });
- });
- }
- });
- runner.progress(event, phase, data);
- }
-
- function close(reject) { // jshint ignore:line
- clearGeneratedClasses(element, options);
- applyAnimationClasses(element, options);
- applyAnimationStyles(element, options);
- options.domOperation();
- runner.complete(!reject);
- }
- }
-
- function closeChildAnimations(element) {
- var node = getDomNode(element);
- var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']');
- forEach(children, function(child) {
- var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME));
- var animationDetails = activeAnimationsLookup.get(child);
- if (animationDetails) {
- switch (state) {
- case RUNNING_STATE:
- animationDetails.runner.end();
- /* falls through */
- case PRE_DIGEST_STATE:
- activeAnimationsLookup.remove(child);
- break;
- }
- }
- });
- }
-
- function clearElementAnimationState(element) {
- var node = getDomNode(element);
- node.removeAttribute(NG_ANIMATE_ATTR_NAME);
- activeAnimationsLookup.remove(node);
- }
-
- function isMatchingElement(nodeOrElmA, nodeOrElmB) {
- return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB);
- }
-
- /**
- * This fn returns false if any of the following is true:
- * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed
- * b) a parent element has an ongoing structural animation, and animateChildren is false
- * c) the element is not a child of the body
- * d) the element is not a child of the $rootElement
- */
- function areAnimationsAllowed(element, parentElement, event) {
- var bodyElement = jqLite($document[0].body);
- var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML';
- var rootElementDetected = isMatchingElement(element, $rootElement);
- var parentAnimationDetected = false;
- var animateChildren;
- var elementDisabled = disabledElementsLookup.get(getDomNode(element));
-
- var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA);
- if (parentHost) {
- parentElement = parentHost;
- }
-
- parentElement = getDomNode(parentElement);
-
- while (parentElement) {
- if (!rootElementDetected) {
- // angular doesn't want to attempt to animate elements outside of the application
- // therefore we need to ensure that the rootElement is an ancestor of the current element
- rootElementDetected = isMatchingElement(parentElement, $rootElement);
- }
-
- if (parentElement.nodeType !== ELEMENT_NODE) {
- // no point in inspecting the #document element
- break;
- }
-
- var details = activeAnimationsLookup.get(parentElement) || {};
- // either an enter, leave or move animation will commence
- // therefore we can't allow any animations to take place
- // but if a parent animation is class-based then that's ok
- if (!parentAnimationDetected) {
- var parentElementDisabled = disabledElementsLookup.get(parentElement);
-
- if (parentElementDisabled === true && elementDisabled !== false) {
- // disable animations if the user hasn't explicitly enabled animations on the
- // current element
- elementDisabled = true;
- // element is disabled via parent element, no need to check anything else
- break;
- } else if (parentElementDisabled === false) {
- elementDisabled = false;
- }
- parentAnimationDetected = details.structural;
- }
-
- if (isUndefined(animateChildren) || animateChildren === true) {
- var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA);
- if (isDefined(value)) {
- animateChildren = value;
- }
- }
-
- // there is no need to continue traversing at this point
- if (parentAnimationDetected && animateChildren === false) break;
-
- if (!bodyElementDetected) {
- // we also need to ensure that the element is or will be a part of the body element
- // otherwise it is pointless to even issue an animation to be rendered
- bodyElementDetected = isMatchingElement(parentElement, bodyElement);
- }
-
- if (bodyElementDetected && rootElementDetected) {
- // If both body and root have been found, any other checks are pointless,
- // as no animation data should live outside the application
- break;
- }
-
- if (!rootElementDetected) {
- // If no rootElement is detected, check if the parentElement is pinned to another element
- parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA);
- if (parentHost) {
- // The pin target element becomes the next parent element
- parentElement = getDomNode(parentHost);
- continue;
- }
- }
-
- parentElement = parentElement.parentNode;
- }
-
- var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true;
- return allowAnimation && rootElementDetected && bodyElementDetected;
- }
-
- function markElementAnimationState(element, state, details) {
- details = details || {};
- details.state = state;
-
- var node = getDomNode(element);
- node.setAttribute(NG_ANIMATE_ATTR_NAME, state);
-
- var oldValue = activeAnimationsLookup.get(node);
- var newValue = oldValue
- ? extend(oldValue, details)
- : details;
- activeAnimationsLookup.put(node, newValue);
- }
- }];
-}];
-
-var $$AnimationProvider = ['$animateProvider', function($animateProvider) {
- var NG_ANIMATE_REF_ATTR = 'ng-animate-ref';
-
- var drivers = this.drivers = [];
-
- var RUNNER_STORAGE_KEY = '$$animationRunner';
-
- function setRunner(element, runner) {
- element.data(RUNNER_STORAGE_KEY, runner);
- }
-
- function removeRunner(element) {
- element.removeData(RUNNER_STORAGE_KEY);
- }
-
- function getRunner(element) {
- return element.data(RUNNER_STORAGE_KEY);
- }
-
- this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler',
- function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) {
-
- var animationQueue = [];
- var applyAnimationClasses = applyAnimationClassesFactory($$jqLite);
-
- function sortAnimations(animations) {
- var tree = { children: [] };
- var i, lookup = new $$HashMap();
-
- // this is done first beforehand so that the hashmap
- // is filled with a list of the elements that will be animated
- for (i = 0; i < animations.length; i++) {
- var animation = animations[i];
- lookup.put(animation.domNode, animations[i] = {
- domNode: animation.domNode,
- fn: animation.fn,
- children: []
- });
- }
-
- for (i = 0; i < animations.length; i++) {
- processNode(animations[i]);
- }
-
- return flatten(tree);
-
- function processNode(entry) {
- if (entry.processed) return entry;
- entry.processed = true;
-
- var elementNode = entry.domNode;
- var parentNode = elementNode.parentNode;
- lookup.put(elementNode, entry);
-
- var parentEntry;
- while (parentNode) {
- parentEntry = lookup.get(parentNode);
- if (parentEntry) {
- if (!parentEntry.processed) {
- parentEntry = processNode(parentEntry);
- }
- break;
- }
- parentNode = parentNode.parentNode;
- }
-
- (parentEntry || tree).children.push(entry);
- return entry;
- }
-
- function flatten(tree) {
- var result = [];
- var queue = [];
- var i;
-
- for (i = 0; i < tree.children.length; i++) {
- queue.push(tree.children[i]);
- }
-
- var remainingLevelEntries = queue.length;
- var nextLevelEntries = 0;
- var row = [];
-
- for (i = 0; i < queue.length; i++) {
- var entry = queue[i];
- if (remainingLevelEntries <= 0) {
- remainingLevelEntries = nextLevelEntries;
- nextLevelEntries = 0;
- result.push(row);
- row = [];
- }
- row.push(entry.fn);
- entry.children.forEach(function(childEntry) {
- nextLevelEntries++;
- queue.push(childEntry);
- });
- remainingLevelEntries--;
- }
-
- if (row.length) {
- result.push(row);
- }
-
- return result;
- }
- }
-
- // TODO(matsko): document the signature in a better way
- return function(element, event, options) {
- options = prepareAnimationOptions(options);
- var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0;
-
- // there is no animation at the current moment, however
- // these runner methods will get later updated with the
- // methods leading into the driver's end/cancel methods
- // for now they just stop the animation from starting
- var runner = new $$AnimateRunner({
- end: function() { close(); },
- cancel: function() { close(true); }
- });
-
- if (!drivers.length) {
- close();
- return runner;
- }
-
- setRunner(element, runner);
-
- var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass));
- var tempClasses = options.tempClasses;
- if (tempClasses) {
- classes += ' ' + tempClasses;
- options.tempClasses = null;
- }
-
- var prepareClassName;
- if (isStructural) {
- prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX;
- $$jqLite.addClass(element, prepareClassName);
- }
-
- animationQueue.push({
- // this data is used by the postDigest code and passed into
- // the driver step function
- element: element,
- classes: classes,
- event: event,
- structural: isStructural,
- options: options,
- beforeStart: beforeStart,
- close: close
- });
-
- element.on('$destroy', handleDestroyedElement);
-
- // we only want there to be one function called within the post digest
- // block. This way we can group animations for all the animations that
- // were apart of the same postDigest flush call.
- if (animationQueue.length > 1) return runner;
-
- $rootScope.$$postDigest(function() {
- var animations = [];
- forEach(animationQueue, function(entry) {
- // the element was destroyed early on which removed the runner
- // form its storage. This means we can't animate this element
- // at all and it already has been closed due to destruction.
- if (getRunner(entry.element)) {
- animations.push(entry);
- } else {
- entry.close();
- }
- });
-
- // now any future animations will be in another postDigest
- animationQueue.length = 0;
-
- var groupedAnimations = groupAnimations(animations);
- var toBeSortedAnimations = [];
-
- forEach(groupedAnimations, function(animationEntry) {
- toBeSortedAnimations.push({
- domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element),
- fn: function triggerAnimationStart() {
- // it's important that we apply the `ng-animate` CSS class and the
- // temporary classes before we do any driver invoking since these
- // CSS classes may be required for proper CSS detection.
- animationEntry.beforeStart();
-
- var startAnimationFn, closeFn = animationEntry.close;
-
- // in the event that the element was removed before the digest runs or
- // during the RAF sequencing then we should not trigger the animation.
- var targetElement = animationEntry.anchors
- ? (animationEntry.from.element || animationEntry.to.element)
- : animationEntry.element;
-
- if (getRunner(targetElement)) {
- var operation = invokeFirstDriver(animationEntry);
- if (operation) {
- startAnimationFn = operation.start;
- }
- }
-
- if (!startAnimationFn) {
- closeFn();
- } else {
- var animationRunner = startAnimationFn();
- animationRunner.done(function(status) {
- closeFn(!status);
- });
- updateAnimationRunners(animationEntry, animationRunner);
- }
- }
- });
- });
-
- // we need to sort each of the animations in order of parent to child
- // relationships. This ensures that the child classes are applied at the
- // right time.
- $$rAFScheduler(sortAnimations(toBeSortedAnimations));
- });
-
- return runner;
-
- // TODO(matsko): change to reference nodes
- function getAnchorNodes(node) {
- var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']';
- var items = node.hasAttribute(NG_ANIMATE_REF_ATTR)
- ? [node]
- : node.querySelectorAll(SELECTOR);
- var anchors = [];
- forEach(items, function(node) {
- var attr = node.getAttribute(NG_ANIMATE_REF_ATTR);
- if (attr && attr.length) {
- anchors.push(node);
- }
- });
- return anchors;
- }
-
- function groupAnimations(animations) {
- var preparedAnimations = [];
- var refLookup = {};
- forEach(animations, function(animation, index) {
- var element = animation.element;
- var node = getDomNode(element);
- var event = animation.event;
- var enterOrMove = ['enter', 'move'].indexOf(event) >= 0;
- var anchorNodes = animation.structural ? getAnchorNodes(node) : [];
-
- if (anchorNodes.length) {
- var direction = enterOrMove ? 'to' : 'from';
-
- forEach(anchorNodes, function(anchor) {
- var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR);
- refLookup[key] = refLookup[key] || {};
- refLookup[key][direction] = {
- animationID: index,
- element: jqLite(anchor)
- };
- });
- } else {
- preparedAnimations.push(animation);
- }
- });
-
- var usedIndicesLookup = {};
- var anchorGroups = {};
- forEach(refLookup, function(operations, key) {
- var from = operations.from;
- var to = operations.to;
-
- if (!from || !to) {
- // only one of these is set therefore we can't have an
- // anchor animation since all three pieces are required
- var index = from ? from.animationID : to.animationID;
- var indexKey = index.toString();
- if (!usedIndicesLookup[indexKey]) {
- usedIndicesLookup[indexKey] = true;
- preparedAnimations.push(animations[index]);
- }
- return;
- }
-
- var fromAnimation = animations[from.animationID];
- var toAnimation = animations[to.animationID];
- var lookupKey = from.animationID.toString();
- if (!anchorGroups[lookupKey]) {
- var group = anchorGroups[lookupKey] = {
- structural: true,
- beforeStart: function() {
- fromAnimation.beforeStart();
- toAnimation.beforeStart();
- },
- close: function() {
- fromAnimation.close();
- toAnimation.close();
- },
- classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes),
- from: fromAnimation,
- to: toAnimation,
- anchors: [] // TODO(matsko): change to reference nodes
- };
-
- // the anchor animations require that the from and to elements both have at least
- // one shared CSS class which effectively marries the two elements together to use
- // the same animation driver and to properly sequence the anchor animation.
- if (group.classes.length) {
- preparedAnimations.push(group);
- } else {
- preparedAnimations.push(fromAnimation);
- preparedAnimations.push(toAnimation);
- }
- }
-
- anchorGroups[lookupKey].anchors.push({
- 'out': from.element, 'in': to.element
- });
- });
-
- return preparedAnimations;
- }
-
- function cssClassesIntersection(a,b) {
- a = a.split(' ');
- b = b.split(' ');
- var matches = [];
-
- for (var i = 0; i < a.length; i++) {
- var aa = a[i];
- if (aa.substring(0,3) === 'ng-') continue;
-
- for (var j = 0; j < b.length; j++) {
- if (aa === b[j]) {
- matches.push(aa);
- break;
- }
- }
- }
-
- return matches.join(' ');
- }
-
- function invokeFirstDriver(animationDetails) {
- // we loop in reverse order since the more general drivers (like CSS and JS)
- // may attempt more elements, but custom drivers are more particular
- for (var i = drivers.length - 1; i >= 0; i--) {
- var driverName = drivers[i];
- if (!$injector.has(driverName)) continue; // TODO(matsko): remove this check
-
- var factory = $injector.get(driverName);
- var driver = factory(animationDetails);
- if (driver) {
- return driver;
- }
- }
- }
-
- function beforeStart() {
- element.addClass(NG_ANIMATE_CLASSNAME);
- if (tempClasses) {
- $$jqLite.addClass(element, tempClasses);
- }
- if (prepareClassName) {
- $$jqLite.removeClass(element, prepareClassName);
- prepareClassName = null;
- }
- }
-
- function updateAnimationRunners(animation, newRunner) {
- if (animation.from && animation.to) {
- update(animation.from.element);
- update(animation.to.element);
- } else {
- update(animation.element);
- }
-
- function update(element) {
- getRunner(element).setHost(newRunner);
- }
- }
-
- function handleDestroyedElement() {
- var runner = getRunner(element);
- if (runner && (event !== 'leave' || !options.$$domOperationFired)) {
- runner.end();
- }
- }
-
- function close(rejected) { // jshint ignore:line
- element.off('$destroy', handleDestroyedElement);
- removeRunner(element);
-
- applyAnimationClasses(element, options);
- applyAnimationStyles(element, options);
- options.domOperation();
-
- if (tempClasses) {
- $$jqLite.removeClass(element, tempClasses);
- }
-
- element.removeClass(NG_ANIMATE_CLASSNAME);
- runner.complete(!rejected);
- }
- };
- }];
-}];
-
-/**
- * @ngdoc directive
- * @name ngAnimateSwap
- * @restrict A
- * @scope
- *
- * @description
- *
- * ngAnimateSwap is a animation-oriented directive that allows for the container to
- * be removed and entered in whenever the associated expression changes. A
- * common usecase for this directive is a rotating banner or slider component which
- * contains one image being present at a time. When the active image changes
- * then the old image will perform a `leave` animation and the new element
- * will be inserted via an `enter` animation.
- *
- * @animations
- * | Animation | Occurs |
- * |----------------------------------|--------------------------------------|
- * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM |
- * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM |
- *
- * @example
- *
- *
- *
- *
- * {{ number }}
- *
- *
- *
- *
- * angular.module('ngAnimateSwapExample', ['ngAnimate'])
- * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) {
- * $scope.number = 0;
- * $interval(function() {
- * $scope.number++;
- * }, 1000);
- *
- * var colors = ['red','blue','green','yellow','orange'];
- * $scope.colorClass = function(number) {
- * return colors[number % colors.length];
- * };
- * }]);
- *
- *
- * .container {
- * height:250px;
- * width:250px;
- * position:relative;
- * overflow:hidden;
- * border:2px solid black;
- * }
- * .container .cell {
- * font-size:150px;
- * text-align:center;
- * line-height:250px;
- * position:absolute;
- * top:0;
- * left:0;
- * right:0;
- * border-bottom:2px solid black;
- * }
- * .swap-animation.ng-enter, .swap-animation.ng-leave {
- * transition:0.5s linear all;
- * }
- * .swap-animation.ng-enter {
- * top:-250px;
- * }
- * .swap-animation.ng-enter-active {
- * top:0px;
- * }
- * .swap-animation.ng-leave {
- * top:0px;
- * }
- * .swap-animation.ng-leave-active {
- * top:250px;
- * }
- * .red { background:red; }
- * .green { background:green; }
- * .blue { background:blue; }
- * .yellow { background:yellow; }
- * .orange { background:orange; }
- *
- *
- */
-var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) {
- return {
- restrict: 'A',
- transclude: 'element',
- terminal: true,
- priority: 600, // we use 600 here to ensure that the directive is caught before others
- link: function(scope, $element, attrs, ctrl, $transclude) {
- var previousElement, previousScope;
- scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) {
- if (previousElement) {
- $animate.leave(previousElement);
- }
- if (previousScope) {
- previousScope.$destroy();
- previousScope = null;
- }
- if (value || value === 0) {
- previousScope = scope.$new();
- $transclude(previousScope, function(element) {
- previousElement = element;
- $animate.enter(element, null, $element);
- });
- }
- });
- }
- };
-}];
-
-/* global angularAnimateModule: true,
-
- ngAnimateSwapDirective,
- $$AnimateAsyncRunFactory,
- $$rAFSchedulerFactory,
- $$AnimateChildrenDirective,
- $$AnimateQueueProvider,
- $$AnimationProvider,
- $AnimateCssProvider,
- $$AnimateCssDriverProvider,
- $$AnimateJsProvider,
- $$AnimateJsDriverProvider,
-*/
-
-/**
- * @ngdoc module
- * @name ngAnimate
- * @description
- *
- * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via
- * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app.
- *
- *
- *
- * # Usage
- * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based
- * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For
- * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within
- * the HTML element that the animation will be triggered on.
- *
- * ## Directive Support
- * The following directives are "animation aware":
- *
- * | Directive | Supported Animations |
- * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------|
- * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move |
- * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave |
- * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave |
- * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave |
- * | {@link ng.directive:ngIf#animations ngIf} | enter and leave |
- * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) |
- * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) |
- * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) |
- * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) |
- * | {@link module:ngMessages#animations ngMessage} | enter and leave |
- *
- * (More information can be found by visiting each the documentation associated with each directive.)
- *
- * ## CSS-based Animations
- *
- * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML
- * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation.
- *
- * The example below shows how an `enter` animation can be made possible on an element using `ng-if`:
- *
- * ```html
- *
- * Fade me in out
- *
- *
- *
- * ```
- *
- * Notice the CSS class **fade**? We can now create the CSS transition code that references this class:
- *
- * ```css
- * /* The starting CSS styles for the enter animation */
- * .fade.ng-enter {
- * transition:0.5s linear all;
- * opacity:0;
- * }
- *
- * /* The finishing CSS styles for the enter animation */
- * .fade.ng-enter.ng-enter-active {
- * opacity:1;
- * }
- * ```
- *
- * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two
- * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition
- * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards.
- *
- * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions:
- *
- * ```css
- * /* now the element will fade out before it is removed from the DOM */
- * .fade.ng-leave {
- * transition:0.5s linear all;
- * opacity:1;
- * }
- * .fade.ng-leave.ng-leave-active {
- * opacity:0;
- * }
- * ```
- *
- * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class:
- *
- * ```css
- * /* there is no need to define anything inside of the destination
- * CSS class since the keyframe will take charge of the animation */
- * .fade.ng-leave {
- * animation: my_fade_animation 0.5s linear;
- * -webkit-animation: my_fade_animation 0.5s linear;
- * }
- *
- * @keyframes my_fade_animation {
- * from { opacity:1; }
- * to { opacity:0; }
- * }
- *
- * @-webkit-keyframes my_fade_animation {
- * from { opacity:1; }
- * to { opacity:0; }
- * }
- * ```
- *
- * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element.
- *
- * ### CSS Class-based Animations
- *
- * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different
- * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added
- * and removed.
- *
- * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class:
- *
- * ```html
- *
- * Show and hide me
- *
- *
- *
- *
- * ```
- *
- * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since
- * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest.
- *
- * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation
- * with CSS styles.
- *
- * ```html
- *
- * Highlight this box
- *
- *
- *
- *
- * ```
- *
- * We can also make use of CSS keyframes by placing them within the CSS classes.
- *
- *
- * ### CSS Staggering Animations
- * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a
- * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be
- * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for
- * the animation. The style property expected within the stagger class can either be a **transition-delay** or an
- * **animation-delay** property (or both if your animation contains both transitions and keyframe animations).
- *
- * ```css
- * .my-animation.ng-enter {
- * /* standard transition code */
- * transition: 1s linear all;
- * opacity:0;
- * }
- * .my-animation.ng-enter-stagger {
- * /* this will have a 100ms delay between each successive leave animation */
- * transition-delay: 0.1s;
- *
- * /* As of 1.4.4, this must always be set: it signals ngAnimate
- * to not accidentally inherit a delay property from another CSS class */
- * transition-duration: 0s;
- * }
- * .my-animation.ng-enter.ng-enter-active {
- * /* standard transition styles */
- * opacity:1;
- * }
- * ```
- *
- * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations
- * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this
- * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation
- * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired.
- *
- * The following code will issue the **ng-leave-stagger** event on the element provided:
- *
- * ```js
- * var kids = parent.children();
- *
- * $animate.leave(kids[0]); //stagger index=0
- * $animate.leave(kids[1]); //stagger index=1
- * $animate.leave(kids[2]); //stagger index=2
- * $animate.leave(kids[3]); //stagger index=3
- * $animate.leave(kids[4]); //stagger index=4
- *
- * window.requestAnimationFrame(function() {
- * //stagger has reset itself
- * $animate.leave(kids[5]); //stagger index=0
- * $animate.leave(kids[6]); //stagger index=1
- *
- * $scope.$digest();
- * });
- * ```
- *
- * Stagger animations are currently only supported within CSS-defined animations.
- *
- * ### The `ng-animate` CSS class
- *
- * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation.
- * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations).
- *
- * Therefore, animations can be applied to an element using this temporary class directly via CSS.
- *
- * ```css
- * .zipper.ng-animate {
- * transition:0.5s linear all;
- * }
- * .zipper.ng-enter {
- * opacity:0;
- * }
- * .zipper.ng-enter.ng-enter-active {
- * opacity:1;
- * }
- * .zipper.ng-leave {
- * opacity:1;
- * }
- * .zipper.ng-leave.ng-leave-active {
- * opacity:0;
- * }
- * ```
- *
- * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove
- * the CSS class once an animation has completed.)
- *
- *
- * ### The `ng-[event]-prepare` class
- *
- * This is a special class that can be used to prevent unwanted flickering / flash of content before
- * the actual animation starts. The class is added as soon as an animation is initialized, but removed
- * before the actual animation starts (after waiting for a $digest).
- * It is also only added for *structural* animations (`enter`, `move`, and `leave`).
- *
- * In practice, flickering can appear when nesting elements with structural animations such as `ngIf`
- * into elements that have class-based animations such as `ngClass`.
- *
- * ```html
- *
- *
- *
- *
- *
- * ```
- *
- * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating.
- * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts:
- *
- * ```css
- * .message.ng-enter-prepare {
- * opacity: 0;
- * }
- *
- * ```
- *
- * ## JavaScript-based Animations
- *
- * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared
- * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the
- * `module.animation()` module function we can register the animation.
- *
- * Let's see an example of a enter/leave animation using `ngRepeat`:
- *
- * ```html
- *
- * {{ item }}
- *
- * ```
- *
- * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`:
- *
- * ```js
- * myModule.animation('.slide', [function() {
- * return {
- * // make note that other events (like addClass/removeClass)
- * // have different function input parameters
- * enter: function(element, doneFn) {
- * jQuery(element).fadeIn(1000, doneFn);
- *
- * // remember to call doneFn so that angular
- * // knows that the animation has concluded
- * },
- *
- * move: function(element, doneFn) {
- * jQuery(element).fadeIn(1000, doneFn);
- * },
- *
- * leave: function(element, doneFn) {
- * jQuery(element).fadeOut(1000, doneFn);
- * }
- * }
- * }]);
- * ```
- *
- * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as
- * greensock.js and velocity.js.
- *
- * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define
- * our animations inside of the same registered animation, however, the function input arguments are a bit different:
- *
- * ```html
- *
- * this box is moody
- *
- *
- *
- *
- * ```
- *
- * ```js
- * myModule.animation('.colorful', [function() {
- * return {
- * addClass: function(element, className, doneFn) {
- * // do some cool animation and call the doneFn
- * },
- * removeClass: function(element, className, doneFn) {
- * // do some cool animation and call the doneFn
- * },
- * setClass: function(element, addedClass, removedClass, doneFn) {
- * // do some cool animation and call the doneFn
- * }
- * }
- * }]);
- * ```
- *
- * ## CSS + JS Animations Together
- *
- * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular,
- * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking
- * charge of the animation**:
- *
- * ```html
- *
- * Slide in and out
- *
- * ```
- *
- * ```js
- * myModule.animation('.slide', [function() {
- * return {
- * enter: function(element, doneFn) {
- * jQuery(element).slideIn(1000, doneFn);
- * }
- * }
- * }]);
- * ```
- *
- * ```css
- * .slide.ng-enter {
- * transition:0.5s linear all;
- * transform:translateY(-100px);
- * }
- * .slide.ng-enter.ng-enter-active {
- * transform:translateY(0);
- * }
- * ```
- *
- * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the
- * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from
- * our own JS-based animation code:
- *
- * ```js
- * myModule.animation('.slide', ['$animateCss', function($animateCss) {
- * return {
- * enter: function(element) {
-* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`.
- * return $animateCss(element, {
- * event: 'enter',
- * structural: true
- * });
- * }
- * }
- * }]);
- * ```
- *
- * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework.
- *
- * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or
- * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that
- * data into `$animateCss` directly:
- *
- * ```js
- * myModule.animation('.slide', ['$animateCss', function($animateCss) {
- * return {
- * enter: function(element) {
- * return $animateCss(element, {
- * event: 'enter',
- * structural: true,
- * addClass: 'maroon-setting',
- * from: { height:0 },
- * to: { height: 200 }
- * });
- * }
- * }
- * }]);
- * ```
- *
- * Now we can fill in the rest via our transition CSS code:
- *
- * ```css
- * /* the transition tells ngAnimate to make the animation happen */
- * .slide.ng-enter { transition:0.5s linear all; }
- *
- * /* this extra CSS class will be absorbed into the transition
- * since the $animateCss code is adding the class */
- * .maroon-setting { background:red; }
- * ```
- *
- * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over.
- *
- * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}.
- *
- * ## Animation Anchoring (via `ng-animate-ref`)
- *
- * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between
- * structural areas of an application (like views) by pairing up elements using an attribute
- * called `ng-animate-ref`.
- *
- * Let's say for example we have two views that are managed by `ng-view` and we want to show
- * that there is a relationship between two components situated in within these views. By using the
- * `ng-animate-ref` attribute we can identify that the two components are paired together and we
- * can then attach an animation, which is triggered when the view changes.
- *
- * Say for example we have the following template code:
- *
- * ```html
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- *
- * ```
- *
- * Now, when the view changes (once the link is clicked), ngAnimate will examine the
- * HTML contents to see if there is a match reference between any components in the view
- * that is leaving and the view that is entering. It will scan both the view which is being
- * removed (leave) and inserted (enter) to see if there are any paired DOM elements that
- * contain a matching ref value.
- *
- * The two images match since they share the same ref value. ngAnimate will now create a
- * transport element (which is a clone of the first image element) and it will then attempt
- * to animate to the position of the second image element in the next view. For the animation to
- * work a special CSS class called `ng-anchor` will be added to the transported element.
- *
- * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then
- * ngAnimate will handle the entire transition for us as well as the addition and removal of
- * any changes of CSS classes between the elements:
- *
- * ```css
- * .banner.ng-anchor {
- * /* this animation will last for 1 second since there are
- * two phases to the animation (an `in` and an `out` phase) */
- * transition:0.5s linear all;
- * }
- * ```
- *
- * We also **must** include animations for the views that are being entered and removed
- * (otherwise anchoring wouldn't be possible since the new view would be inserted right away).
- *
- * ```css
- * .view-animation.ng-enter, .view-animation.ng-leave {
- * transition:0.5s linear all;
- * position:fixed;
- * left:0;
- * top:0;
- * width:100%;
- * }
- * .view-animation.ng-enter {
- * transform:translateX(100%);
- * }
- * .view-animation.ng-leave,
- * .view-animation.ng-enter.ng-enter-active {
- * transform:translateX(0%);
- * }
- * .view-animation.ng-leave.ng-leave-active {
- * transform:translateX(-100%);
- * }
- * ```
- *
- * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur:
- * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away
- * from its origin. Once that animation is over then the `in` stage occurs which animates the
- * element to its destination. The reason why there are two animations is to give enough time
- * for the enter animation on the new element to be ready.
- *
- * The example above sets up a transition for both the in and out phases, but we can also target the out or
- * in phases directly via `ng-anchor-out` and `ng-anchor-in`.
- *
- * ```css
- * .banner.ng-anchor-out {
- * transition: 0.5s linear all;
- *
- * /* the scale will be applied during the out animation,
- * but will be animated away when the in animation runs */
- * transform: scale(1.2);
- * }
- *
- * .banner.ng-anchor-in {
- * transition: 1s linear all;
- * }
- * ```
- *
- *
- *
- *
- * ### Anchoring Demo
- *
-
-
- Home
-
-
-
-
- .record {
- display:block;
- font-size:20px;
- }
- .profile {
- background:black;
- color:white;
- font-size:100px;
- }
- .view-container {
- position:relative;
- }
- .view-container > .view.ng-animate {
- position:absolute;
- top:0;
- left:0;
- width:100%;
- min-height:500px;
- }
- .view.ng-enter, .view.ng-leave,
- .record.ng-anchor {
- transition:0.5s linear all;
- }
- .view.ng-enter {
- transform:translateX(100%);
- }
- .view.ng-enter.ng-enter-active, .view.ng-leave {
- transform:translateX(0%);
- }
- .view.ng-leave.ng-leave-active {
- transform:translateX(-100%);
- }
- .record.ng-anchor-out {
- background:red;
- }
-
-
- *
- * ### How is the element transported?
- *
- * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting
- * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element
- * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The
- * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match
- * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied
- * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class
- * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element
- * will become visible since the shim class will be removed.
- *
- * ### How is the morphing handled?
- *
- * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out
- * what CSS classes differ between the starting element and the destination element. These different CSS classes
- * will be added/removed on the anchor element and a transition will be applied (the transition that is provided
- * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will
- * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that
- * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since
- * the cloned element is placed inside of root element which is likely close to the body element).
- *
- * Note that if the root element is on the `` element then the cloned node will be placed inside of body.
- *
- *
- * ## Using $animate in your directive code
- *
- * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application?
- * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's
- * imagine we have a greeting box that shows and hides itself when the data changes
- *
- * ```html
- * Hi there
- * ```
- *
- * ```js
- * ngModule.directive('greetingBox', ['$animate', function($animate) {
- * return function(scope, element, attrs) {
- * attrs.$observe('active', function(value) {
- * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on');
- * });
- * });
- * }]);
- * ```
- *
- * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element
- * in our HTML code then we can trigger a CSS or JS animation to happen.
- *
- * ```css
- * /* normally we would create a CSS class to reference on the element */
- * greeting-box.on { transition:0.5s linear all; background:green; color:white; }
- * ```
- *
- * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's
- * possible be sure to visit the {@link ng.$animate $animate service API page}.
- *
- *
- * ## Callbacks and Promises
- *
- * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger
- * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has
- * ended by chaining onto the returned promise that animation method returns.
- *
- * ```js
- * // somewhere within the depths of the directive
- * $animate.enter(element, parent).then(function() {
- * //the animation has completed
- * });
- * ```
- *
- * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case
- * anymore.)
- *
- * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering
- * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view
- * routing controller to hook into that:
- *
- * ```js
- * ngModule.controller('HomePageController', ['$animate', function($animate) {
- * $animate.on('enter', ngViewElement, function(element) {
- * // the animation for this route has completed
- * }]);
- * }])
- * ```
- *
- * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.)
- */
-
-/**
- * @ngdoc service
- * @name $animate
- * @kind object
- *
- * @description
- * The ngAnimate `$animate` service documentation is the same for the core `$animate` service.
- *
- * Click here {@link ng.$animate to learn more about animations with `$animate`}.
- */
-angular.module('ngAnimate', [])
- .directive('ngAnimateSwap', ngAnimateSwapDirective)
-
- .directive('ngAnimateChildren', $$AnimateChildrenDirective)
- .factory('$$rAFScheduler', $$rAFSchedulerFactory)
-
- .provider('$$animateQueue', $$AnimateQueueProvider)
- .provider('$$animation', $$AnimationProvider)
-
- .provider('$animateCss', $AnimateCssProvider)
- .provider('$$animateCssDriver', $$AnimateCssDriverProvider)
-
- .provider('$$animateJs', $$AnimateJsProvider)
- .provider('$$animateJsDriver', $$AnimateJsDriverProvider);
-
-
-})(window, window.angular);
diff --git a/app/www/lib/ionic/js/angular/angular-animate.min.js b/app/www/lib/ionic/js/angular/angular-animate.min.js
deleted file mode 100644
index 57108b2..0000000
--- a/app/www/lib/ionic/js/angular/angular-animate.min.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- AngularJS v1.5.3
- (c) 2010-2016 Google, Inc. http://angularjs.org
- License: MIT
-*/
-(function(E,w,Va){'use strict';function ya(a,b,c){if(!a)throw Ka("areq",b||"?",c||"required");return a}function za(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;ba(a)&&(a=a.join(" "));ba(b)&&(b=b.join(" "));return a+" "+b}function La(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function V(a,b,c){var d="";a=ba(a)?a:a&&I(a)&&a.length?a.split(/\s+/):[];q(a,function(a,f){a&&0=a&&(a=h,h=0,b.push(e),e=[]);e.push(f.fn);f.children.forEach(function(a){h++;c.push(a)});a--}e.length&&b.push(e);return b}(c)}var l=[],w=Q(a);return function(s,x,v){function h(a){a=a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];q(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function P(a){var b=[],c={};q(a,function(a,g){var d=A(a.element),k=0<=["enter","move"].indexOf(a.event),d=a.structural?
-h(d):[];if(d.length){var e=k?"to":"from";q(d,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][e]={animationID:g,element:H(a)}})}else b.push(a)});var d={},k={};q(c,function(c,h){var e=c.from,f=c.to;if(e&&f){var m=a[e.animationID],C=a[f.animationID],n=e.animationID.toString();if(!k[n]){var D=k[n]={structural:!0,beforeStart:function(){m.beforeStart();C.beforeStart()},close:function(){m.close();C.close()},classes:y(m.classes,C.classes),from:m,to:C,anchors:[]};D.classes.length?b.push(D):
-(b.push(m),b.push(C))}k[n].anchors.push({out:e.element,"in":f.element})}else e=e?e.animationID:f.animationID,f=e.toString(),d[f]||(d[f]=!0,b.push(a[e]))});return b}function y(a,b){a=a.split(" ");b=b.split(" ");for(var c=[],d=0;d=Q&&b>=L&&(H=!0,n())}function K(){function b(){if(!P){D(!1);q(y,function(a){m.style[a[0]]=a[1]});h(a,g);e.addClass(a,ga);if(p.recalculateTimingStyles){na=m.className+" "+fa;ia=w(m,na);B=v(m,na,ia);$=B.maxDelay;I=Math.max($,0);L=B.maxDuration;if(0===L){n();return}p.hasTransitions=0z.expectedEndTime)?r.cancel(z.timer):f.push(n)}K&&(l=r(c,l,!1),f[0]={timer:l,expectedEndTime:d},f.push(n),a.data("$$animateCss",f));
-if(ea.length)a.on(ea.join(" "),C);g.to&&(g.cleanupStyles&&Ga(G,m,Object.keys(g.to)),Ba(a,g))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d
- *
- * See {@link ngResource.$resource `$resource`} for usage.
- */
-
-/**
- * @ngdoc service
- * @name $resource
- * @requires $http
- * @requires ng.$log
- * @requires $q
- * @requires ng.$timeout
- *
- * @description
- * A factory which creates a resource object that lets you interact with
- * [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
- *
- * The returned resource object has action methods which provide high-level behaviors without
- * the need to interact with the low level {@link ng.$http $http} service.
- *
- * Requires the {@link ngResource `ngResource`} module to be installed.
- *
- * By default, trailing slashes will be stripped from the calculated URLs,
- * which can pose problems with server backends that do not expect that
- * behavior. This can be disabled by configuring the `$resourceProvider` like
- * this:
- *
- * ```js
- app.config(['$resourceProvider', function($resourceProvider) {
- // Don't strip trailing slashes from calculated URLs
- $resourceProvider.defaults.stripTrailingSlashes = false;
- }]);
- * ```
- *
- * @param {string} url A parameterized URL template with parameters prefixed by `:` as in
- * `/user/:username`. If you are using a URL with a port number (e.g.
- * `http://example.com:8080/api`), it will be respected.
- *
- * If you are using a url with a suffix, just add the suffix, like this:
- * `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')`
- * or even `$resource('http://example.com/resource/:resource_id.:format')`
- * If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be
- * collapsed down to a single `.`. If you need this sequence to appear and not collapse then you
- * can escape it with `/\.`.
- *
- * @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in
- * `actions` methods. If a parameter value is a function, it will be executed every time
- * when a param value needs to be obtained for a request (unless the param was overridden).
- *
- * Each key value in the parameter object is first bound to url template if present and then any
- * excess keys are appended to the url search query after the `?`.
- *
- * Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in
- * URL `/path/greet?salutation=Hello`.
- *
- * If the parameter value is prefixed with `@` then the value for that parameter will be extracted
- * from the corresponding property on the `data` object (provided when calling an action method).
- * For example, if the `defaultParam` object is `{someParam: '@someProp'}` then the value of
- * `someParam` will be `data.someProp`.
- *
- * @param {Object.