to learn more about them.
* You can ensure your document is in standards mode and not quirks mode by adding ``
@@ -22678,10 +26008,10 @@ function $SceDelegateProvider() {
*
* Here's an example of a binding in a privileged context:
*
- *
- *
- *
- *
+ * ```
+ *
+ *
+ * ```
*
* Notice that `ng-bind-html` is bound to `userHtml` controlled by the user. With SCE
* disabled, this application allows the user to render arbitrary HTML into the DIV.
@@ -22705,31 +26035,31 @@ function $SceDelegateProvider() {
* allowing only the files in a specific directory to do this. Ensuring that the internal API
* exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
*
- * In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs}
- * (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to
+ * In the case of AngularJS' SCE service, one uses {@link ng.$sce#trustAs $sce.trustAs}
+ * (and shorthand methods such as {@link ng.$sce#trustAsHtml $sce.trustAsHtml}, etc.) to
* obtain values that will be accepted by SCE / privileged contexts.
*
*
* ## How does it work?
*
- * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#methods_getTrusted
+ * In privileged contexts, directives and code will bind to the result of {@link ng.$sce#getTrusted
* $sce.getTrusted(context, value)} rather than to the value directly. Directives use {@link
- * ng.$sce#methods_parse $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
- * {@link ng.$sce#methods_getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
+ * ng.$sce#parseAs $sce.parseAs} rather than `$parse` to watch attribute bindings, which performs the
+ * {@link ng.$sce#getTrusted $sce.getTrusted} behind the scenes on non-constant literals.
*
* As an example, {@link ng.directive:ngBindHtml ngBindHtml} uses {@link
- * ng.$sce#methods_parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
+ * ng.$sce#parseAsHtml $sce.parseAsHtml(binding expression)}. Here's the actual code (slightly
* simplified):
*
- *
- * var ngBindHtmlDirective = ['$sce', function($sce) {
- * return function(scope, element, attr) {
- * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
- * element.html(value || '');
- * });
- * };
- * }];
- *
+ * ```
+ * var ngBindHtmlDirective = ['$sce', function($sce) {
+ * return function(scope, element, attr) {
+ * scope.$watch($sce.parseAsHtml(attr.ngBindHtml), function(value) {
+ * element.html(value || '');
+ * });
+ * };
+ * }];
+ * ```
*
* ## Impact on loading templates
*
@@ -22737,21 +26067,21 @@ function $SceDelegateProvider() {
* `templateUrl`'s specified by {@link guide/directive directives}.
*
* By default, Angular only loads templates from the same domain and protocol as the application
- * document. This is done by calling {@link ng.$sce#methods_getTrustedResourceUrl
+ * document. This is done by calling {@link ng.$sce#getTrustedResourceUrl
* $sce.getTrustedResourceUrl} on the template URL. To load templates from other domains and/or
- * protocols, you may either either {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelist
- * them} or {@link ng.$sce#methods_trustAsResourceUrl wrap it} into a trusted value.
+ * protocols, you may either either {@link ng.$sceDelegateProvider#resourceUrlWhitelist whitelist
+ * them} or {@link ng.$sce#trustAsResourceUrl wrap it} into a trusted value.
*
* *Please note*:
* The browser's
- * {@link https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest
- * Same Origin Policy} and {@link http://www.w3.org/TR/cors/ Cross-Origin Resource Sharing (CORS)}
+ * [Same Origin Policy](https://code.google.com/p/browsersec/wiki/Part2#Same-origin_policy_for_XMLHttpRequest)
+ * and [Cross-Origin Resource Sharing (CORS)](http://www.w3.org/TR/cors/)
* policy apply in addition to this and may further restrict whether the template is successfully
* loaded. This means that without the right CORS policy, loading templates from a different domain
* won't work on all browsers. Also, loading templates from `file://` URL does not work on some
* browsers.
*
- * ## This feels like too much overhead for the developer?
+ * ## This feels like too much overhead
*
* It's important to remember that SCE only applies to interpolation expressions.
*
@@ -22760,14 +26090,14 @@ function $SceDelegateProvider() {
* `
`) just works.
*
* Additionally, `a[href]` and `img[src]` automatically sanitize their URLs and do not pass them
- * through {@link ng.$sce#methods_getTrusted $sce.getTrusted}. SCE doesn't play a role here.
+ * through {@link ng.$sce#getTrusted $sce.getTrusted}. SCE doesn't play a role here.
*
* The included {@link ng.$sceDelegate $sceDelegate} comes with sane defaults to allow you to load
* templates in `ng-include` from your application's domain without having to even know about SCE.
* It blocks loading templates from other domains or loading templates over http from an https
* served document. You can change these by setting your own custom {@link
- * ng.$sceDelegateProvider#methods_resourceUrlWhitelist whitelists} and {@link
- * ng.$sceDelegateProvider#methods_resourceUrlBlacklist blacklists} for matching such URLs.
+ * ng.$sceDelegateProvider#resourceUrlWhitelist whitelists} and {@link
+ * ng.$sceDelegateProvider#resourceUrlBlacklist blacklists} for matching such URLs.
*
* This significantly reduces the overhead. It is far easier to pay the small overhead and have an
* application that's secure and can be audited to verify that with much more ease than bolting
@@ -22778,13 +26108,13 @@ function $SceDelegateProvider() {
*
* | Context | Notes |
* |---------------------|----------------|
- * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. |
+ * | `$sce.HTML` | For HTML that's safe to source into the application. The {@link ng.directive:ngBindHtml ngBindHtml} directive uses this context for bindings. If an unsafe value is encountered and the {@link ngSanitize $sanitize} module is present this will sanitize the value instead of throwing an error. |
* | `$sce.CSS` | For CSS that's safe to source into the application. Currently unused. Feel free to use it in your own directives. |
- * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
+ * | `$sce.URL` | For URLs that are safe to follow as links. Currently unused (`
Note that `$sce.RESOURCE_URL` makes a stronger statement about the URL than `$sce.URL` does and therefore contexts requiring values trusted for `$sce.RESOURCE_URL` can be used anywhere that values trusted for `$sce.URL` are required. |
* | `$sce.JS` | For JavaScript that is safe to execute in your application's context. Currently unused. Feel free to use it in your own directives. |
*
- * ## Format of items in {@link ng.$sceDelegateProvider#methods_resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#methods_resourceUrlBlacklist Blacklist}
+ * ## Format of items in {@link ng.$sceDelegateProvider#resourceUrlWhitelist resourceUrlWhitelist}/{@link ng.$sceDelegateProvider#resourceUrlBlacklist Blacklist}
*
* Each element in these arrays must be one of the following:
*
@@ -22796,23 +26126,23 @@ function $SceDelegateProvider() {
* being tested (substring matches are not good enough.)
* - There are exactly **two wildcard sequences** - `*` and `**`. All other characters
* match themselves.
- * - `*`: matches zero or more occurances of any character other than one of the following 6
- * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
+ * - `*`: matches zero or more occurrences of any character other than one of the following 6
+ * characters: '`:`', '`/`', '`.`', '`?`', '`&`' and '`;`'. It's a useful wildcard for use
* in a whitelist.
- * - `**`: matches zero or more occurances of *any* character. As such, it's not
- * not appropriate to use in for a scheme, domain, etc. as it would match too much. (e.g.
+ * - `**`: matches zero or more occurrences of *any* character. As such, it's not
+ * appropriate for use in a scheme, domain, etc. as it would match too much. (e.g.
* http://**.example.com/ would match http://evil.com/?ignore=.example.com/ and that might
- * not have been the intention.) It's usage at the very end of the path is ok. (e.g.
+ * not have been the intention.) Its usage at the very end of the path is ok. (e.g.
* http://foo.example.com/templates/**).
* - **RegExp** (*see caveat below*)
* - *Caveat*: While regular expressions are powerful and offer great flexibility, their syntax
* (and all the inevitable escaping) makes them *harder to maintain*. It's easy to
* accidentally introduce a bug when one updates a complex expression (imho, all regexes should
- * have good test coverage.). For instance, the use of `.` in the regex is correct only in a
+ * have good test coverage). For instance, the use of `.` in the regex is correct only in a
* small number of cases. A `.` character in the regex used when matching the scheme or a
* subdomain could be matched against a `:` or literal `.` that was likely not intended. It
* is highly recommended to use the string patterns and only fall back to regular expressions
- * if they as a last resort.
+ * as a last resort.
* - The regular expression must be an instance of RegExp (i.e. not a string.) It is
* matched against the **entire** *normalized / absolute URL* of the resource being tested
* (even when the RegExp did not have the `^` and `$` codes.) In addition, any flags
@@ -22822,7 +26152,7 @@ function $SceDelegateProvider() {
* remember to escape your regular expression (and be aware that you might need more than
* one level of escaping depending on your templating engine and the way you interpolated
* the value.) Do make use of your platform's escaping mechanism as it might be good
- * enough before coding your own. e.g. Ruby has
+ * enough before coding your own. E.g. Ruby has
* [Regexp.escape(str)](http://www.ruby-doc.org/core-2.0.0/Regexp.html#method-c-escape)
* and Python has [re.escape](http://docs.python.org/library/re.html#re.escape).
* Javascript lacks a similar built in function for escaping. Take a look at Google
@@ -22833,66 +26163,65 @@ function $SceDelegateProvider() {
*
* ## Show me an example using SCE.
*
- * @example
-
-
-
-
-
User comments
- By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
- $sanitize is available. If $sanitize isn't available, this results in an error instead of an
- exploit.
-
-
- {{userComment.name}}:
-
-
-
-
-
-
-
-
- var mySceApp = angular.module('mySceApp', ['ngSanitize']);
-
- mySceApp.controller("myAppController", function myAppController($http, $templateCache, $sce) {
- var self = this;
- $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
- self.userComments = userComments;
- });
- self.explicitlyTrustedHtml = $sce.trustAsHtml(
- 'Hover over this text.');
- });
-
-
-
-[
- { "name": "Alice",
- "htmlComment":
- "Is anyone reading this?"
- },
- { "name": "Bob",
- "htmlComment": "Yes! Am I the only other one?"
- }
-]
-
-
-
- describe('SCE doc demo', function() {
- it('should sanitize untrusted values', function() {
- expect(element(by.css('.htmlComment')).getInnerHtml())
- .toBe('Is anyone reading this?');
- });
-
- it('should NOT sanitize explicitly trusted values', function() {
- expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
- 'Hover over this text.');
- });
- });
-
-
+ *
+ *
+ *
+ *
+ *
User comments
+ * By default, HTML that isn't explicitly trusted (e.g. Alice's comment) is sanitized when
+ * $sanitize is available. If $sanitize isn't available, this results in an error instead of an
+ * exploit.
+ *
+ *
+ * {{userComment.name}}:
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ *
+ * angular.module('mySceApp', ['ngSanitize'])
+ * .controller('AppController', ['$http', '$templateCache', '$sce',
+ * function($http, $templateCache, $sce) {
+ * var self = this;
+ * $http.get("test_data.json", {cache: $templateCache}).success(function(userComments) {
+ * self.userComments = userComments;
+ * });
+ * self.explicitlyTrustedHtml = $sce.trustAsHtml(
+ * 'Hover over this text.');
+ * }]);
+ *
+ *
+ *
+ * [
+ * { "name": "Alice",
+ * "htmlComment":
+ * "Is anyone reading this?"
+ * },
+ * { "name": "Bob",
+ * "htmlComment": "Yes! Am I the only other one?"
+ * }
+ * ]
+ *
+ *
+ *
+ * describe('SCE doc demo', function() {
+ * it('should sanitize untrusted values', function() {
+ * expect(element.all(by.css('.htmlComment')).first().getInnerHtml())
+ * .toBe('Is anyone reading this?');
+ * });
+ *
+ * it('should NOT sanitize explicitly trusted values', function() {
+ * expect(element(by.id('explicitlyTrustedHtml')).getInnerHtml()).toBe(
+ * 'Hover over this text.');
+ * });
+ * });
+ *
+ *
*
*
*
@@ -22906,13 +26235,13 @@ function $SceDelegateProvider() {
*
* That said, here's how you can completely disable SCE:
*
- *
- * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
- * // Completely disable SCE. For demonstration purposes only!
- * // Do not use in new projects.
- * $sceProvider.enabled(false);
- * });
- *
+ * ```
+ * angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
+ * // Completely disable SCE. For demonstration purposes only!
+ * // Do not use in new projects.
+ * $sceProvider.enabled(false);
+ * });
+ * ```
*
*/
/* jshint maxlen: 100 */
@@ -22921,10 +26250,9 @@ function $SceProvider() {
var enabled = true;
/**
- * @ngdoc function
- * @name ng.sceProvider#enabled
- * @methodOf ng.$sceProvider
- * @function
+ * @ngdoc method
+ * @name $sceProvider#enabled
+ * @kind function
*
* @param {boolean=} value If provided, then enables/disables SCE.
* @return {boolean} true if SCE is enabled, false otherwise.
@@ -22932,7 +26260,7 @@ function $SceProvider() {
* @description
* Enables/disables SCE and returns the current value.
*/
- this.enabled = function (value) {
+ this.enabled = function(value) {
if (arguments.length) {
enabled = !!value;
}
@@ -22986,24 +26314,23 @@ function $SceProvider() {
* sce.js and sceSpecs.js would need to be aware of this detail.
*/
- this.$get = ['$parse', '$sniffer', '$sceDelegate', function(
- $parse, $sniffer, $sceDelegate) {
- // Prereq: Ensure that we're not running in IE8 quirks mode. In that mode, IE allows
+ this.$get = ['$parse', '$sceDelegate', function(
+ $parse, $sceDelegate) {
+ // Prereq: Ensure that we're not running in IE<11 quirks mode. In that mode, IE < 11 allow
// the "expression(javascript expression)" syntax which is insecure.
- if (enabled && $sniffer.msie && $sniffer.msieDocumentMode < 8) {
+ if (enabled && msie < 8) {
throw $sceMinErr('iequirks',
- 'Strict Contextual Escaping does not support Internet Explorer version < 9 in quirks ' +
+ 'Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks ' +
'mode. You can fix this by adding the text to the top of your HTML ' +
'document. See http://docs.angularjs.org/api/ng.$sce for more information.');
}
- var sce = copy(SCE_CONTEXTS);
+ var sce = shallowCopy(SCE_CONTEXTS);
/**
- * @ngdoc function
- * @name ng.sce#isEnabled
- * @methodOf ng.$sce
- * @function
+ * @ngdoc method
+ * @name $sce#isEnabled
+ * @kind function
*
* @return {Boolean} true if SCE is enabled, false otherwise. If you want to set the value, you
* have to do it at module config time on {@link ng.$sceProvider $sceProvider}.
@@ -23011,7 +26338,7 @@ function $SceProvider() {
* @description
* Returns a boolean indicating if SCE is enabled.
*/
- sce.isEnabled = function () {
+ sce.isEnabled = function() {
return enabled;
};
sce.trustAs = $sceDelegate.trustAs;
@@ -23025,13 +26352,12 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parse
- * @methodOf ng.$sce
+ * @name $sce#parseAs
*
* @description
* Converts Angular {@link guide/expression expression} into a function. This is like {@link
* ng.$parse $parse} and is identical when the expression is a literal constant. Otherwise, it
- * wraps the expression in a call to {@link ng.$sce#methods_getTrusted $sce.getTrusted(*type*,
+ * wraps the expression in a call to {@link ng.$sce#getTrusted $sce.getTrusted(*type*,
* *result*)}
*
* @param {string} type The kind of SCE context in which this result will be used.
@@ -23048,19 +26374,18 @@ function $SceProvider() {
if (parsed.literal && parsed.constant) {
return parsed;
} else {
- return function sceParseAsTrusted(self, locals) {
- return sce.getTrusted(type, parsed(self, locals));
- };
+ return $parse(expr, function(value) {
+ return sce.getTrusted(type, value);
+ });
}
};
/**
* @ngdoc method
- * @name ng.$sce#trustAs
- * @methodOf ng.$sce
+ * @name $sce#trustAs
*
* @description
- * Delegates to {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs`}. As such,
+ * Delegates to {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs`}. As such,
* returns an object that is trusted by angular for use in specified strict contextual
* escaping contexts (such as ng-bind-html, ng-include, any src attribute
* interpolation, any dom event binding attribute interpolation such as for onclick, etc.)
@@ -23068,7 +26393,7 @@ function $SceProvider() {
* escaping.
*
* @param {string} type The kind of context in which this value is safe for use. e.g. url,
- * resource_url, html, js and css.
+ * resourceUrl, html, js and css.
* @param {*} value The value that that should be considered trusted/safe.
* @returns {*} A value that can be used to stand in for the provided `value` in places
* where Angular expects a $sce.trustAs() return value.
@@ -23076,95 +26401,89 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#trustAsHtml
- * @methodOf ng.$sce
+ * @name $sce#trustAsHtml
*
* @description
* Shorthand method. `$sce.trustAsHtml(value)` →
- * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.HTML, value)`}
*
* @param {*} value The value to trustAs.
- * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedHtml
+ * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedHtml
* $sce.getTrustedHtml(value)} to obtain the original value. (privileged directives
* only accept expressions that are either literal constants or are the
- * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.)
+ * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
*/
/**
* @ngdoc method
- * @name ng.$sce#trustAsUrl
- * @methodOf ng.$sce
+ * @name $sce#trustAsUrl
*
* @description
* Shorthand method. `$sce.trustAsUrl(value)` →
- * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.URL, value)`}
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.URL, value)`}
*
* @param {*} value The value to trustAs.
- * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedUrl
+ * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedUrl
* $sce.getTrustedUrl(value)} to obtain the original value. (privileged directives
* only accept expressions that are either literal constants or are the
- * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.)
+ * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
*/
/**
* @ngdoc method
- * @name ng.$sce#trustAsResourceUrl
- * @methodOf ng.$sce
+ * @name $sce#trustAsResourceUrl
*
* @description
* Shorthand method. `$sce.trustAsResourceUrl(value)` →
- * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.RESOURCE_URL, value)`}
*
* @param {*} value The value to trustAs.
- * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedResourceUrl
+ * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedResourceUrl
* $sce.getTrustedResourceUrl(value)} to obtain the original value. (privileged directives
* only accept expressions that are either literal constants or are the return
- * value of {@link ng.$sce#methods_trustAs $sce.trustAs}.)
+ * value of {@link ng.$sce#trustAs $sce.trustAs}.)
*/
/**
* @ngdoc method
- * @name ng.$sce#trustAsJs
- * @methodOf ng.$sce
+ * @name $sce#trustAsJs
*
* @description
* Shorthand method. `$sce.trustAsJs(value)` →
- * {@link ng.$sceDelegate#methods_trustAs `$sceDelegate.trustAs($sce.JS, value)`}
+ * {@link ng.$sceDelegate#trustAs `$sceDelegate.trustAs($sce.JS, value)`}
*
* @param {*} value The value to trustAs.
- * @returns {*} An object that can be passed to {@link ng.$sce#methods_getTrustedJs
+ * @returns {*} An object that can be passed to {@link ng.$sce#getTrustedJs
* $sce.getTrustedJs(value)} to obtain the original value. (privileged directives
* only accept expressions that are either literal constants or are the
- * return value of {@link ng.$sce#methods_trustAs $sce.trustAs}.)
+ * return value of {@link ng.$sce#trustAs $sce.trustAs}.)
*/
/**
* @ngdoc method
- * @name ng.$sce#getTrusted
- * @methodOf ng.$sce
+ * @name $sce#getTrusted
*
* @description
- * Delegates to {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted`}. As such,
- * takes the result of a {@link ng.$sce#methods_trustAs `$sce.trustAs`}() call and returns the
+ * Delegates to {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted`}. As such,
+ * takes the result of a {@link ng.$sce#trustAs `$sce.trustAs`}() call and returns the
* originally supplied value if the queried context type is a supertype of the created type.
* If this condition isn't satisfied, throws an exception.
*
* @param {string} type The kind of context in which this value is to be used.
- * @param {*} maybeTrusted The result of a prior {@link ng.$sce#methods_trustAs `$sce.trustAs`}
+ * @param {*} maybeTrusted The result of a prior {@link ng.$sce#trustAs `$sce.trustAs`}
* call.
* @returns {*} The value the was originally provided to
- * {@link ng.$sce#methods_trustAs `$sce.trustAs`} if valid in this context.
+ * {@link ng.$sce#trustAs `$sce.trustAs`} if valid in this context.
* Otherwise, throws an exception.
*/
/**
* @ngdoc method
- * @name ng.$sce#getTrustedHtml
- * @methodOf ng.$sce
+ * @name $sce#getTrustedHtml
*
* @description
* Shorthand method. `$sce.getTrustedHtml(value)` →
- * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.HTML, value)`}
*
* @param {*} value The value to pass to `$sce.getTrusted`.
* @returns {*} The return value of `$sce.getTrusted($sce.HTML, value)`
@@ -23172,12 +26491,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#getTrustedCss
- * @methodOf ng.$sce
+ * @name $sce#getTrustedCss
*
* @description
* Shorthand method. `$sce.getTrustedCss(value)` →
- * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.CSS, value)`}
*
* @param {*} value The value to pass to `$sce.getTrusted`.
* @returns {*} The return value of `$sce.getTrusted($sce.CSS, value)`
@@ -23185,12 +26503,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#getTrustedUrl
- * @methodOf ng.$sce
+ * @name $sce#getTrustedUrl
*
* @description
* Shorthand method. `$sce.getTrustedUrl(value)` →
- * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.URL, value)`}
*
* @param {*} value The value to pass to `$sce.getTrusted`.
* @returns {*} The return value of `$sce.getTrusted($sce.URL, value)`
@@ -23198,12 +26515,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#getTrustedResourceUrl
- * @methodOf ng.$sce
+ * @name $sce#getTrustedResourceUrl
*
* @description
* Shorthand method. `$sce.getTrustedResourceUrl(value)` →
- * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.RESOURCE_URL, value)`}
*
* @param {*} value The value to pass to `$sceDelegate.getTrusted`.
* @returns {*} The return value of `$sce.getTrusted($sce.RESOURCE_URL, value)`
@@ -23211,12 +26527,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#getTrustedJs
- * @methodOf ng.$sce
+ * @name $sce#getTrustedJs
*
* @description
* Shorthand method. `$sce.getTrustedJs(value)` →
- * {@link ng.$sceDelegate#methods_getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
+ * {@link ng.$sceDelegate#getTrusted `$sceDelegate.getTrusted($sce.JS, value)`}
*
* @param {*} value The value to pass to `$sce.getTrusted`.
* @returns {*} The return value of `$sce.getTrusted($sce.JS, value)`
@@ -23224,12 +26539,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parseAsHtml
- * @methodOf ng.$sce
+ * @name $sce#parseAsHtml
*
* @description
* Shorthand method. `$sce.parseAsHtml(expression string)` →
- * {@link ng.$sce#methods_parse `$sce.parseAs($sce.HTML, value)`}
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.HTML, value)`}
*
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
@@ -23242,12 +26556,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parseAsCss
- * @methodOf ng.$sce
+ * @name $sce#parseAsCss
*
* @description
* Shorthand method. `$sce.parseAsCss(value)` →
- * {@link ng.$sce#methods_parse `$sce.parseAs($sce.CSS, value)`}
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.CSS, value)`}
*
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
@@ -23260,12 +26573,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parseAsUrl
- * @methodOf ng.$sce
+ * @name $sce#parseAsUrl
*
* @description
* Shorthand method. `$sce.parseAsUrl(value)` →
- * {@link ng.$sce#methods_parse `$sce.parseAs($sce.URL, value)`}
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.URL, value)`}
*
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
@@ -23278,12 +26590,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parseAsResourceUrl
- * @methodOf ng.$sce
+ * @name $sce#parseAsResourceUrl
*
* @description
* Shorthand method. `$sce.parseAsResourceUrl(value)` →
- * {@link ng.$sce#methods_parse `$sce.parseAs($sce.RESOURCE_URL, value)`}
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.RESOURCE_URL, value)`}
*
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
@@ -23296,12 +26607,11 @@ function $SceProvider() {
/**
* @ngdoc method
- * @name ng.$sce#parseAsJs
- * @methodOf ng.$sce
+ * @name $sce#parseAsJs
*
* @description
* Shorthand method. `$sce.parseAsJs(value)` →
- * {@link ng.$sce#methods_parse `$sce.parseAs($sce.JS, value)`}
+ * {@link ng.$sce#parseAs `$sce.parseAs($sce.JS, value)`}
*
* @param {string} expression String expression to compile.
* @returns {function(context, locals)} a function which represents the compiled expression:
@@ -23317,15 +26627,15 @@ function $SceProvider() {
getTrusted = sce.getTrusted,
trustAs = sce.trustAs;
- forEach(SCE_CONTEXTS, function (enumValue, name) {
+ forEach(SCE_CONTEXTS, function(enumValue, name) {
var lName = lowercase(name);
- sce[camelCase("parse_as_" + lName)] = function (expr) {
+ sce[camelCase("parse_as_" + lName)] = function(expr) {
return parse(enumValue, expr);
};
- sce[camelCase("get_trusted_" + lName)] = function (value) {
+ sce[camelCase("get_trusted_" + lName)] = function(value) {
return getTrusted(enumValue, value);
};
- sce[camelCase("trust_as_" + lName)] = function (value) {
+ sce[camelCase("trust_as_" + lName)] = function(value) {
return trustAs(enumValue, value);
};
});
@@ -23337,12 +26647,11 @@ function $SceProvider() {
/**
* !!! This is an undocumented "private" service !!!
*
- * @name ng.$sniffer
+ * @name $sniffer
* @requires $window
* @requires $document
*
* @property {boolean} history Does the browser support html5 history api ?
- * @property {boolean} hashchange Does the browser support hashchange event ?
* @property {boolean} transitions Does the browser support CSS transition events ?
* @property {boolean} animations Does the browser support CSS animation events ?
*
@@ -23353,36 +26662,35 @@ function $SnifferProvider() {
this.$get = ['$window', '$document', function($window, $document) {
var eventSupport = {},
android =
- int((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
+ toInt((/android (\d+)/.exec(lowercase(($window.navigator || {}).userAgent)) || [])[1]),
boxee = /Boxee/i.test(($window.navigator || {}).userAgent),
document = $document[0] || {},
- documentMode = document.documentMode,
vendorPrefix,
- vendorRegex = /^(Moz|webkit|O|ms)(?=[A-Z])/,
+ vendorRegex = /^(Moz|webkit|ms)(?=[A-Z])/,
bodyStyle = document.body && document.body.style,
transitions = false,
animations = false,
match;
if (bodyStyle) {
- for(var prop in bodyStyle) {
- if(match = vendorRegex.exec(prop)) {
+ for (var prop in bodyStyle) {
+ if (match = vendorRegex.exec(prop)) {
vendorPrefix = match[0];
vendorPrefix = vendorPrefix.substr(0, 1).toUpperCase() + vendorPrefix.substr(1);
break;
}
}
- if(!vendorPrefix) {
+ if (!vendorPrefix) {
vendorPrefix = ('WebkitOpacity' in bodyStyle) && 'webkit';
}
transitions = !!(('transition' in bodyStyle) || (vendorPrefix + 'Transition' in bodyStyle));
animations = !!(('animation' in bodyStyle) || (vendorPrefix + 'Animation' in bodyStyle));
- if (android && (!transitions||!animations)) {
- transitions = isString(document.body.style.webkitTransition);
- animations = isString(document.body.style.webkitAnimation);
+ if (android && (!transitions || !animations)) {
+ transitions = isString(bodyStyle.webkitTransition);
+ animations = isString(bodyStyle.webkitAnimation);
}
}
@@ -23399,14 +26707,13 @@ function $SnifferProvider() {
// jshint -W018
history: !!($window.history && $window.history.pushState && !(android < 4) && !boxee),
// jshint +W018
- hashchange: 'onhashchange' in $window &&
- // IE8 compatible mode lies
- (!documentMode || documentMode > 7),
hasEvent: function(event) {
// IE9 implements 'input' event it's so fubared that we rather pretend that it doesn't have
// it. In particular the event is not fired when backspace or delete key are pressed or
// when cut operation is performed.
- if (event == 'input' && msie == 9) return false;
+ // IE10+ implements 'input' event but it erroneously fires under various situations,
+ // e.g. when placeholder changes, or a form is focused.
+ if (event === 'input' && msie <= 11) return false;
if (isUndefined(eventSupport[event])) {
var divElm = document.createElement('div');
@@ -23417,57 +26724,255 @@ function $SnifferProvider() {
},
csp: csp(),
vendorPrefix: vendorPrefix,
- transitions : transitions,
- animations : animations,
- android: android,
- msie : msie,
- msieDocumentMode: documentMode
+ transitions: transitions,
+ animations: animations,
+ android: android
};
}];
}
+var $compileMinErr = minErr('$compile');
+
+/**
+ * @ngdoc service
+ * @name $templateRequest
+ *
+ * @description
+ * The `$templateRequest` service runs security checks then downloads the provided template using
+ * `$http` and, upon success, stores the contents inside of `$templateCache`. If the HTTP request
+ * fails or the response data of the HTTP request is empty, a `$compile` error will be thrown (the
+ * exception can be thwarted by setting the 2nd parameter of the function to true). Note that the
+ * contents of `$templateCache` are trusted, so the call to `$sce.getTrustedUrl(tpl)` is omitted
+ * when `tpl` is of type string and `$templateCache` has the matching entry.
+ *
+ * @param {string|TrustedResourceUrl} tpl The HTTP request template URL
+ * @param {boolean=} ignoreRequestError Whether or not to ignore the exception when the request fails or the template is empty
+ *
+ * @return {Promise} a promise for the HTTP response data of the given URL.
+ *
+ * @property {number} totalPendingRequests total amount of pending template requests being downloaded.
+ */
+function $TemplateRequestProvider() {
+ this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
+ function handleRequestFn(tpl, ignoreRequestError) {
+ handleRequestFn.totalPendingRequests++;
+
+ // We consider the template cache holds only trusted templates, so
+ // there's no need to go through whitelisting again for keys that already
+ // are included in there. This also makes Angular accept any script
+ // directive, no matter its name. However, we still need to unwrap trusted
+ // types.
+ if (!isString(tpl) || !$templateCache.get(tpl)) {
+ tpl = $sce.getTrustedResourceUrl(tpl);
+ }
+
+ var transformResponse = $http.defaults && $http.defaults.transformResponse;
+
+ if (isArray(transformResponse)) {
+ transformResponse = transformResponse.filter(function(transformer) {
+ return transformer !== defaultHttpResponseTransform;
+ });
+ } else if (transformResponse === defaultHttpResponseTransform) {
+ transformResponse = null;
+ }
+
+ var httpOptions = {
+ cache: $templateCache,
+ transformResponse: transformResponse
+ };
+
+ return $http.get(tpl, httpOptions)
+ ['finally'](function() {
+ handleRequestFn.totalPendingRequests--;
+ })
+ .then(function(response) {
+ $templateCache.put(tpl, response.data);
+ return response.data;
+ }, handleError);
+
+ function handleError(resp) {
+ if (!ignoreRequestError) {
+ throw $compileMinErr('tpload', 'Failed to load template: {0} (HTTP status: {1} {2})',
+ tpl, resp.status, resp.statusText);
+ }
+ return $q.reject(resp);
+ }
+ }
+
+ handleRequestFn.totalPendingRequests = 0;
+
+ return handleRequestFn;
+ }];
+}
+
+function $$TestabilityProvider() {
+ this.$get = ['$rootScope', '$browser', '$location',
+ function($rootScope, $browser, $location) {
+
+ /**
+ * @name $testability
+ *
+ * @description
+ * The private $$testability service provides a collection of methods for use when debugging
+ * or by automated test and debugging tools.
+ */
+ var testability = {};
+
+ /**
+ * @name $$testability#findBindings
+ *
+ * @description
+ * Returns an array of elements that are bound (via ng-bind or {{}})
+ * to expressions matching the input.
+ *
+ * @param {Element} element The element root to search from.
+ * @param {string} expression The binding expression to match.
+ * @param {boolean} opt_exactMatch If true, only returns exact matches
+ * for the expression. Filters and whitespace are ignored.
+ */
+ testability.findBindings = function(element, expression, opt_exactMatch) {
+ var bindings = element.getElementsByClassName('ng-binding');
+ var matches = [];
+ forEach(bindings, function(binding) {
+ var dataBinding = angular.element(binding).data('$binding');
+ if (dataBinding) {
+ forEach(dataBinding, function(bindingName) {
+ if (opt_exactMatch) {
+ var matcher = new RegExp('(^|\\s)' + escapeForRegexp(expression) + '(\\s|\\||$)');
+ if (matcher.test(bindingName)) {
+ matches.push(binding);
+ }
+ } else {
+ if (bindingName.indexOf(expression) != -1) {
+ matches.push(binding);
+ }
+ }
+ });
+ }
+ });
+ return matches;
+ };
+
+ /**
+ * @name $$testability#findModels
+ *
+ * @description
+ * Returns an array of elements that are two-way found via ng-model to
+ * expressions matching the input.
+ *
+ * @param {Element} element The element root to search from.
+ * @param {string} expression The model expression to match.
+ * @param {boolean} opt_exactMatch If true, only returns exact matches
+ * for the expression.
+ */
+ testability.findModels = function(element, expression, opt_exactMatch) {
+ var prefixes = ['ng-', 'data-ng-', 'ng\\:'];
+ for (var p = 0; p < prefixes.length; ++p) {
+ var attributeEquals = opt_exactMatch ? '=' : '*=';
+ var selector = '[' + prefixes[p] + 'model' + attributeEquals + '"' + expression + '"]';
+ var elements = element.querySelectorAll(selector);
+ if (elements.length) {
+ return elements;
+ }
+ }
+ };
+
+ /**
+ * @name $$testability#getLocation
+ *
+ * @description
+ * Shortcut for getting the location in a browser agnostic way. Returns
+ * the path, search, and hash. (e.g. /path?a=b#hash)
+ */
+ testability.getLocation = function() {
+ return $location.url();
+ };
+
+ /**
+ * @name $$testability#setLocation
+ *
+ * @description
+ * Shortcut for navigating to a location without doing a full page reload.
+ *
+ * @param {string} url The location url (path, search and hash,
+ * e.g. /path?a=b#hash) to go to.
+ */
+ testability.setLocation = function(url) {
+ if (url !== $location.url()) {
+ $location.url(url);
+ $rootScope.$digest();
+ }
+ };
+
+ /**
+ * @name $$testability#whenStable
+ *
+ * @description
+ * Calls the callback when $timeout and $http requests are completed.
+ *
+ * @param {function} callback
+ */
+ testability.whenStable = function(callback) {
+ $browser.notifyWhenNoOutstandingRequests(callback);
+ };
+
+ return testability;
+ }];
+}
+
function $TimeoutProvider() {
- this.$get = ['$rootScope', '$browser', '$q', '$exceptionHandler',
- function($rootScope, $browser, $q, $exceptionHandler) {
+ this.$get = ['$rootScope', '$browser', '$q', '$$q', '$exceptionHandler',
+ function($rootScope, $browser, $q, $$q, $exceptionHandler) {
+
var deferreds = {};
/**
- * @ngdoc function
- * @name ng.$timeout
- * @requires $browser
+ * @ngdoc service
+ * @name $timeout
*
* @description
* Angular's wrapper for `window.setTimeout`. The `fn` function is wrapped into a try/catch
* block and delegates any exceptions to
* {@link ng.$exceptionHandler $exceptionHandler} service.
*
- * The return value of registering a timeout function is a promise, which will be resolved when
- * the timeout is reached and the timeout function is executed.
+ * The return value of calling `$timeout` is a promise, which will be resolved when
+ * the delay has passed and the timeout function, if provided, is executed.
*
* To cancel a timeout request, call `$timeout.cancel(promise)`.
*
* In tests you can use {@link ngMock.$timeout `$timeout.flush()`} to
* synchronously flush the queue of deferred functions.
*
- * @param {function()} fn A function, whose execution should be delayed.
+ * If you only want a promise that will be resolved after some specified delay
+ * then you can call `$timeout` without the `fn` function.
+ *
+ * @param {function()=} fn A function, whose execution should be delayed.
* @param {number=} [delay=0] Delay in milliseconds.
* @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise
- * will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
+ * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block.
+ * @param {...*=} Pass additional parameters to the executed function.
* @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
* promise will be resolved with is the return value of the `fn` function.
- *
+ *
*/
function timeout(fn, delay, invokeApply) {
- var deferred = $q.defer(),
- promise = deferred.promise,
+ if (!isFunction(fn)) {
+ invokeApply = delay;
+ delay = fn;
+ fn = noop;
+ }
+
+ var args = sliceArgs(arguments, 3),
skipApply = (isDefined(invokeApply) && !invokeApply),
+ deferred = (skipApply ? $$q : $q).defer(),
+ promise = deferred.promise,
timeoutId;
timeoutId = $browser.defer(function() {
try {
- deferred.resolve(fn());
- } catch(e) {
+ deferred.resolve(fn.apply(null, args));
+ } catch (e) {
deferred.reject(e);
$exceptionHandler(e);
}
@@ -23486,9 +26991,8 @@ function $TimeoutProvider() {
/**
- * @ngdoc function
- * @name ng.$timeout#cancel
- * @methodOf ng.$timeout
+ * @ngdoc method
+ * @name $timeout#cancel
*
* @description
* Cancels a task associated with the `promise`. As a result of this, the promise will be
@@ -23519,7 +27023,7 @@ function $TimeoutProvider() {
// exactly the behavior needed here. There is little value is mocking these out for this
// service.
var urlParsingNode = document.createElement("a");
-var originUrl = urlResolve(window.location.href, true);
+var originUrl = urlResolve(window.location.href);
/**
@@ -23536,20 +27040,13 @@ var originUrl = urlResolve(window.location.href, true);
*
* Implementation Notes for IE
* ---------------------------
- * IE >= 8 and <= 10 normalizes the URL when assigned to the anchor node similar to the other
+ * IE <= 10 normalizes the URL when assigned to the anchor node similar to the other
* browsers. However, the parsed components will not be set if the URL assigned did not specify
* them. (e.g. if you assign a.href = "foo", then a.protocol, a.host, etc. will be empty.) We
* work around that by performing the parsing in a 2nd step by taking a previously normalized
* URL (e.g. by assigning to a.href) and assigning it a.href again. This correctly populates the
* properties such as protocol, hostname, port, etc.
*
- * IE7 does not normalize the URL when assigned to an anchor node. (Apparently, it does, if one
- * uses the inner HTML approach to assign the URL as part of an HTML snippet -
- * http://stackoverflow.com/a/472729) However, setting img[src] does normalize the URL.
- * Unfortunately, setting img[src] to something like "javascript:foo" on IE throws an exception.
- * Since the primary usage for normalizing URLs is to sanitize such URLs, we can't use that
- * method and IE < 8 is unsupported.
- *
* References:
* http://developer.mozilla.org/en-US/docs/Web/API/HTMLAnchorElement
* http://www.aptana.com/reference/html/api/HTMLAnchorElement.html
@@ -23557,7 +27054,7 @@ var originUrl = urlResolve(window.location.href, true);
* https://github.com/angular/angular.js/pull/2902
* http://james.padolsey.com/javascript/parsing-urls-with-the-dom/
*
- * @function
+ * @kind function
* @param {string} url The URL to be parsed.
* @description Normalizes and parses a URL.
* @returns {object} Returns the normalized URL as a dictionary.
@@ -23574,7 +27071,7 @@ var originUrl = urlResolve(window.location.href, true);
* | pathname | The pathname, beginning with "/"
*
*/
-function urlResolve(url, base) {
+function urlResolve(url) {
var href = url;
if (msie) {
@@ -23615,8 +27112,8 @@ function urlIsSameOrigin(requestUrl) {
}
/**
- * @ngdoc object
- * @name ng.$window
+ * @ngdoc service
+ * @name $window
*
* @description
* A reference to the browser's `window` object. While `window`
@@ -23630,44 +27127,118 @@ function urlIsSameOrigin(requestUrl) {
* expression.
*
* @example
-
-
+
+
-
-
+
+
-
-
+
+
it('should display the greeting in the input box', function() {
element(by.model('greeting')).sendKeys('Hello, E2E Tests');
// If we click the button it will block the test runner
// element(':button').click();
});
-
-
+
+
*/
-function $WindowProvider(){
+function $WindowProvider() {
this.$get = valueFn(window);
}
/**
- * @ngdoc object
- * @name ng.$filterProvider
+ * @name $$cookieReader
+ * @requires $document
+ *
+ * @description
+ * This is a private service for reading cookies used by $http and ngCookies
+ *
+ * @return {Object} a key/value map of the current cookies
+ */
+function $$CookieReader($document) {
+ var rawDocument = $document[0] || {};
+ var lastCookies = {};
+ var lastCookieString = '';
+
+ function safeDecodeURIComponent(str) {
+ try {
+ return decodeURIComponent(str);
+ } catch (e) {
+ return str;
+ }
+ }
+
+ return function() {
+ var cookieArray, cookie, i, index, name;
+ var currentCookieString = rawDocument.cookie || '';
+
+ if (currentCookieString !== lastCookieString) {
+ lastCookieString = currentCookieString;
+ cookieArray = lastCookieString.split('; ');
+ lastCookies = {};
+
+ for (i = 0; i < cookieArray.length; i++) {
+ cookie = cookieArray[i];
+ index = cookie.indexOf('=');
+ if (index > 0) { //ignore nameless cookies
+ name = safeDecodeURIComponent(cookie.substring(0, index));
+ // the first value that is seen for a cookie is the most
+ // specific one. values for the same cookie name that
+ // follow are for less specific paths.
+ if (lastCookies[name] === undefined) {
+ lastCookies[name] = safeDecodeURIComponent(cookie.substring(index + 1));
+ }
+ }
+ }
+ }
+ return lastCookies;
+ };
+}
+
+$$CookieReader.$inject = ['$document'];
+
+function $$CookieReaderProvider() {
+ this.$get = $$CookieReader;
+}
+
+/* global currencyFilter: true,
+ dateFilter: true,
+ filterFilter: true,
+ jsonFilter: true,
+ limitToFilter: true,
+ lowercaseFilter: true,
+ numberFilter: true,
+ orderByFilter: true,
+ uppercaseFilter: true,
+ */
+
+/**
+ * @ngdoc provider
+ * @name $filterProvider
* @description
*
* Filters are just functions which transform input to an output. However filters need to be
* Dependency Injected. To achieve this a filter definition consists of a factory function which is
* annotated with dependencies and is responsible for creating a filter function.
*
- *
+ *
+ * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
+ * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
+ * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
+ * (`myapp_subsection_filterx`).
+ *
+ *
+ * ```js
* // Filter registration
* function MyModule($provide, $filterProvider) {
* // create a service to demonstrate injection (not always needed)
@@ -23686,12 +27257,12 @@ function $WindowProvider(){
* };
* });
* }
- *
+ * ```
*
* The filter function is registered with the `$injector` under the filter name suffix with
* `Filter`.
- *
- *
+ *
+ * ```js
* it('should be the same instance', inject(
* function($filterProvider) {
* $filterProvider.register('reverse', function(){
@@ -23701,28 +27272,17 @@ function $WindowProvider(){
* function($filter, reverseFilter) {
* expect($filter('reverse')).toBe(reverseFilter);
* });
- *
+ * ```
*
*
* For more information about how angular filters work, and how to create your own filters, see
* {@link guide/filter Filters} in the Angular Developer Guide.
*/
-/**
- * @ngdoc method
- * @name ng.$filterProvider#register
- * @methodOf ng.$filterProvider
- * @description
- * Register filter factory function.
- *
- * @param {String} name Name of the filter.
- * @param {function} fn The filter factory function which is injectable.
- */
-
/**
- * @ngdoc function
- * @name ng.$filter
- * @function
+ * @ngdoc service
+ * @name $filter
+ * @kind function
* @description
* Filters are used for formatting data displayed to the user.
*
@@ -23732,22 +27292,46 @@ function $WindowProvider(){
*
* @param {String} name Name of the filter function to retrieve
* @return {Function} the filter function
- */
+ * @example
+
+
+
+
{{ originalText }}
+ {{ filteredText }}
+
+
+
+
+ angular.module('filterExample', [])
+ .controller('MainCtrl', function($scope, $filter) {
+ $scope.originalText = 'hello';
+ $scope.filteredText = $filter('uppercase')($scope.originalText);
+ });
+
+
+ */
$FilterProvider.$inject = ['$provide'];
function $FilterProvider($provide) {
var suffix = 'Filter';
/**
- * @ngdoc function
- * @name ng.$controllerProvider#register
- * @methodOf ng.$controllerProvider
+ * @ngdoc method
+ * @name $filterProvider#register
* @param {string|Object} name Name of the filter function, or an object map of filters where
* the keys are the filter names and the values are the filter factories.
+ *
+ *
+ * **Note:** Filter names must be valid angular {@link expression} identifiers, such as `uppercase` or `orderBy`.
+ * Names with special characters, such as hyphens and dots, are not allowed. If you wish to namespace
+ * your filters, then you can use capitalization (`myappSubsectionFilterx`) or underscores
+ * (`myapp_subsection_filterx`).
+ *
+ * @param {Function} factory If the first argument was a string, a factory function for the filter to be registered.
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
* of the registered filter instances.
*/
function register(name, factory) {
- if(isObject(name)) {
+ if (isObject(name)) {
var filters = {};
forEach(name, function(filter, key) {
filters[key] = register(key, filter);
@@ -23766,7 +27350,7 @@ function $FilterProvider($provide) {
}];
////////////////////////////////////////
-
+
/* global
currencyFilter: false,
dateFilter: false,
@@ -23792,8 +27376,8 @@ function $FilterProvider($provide) {
/**
* @ngdoc filter
- * @name ng.filter:filter
- * @function
+ * @name filter
+ * @kind function
*
* @description
* Selects a subset of items from `array` and returns it as a new array.
@@ -23804,20 +27388,31 @@ function $FilterProvider($provide) {
*
* Can be one of:
*
- * - `string`: The string is evaluated as an expression and the resulting value is used for substring match against
- * the contents of the `array`. All strings or objects with string properties in `array` that contain this string
- * will be returned. The predicate can be negated by prefixing the string with `!`.
+ * - `string`: The string is used for matching against the contents of the `array`. All strings or
+ * objects with string properties in `array` that match this string will be returned. This also
+ * applies to nested object properties.
+ * The predicate can be negated by prefixing the string with `!`.
*
* - `Object`: A pattern object can be used to filter specific properties on objects contained
* by `array`. For example `{name:"M", phone:"1"}` predicate will return an array of items
* which have property `name` containing "M" and property `phone` containing "1". A special
* property name `$` can be used (as in `{$:"text"}`) to accept a match against any
- * property of the object. That's equivalent to the simple substring match with a `string`
- * as described above.
+ * property of the object or its nested object properties. That's equivalent to the simple
+ * substring match with a `string` as described above. The predicate can be negated by prefixing
+ * the string with `!`.
+ * For example `{name: "!M"}` predicate will return an array of items which have property `name`
+ * not containing "M".
*
- * - `function(value)`: A predicate function can be used to write arbitrary filters. The function is
- * called for each element of `array`. The final result is an array of those elements that
- * the predicate returned true for.
+ * Note that a named property will match properties on the same level only, while the special
+ * `$` property will match properties on the same level or deeper. E.g. an array item like
+ * `{name: {first: 'John', last: 'Doe'}}` will **not** be matched by `{name: 'John'}`, but
+ * **will** be matched by `{$: 'John'}`.
+ *
+ * - `function(value, index, array)`: A predicate function can be used to write arbitrary filters.
+ * The function is called for each element of the array, with the element, its index, and
+ * the entire array itself as arguments.
+ *
+ * The final result is an array of those elements that the predicate returned true for.
*
* @param {function(actual, expected)|true|undefined} comparator Comparator which is used in
* determining if the expected value (from the filter expression) and actual value (from
@@ -23825,19 +27420,22 @@ function $FilterProvider($provide) {
*
* Can be one of:
*
- * - `function(actual, expected)`:
- * The function will be given the object value and the predicate value to compare and
- * should return true if the item should be included in filtered result.
+ * - `function(actual, expected)`:
+ * The function will be given the object value and the predicate value to compare and
+ * should return true if both values should be considered equal.
*
- * - `true`: A shorthand for `function(actual, expected) { return angular.equals(expected, actual)}`.
- * this is essentially strict comparison of expected and actual.
+ * - `true`: A shorthand for `function(actual, expected) { return angular.equals(actual, expected)}`.
+ * This is essentially strict comparison of expected and actual.
*
- * - `false|undefined`: A short hand for a function which will look for a substring match in case
- * insensitive way.
+ * - `false|undefined`: A short hand for a function which will look for a substring match in case
+ * insensitive way.
+ *
+ * Primitive values are converted to strings. Objects are not compared against primitives,
+ * unless they have a custom `toString` method (e.g. `Date` objects).
*
* @example
-
-
+
+
- Search:
+
Name | Phone |
@@ -23854,10 +27452,10 @@ function $FilterProvider($provide) {
- Any:
- Name only
- Phone only
- Equality
+
+
+
+
Name | Phone |
@@ -23865,8 +27463,8 @@ function $FilterProvider($provide) {
{{friendObj.phone}} |
-
-
+
+
var expectFriendNames = function(expectedNames, key) {
element.all(by.repeater(key + ' in friends').column(key + '.name')).then(function(arr) {
arr.forEach(function(wd, i) {
@@ -23900,119 +27498,141 @@ function $FilterProvider($provide) {
strict.click();
expectFriendNames(['Julie'], 'friendObj');
});
-
-
+
+
*/
function filterFilter() {
return function(array, expression, comparator) {
- if (!isArray(array)) return array;
-
- var comparatorType = typeof(comparator),
- predicates = [];
-
- predicates.check = function(value) {
- for (var j = 0; j < predicates.length; j++) {
- if(!predicates[j](value)) {
- return false;
- }
- }
- return true;
- };
-
- if (comparatorType !== 'function') {
- if (comparatorType === 'boolean' && comparator) {
- comparator = function(obj, text) {
- return angular.equals(obj, text);
- };
+ if (!isArrayLike(array)) {
+ if (array == null) {
+ return array;
} else {
- comparator = function(obj, text) {
- if (obj && text && typeof obj === 'object' && typeof text === 'object') {
- for (var objKey in obj) {
- if (objKey.charAt(0) !== '$' && hasOwnProperty.call(obj, objKey) &&
- comparator(obj[objKey], text[objKey])) {
- return true;
- }
- }
- return false;
- }
- text = (''+text).toLowerCase();
- return (''+obj).toLowerCase().indexOf(text) > -1;
- };
+ throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
- var search = function(obj, text){
- if (typeof text == 'string' && text.charAt(0) === '!') {
- return !search(obj, text.substr(1));
- }
- switch (typeof obj) {
- case "boolean":
- case "number":
- case "string":
- return comparator(obj, text);
- case "object":
- switch (typeof text) {
- case "object":
- return comparator(obj, text);
- default:
- for ( var objKey in obj) {
- if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
- return true;
- }
- }
- break;
- }
- return false;
- case "array":
- for ( var i = 0; i < obj.length; i++) {
- if (search(obj[i], text)) {
- return true;
- }
- }
- return false;
- default:
- return false;
- }
- };
- switch (typeof expression) {
- case "boolean":
- case "number":
- case "string":
- // Set up expression object and fall through
- expression = {$:expression};
- // jshint -W086
- case "object":
- // jshint +W086
- for (var key in expression) {
- (function(path) {
- if (typeof expression[path] == 'undefined') return;
- predicates.push(function(value) {
- return search(path == '$' ? value : (value && value[path]), expression[path]);
- });
- })(key);
- }
- break;
+ var expressionType = getTypeForFilter(expression);
+ var predicateFn;
+ var matchAgainstAnyProp;
+
+ switch (expressionType) {
case 'function':
- predicates.push(expression);
+ predicateFn = expression;
+ break;
+ case 'boolean':
+ case 'null':
+ case 'number':
+ case 'string':
+ matchAgainstAnyProp = true;
+ //jshint -W086
+ case 'object':
+ //jshint +W086
+ predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
- var filtered = [];
- for ( var j = 0; j < array.length; j++) {
- var value = array[j];
- if (predicates.check(value)) {
- filtered.push(value);
- }
- }
- return filtered;
+
+ return Array.prototype.filter.call(array, predicateFn);
};
}
+// Helper functions for `filterFilter`
+function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
+ var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
+ var predicateFn;
+
+ if (comparator === true) {
+ comparator = equals;
+ } else if (!isFunction(comparator)) {
+ comparator = function(actual, expected) {
+ if (isUndefined(actual)) {
+ // No substring matching against `undefined`
+ return false;
+ }
+ if ((actual === null) || (expected === null)) {
+ // No substring matching against `null`; only match against `null`
+ return actual === expected;
+ }
+ if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual))) {
+ // Should not compare primitives against objects, unless they have custom `toString` method
+ return false;
+ }
+
+ actual = lowercase('' + actual);
+ expected = lowercase('' + expected);
+ return actual.indexOf(expected) !== -1;
+ };
+ }
+
+ predicateFn = function(item) {
+ if (shouldMatchPrimitives && !isObject(item)) {
+ return deepCompare(item, expression.$, comparator, false);
+ }
+ return deepCompare(item, expression, comparator, matchAgainstAnyProp);
+ };
+
+ return predicateFn;
+}
+
+function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
+ var actualType = getTypeForFilter(actual);
+ var expectedType = getTypeForFilter(expected);
+
+ if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
+ return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
+ } else if (isArray(actual)) {
+ // In case `actual` is an array, consider it a match
+ // if ANY of it's items matches `expected`
+ return actual.some(function(item) {
+ return deepCompare(item, expected, comparator, matchAgainstAnyProp);
+ });
+ }
+
+ switch (actualType) {
+ case 'object':
+ var key;
+ if (matchAgainstAnyProp) {
+ for (key in actual) {
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
+ return true;
+ }
+ }
+ return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
+ } else if (expectedType === 'object') {
+ for (key in expected) {
+ var expectedVal = expected[key];
+ if (isFunction(expectedVal) || isUndefined(expectedVal)) {
+ continue;
+ }
+
+ var matchAnyProperty = key === '$';
+ var actualVal = matchAnyProperty ? actual : actual[key];
+ if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return comparator(actual, expected);
+ }
+ break;
+ case 'function':
+ return false;
+ default:
+ return comparator(actual, expected);
+ }
+}
+
+// Used for easily differentiating between `null` and actual `object`
+function getTypeForFilter(val) {
+ return (val === null) ? 'null' : typeof val;
+}
+
/**
* @ngdoc filter
- * @name ng.filter:currency
- * @function
+ * @name currency
+ * @kind function
*
* @description
* Formats a number as a currency (ie $1,234.56). When no currency symbol is provided, default
@@ -24020,27 +27640,31 @@ function filterFilter() {
*
* @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed.
+ * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
* @returns {string} Formatted number.
*
*
* @example
-
-
+
+
-
-
+
+
default currency symbol ($): {{amount | currency}}
- custom currency identifier (USD$): {{amount | currency:"USD$"}}
+ custom currency identifier (USD$): {{amount | currency:"USD$"}}
+ no fractions (0): {{amount | currency:"USD$":0}}
-
-
+
+
it('should init with 1234.56', function() {
expect(element(by.id('currency-default')).getText()).toBe('$1,234.56');
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('USD$1,234.56');
+ expect(element(by.id('currency-custom')).getText()).toBe('USD$1,234.56');
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('USD$1,235');
});
it('should update', function() {
if (browser.params.browser == 'safari') {
@@ -24050,32 +27674,46 @@ function filterFilter() {
}
element(by.model('amount')).clear();
element(by.model('amount')).sendKeys('-1234');
- expect(element(by.id('currency-default')).getText()).toBe('($1,234.00)');
- expect(element(by.binding('amount | currency:"USD$"')).getText()).toBe('(USD$1,234.00)');
+ expect(element(by.id('currency-default')).getText()).toBe('-$1,234.00');
+ expect(element(by.id('currency-custom')).getText()).toBe('-USD$1,234.00');
+ expect(element(by.id('currency-no-fractions')).getText()).toBe('-USD$1,234');
});
-
-
+
+
*/
currencyFilter.$inject = ['$locale'];
function currencyFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
- return function(amount, currencySymbol){
- if (isUndefined(currencySymbol)) currencySymbol = formats.CURRENCY_SYM;
- return formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, 2).
- replace(/\u00A4/g, currencySymbol);
+ return function(amount, currencySymbol, fractionSize) {
+ if (isUndefined(currencySymbol)) {
+ currencySymbol = formats.CURRENCY_SYM;
+ }
+
+ if (isUndefined(fractionSize)) {
+ fractionSize = formats.PATTERNS[1].maxFrac;
+ }
+
+ // if null or undefined pass it through
+ return (amount == null)
+ ? amount
+ : formatNumber(amount, formats.PATTERNS[1], formats.GROUP_SEP, formats.DECIMAL_SEP, fractionSize).
+ replace(/\u00A4/g, currencySymbol);
};
}
/**
* @ngdoc filter
- * @name ng.filter:number
- * @function
+ * @name number
+ * @kind function
*
* @description
* Formats a number as text.
*
+ * If the input is null or undefined, it will just be returned.
+ * If the input is infinite (Infinity/-Infinity) the Infinity symbol '∞' is returned.
* If the input is not a number an empty string is returned.
*
+ *
* @param {number|string} number Number to format.
* @param {(number|string)=} fractionSize Number of decimal places to round the number to.
* If this is not provided then the fraction size is computed from the current locale's number
@@ -24083,21 +27721,22 @@ function currencyFilter($locale) {
* @returns {string} Number rounded to decimalPlaces and places a “,” after each third digit.
*
* @example
-
-
+
+
-
- Enter number:
+
+
Default formatting: {{val | number}}
No fractions: {{val | number:0}}
Negative number: {{-val | number:4}}
-
-
+
+
it('should format numbers', function() {
expect(element(by.id('number-default')).getText()).toBe('1,234.568');
expect(element(by.binding('val | number:0')).getText()).toBe('1,235');
@@ -24111,8 +27750,8 @@ function currencyFilter($locale) {
expect(element(by.binding('val | number:0')).getText()).toBe('3,374');
expect(element(by.binding('-val | number:4')).getText()).toBe('-3,374.3330');
});
-
-
+
+
*/
@@ -24120,33 +27759,43 @@ numberFilter.$inject = ['$locale'];
function numberFilter($locale) {
var formats = $locale.NUMBER_FORMATS;
return function(number, fractionSize) {
- return formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
- fractionSize);
+
+ // if null or undefined pass it through
+ return (number == null)
+ ? number
+ : formatNumber(number, formats.PATTERNS[0], formats.GROUP_SEP, formats.DECIMAL_SEP,
+ fractionSize);
};
}
var DECIMAL_SEP = '.';
function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
- if (isNaN(number) || !isFinite(number)) return '';
+ if (isObject(number)) return '';
var isNegative = number < 0;
number = Math.abs(number);
+
+ var isInfinity = number === Infinity;
+ if (!isInfinity && !isFinite(number)) return '';
+
var numStr = number + '',
formatedText = '',
+ hasExponent = false,
parts = [];
- var hasExponent = false;
- if (numStr.indexOf('e') !== -1) {
+ if (isInfinity) formatedText = '\u221e';
+
+ if (!isInfinity && numStr.indexOf('e') !== -1) {
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
if (match && match[2] == '-' && match[3] > fractionSize + 1) {
- numStr = '0';
+ number = 0;
} else {
formatedText = numStr;
hasExponent = true;
}
}
- if (!hasExponent) {
+ if (!isInfinity && !hasExponent) {
var fractionLen = (numStr.split(DECIMAL_SEP)[1] || '').length;
// determine fractionSize if it is not specified
@@ -24154,8 +27803,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
}
- var pow = Math.pow(10, fractionSize);
- number = Math.round(number * pow) / pow;
+ // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
+ // inspired by:
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
+ number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
+
var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0];
fraction = fraction[1] || '';
@@ -24167,7 +27819,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
if (whole.length >= (lgroup + group)) {
pos = whole.length - lgroup;
for (i = 0; i < pos; i++) {
- if ((pos - i)%group === 0 && i !== 0) {
+ if ((pos - i) % group === 0 && i !== 0) {
formatedText += groupSep;
}
formatedText += whole.charAt(i);
@@ -24175,28 +27827,32 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
}
for (i = pos; i < whole.length; i++) {
- if ((whole.length - i)%lgroup === 0 && i !== 0) {
+ if ((whole.length - i) % lgroup === 0 && i !== 0) {
formatedText += groupSep;
}
formatedText += whole.charAt(i);
}
// format fraction part.
- while(fraction.length < fractionSize) {
+ while (fraction.length < fractionSize) {
fraction += '0';
}
if (fractionSize && fractionSize !== "0") formatedText += decimalSep + fraction.substr(0, fractionSize);
} else {
-
- if (fractionSize > 0 && number > -1 && number < 1) {
+ if (fractionSize > 0 && number < 1) {
formatedText = number.toFixed(fractionSize);
+ number = parseFloat(formatedText);
}
}
- parts.push(isNegative ? pattern.negPre : pattern.posPre);
- parts.push(formatedText);
- parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
+ if (number === 0) {
+ isNegative = false;
+ }
+
+ parts.push(isNegative ? pattern.negPre : pattern.posPre,
+ formatedText,
+ isNegative ? pattern.negSuf : pattern.posSuf);
return parts.join('');
}
@@ -24207,9 +27863,10 @@ function padNumber(num, digits, trim) {
num = -num;
}
num = '' + num;
- while(num.length < digits) num = '0' + num;
- if (trim)
+ while (num.length < digits) num = '0' + num;
+ if (trim) {
num = num.substr(num.length - digits);
+ }
return neg + num;
}
@@ -24218,9 +27875,10 @@ function dateGetter(name, size, offset, trim) {
offset = offset || 0;
return function(date) {
var value = date['get' + name]();
- if (offset > 0 || value > -offset)
+ if (offset > 0 || value > -offset) {
value += offset;
- if (value === 0 && offset == -12 ) value = 12;
+ }
+ if (value === 0 && offset == -12) value = 12;
return padNumber(value, size, trim);
};
}
@@ -24234,8 +27892,8 @@ function dateStrGetter(name, shortForm) {
};
}
-function timeZoneGetter(date) {
- var zone = -1 * date.getTimezoneOffset();
+function timeZoneGetter(date, formats, offset) {
+ var zone = -1 * offset;
var paddedZone = (zone >= 0) ? "+" : "";
paddedZone += padNumber(Math[zone > 0 ? 'floor' : 'ceil'](zone / 60), 2) +
@@ -24244,10 +27902,44 @@ function timeZoneGetter(date) {
return paddedZone;
}
+function getFirstThursdayOfYear(year) {
+ // 0 = index of January
+ var dayOfWeekOnFirst = (new Date(year, 0, 1)).getDay();
+ // 4 = index of Thursday (+1 to account for 1st = 5)
+ // 11 = index of *next* Thursday (+1 account for 1st = 12)
+ return new Date(year, 0, ((dayOfWeekOnFirst <= 4) ? 5 : 12) - dayOfWeekOnFirst);
+}
+
+function getThursdayThisWeek(datetime) {
+ return new Date(datetime.getFullYear(), datetime.getMonth(),
+ // 4 = index of Thursday
+ datetime.getDate() + (4 - datetime.getDay()));
+}
+
+function weekGetter(size) {
+ return function(date) {
+ var firstThurs = getFirstThursdayOfYear(date.getFullYear()),
+ thisThurs = getThursdayThisWeek(date);
+
+ var diff = +thisThurs - +firstThurs,
+ result = 1 + Math.round(diff / 6.048e8); // 6.048e8 ms per week
+
+ return padNumber(result, size);
+ };
+}
+
function ampmGetter(date, formats) {
return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1];
}
+function eraGetter(date, formats) {
+ return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1];
+}
+
+function longEraGetter(date, formats) {
+ return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1];
+}
+
var DATE_FORMATS = {
yyyy: dateGetter('FullYear', 4),
yy: dateGetter('FullYear', 2, 0, true),
@@ -24272,16 +27964,22 @@ var DATE_FORMATS = {
EEEE: dateStrGetter('Day'),
EEE: dateStrGetter('Day', true),
a: ampmGetter,
- Z: timeZoneGetter
+ Z: timeZoneGetter,
+ ww: weekGetter(2),
+ w: weekGetter(1),
+ G: eraGetter,
+ GG: eraGetter,
+ GGG: eraGetter,
+ GGGG: longEraGetter
};
-var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z))(.*)/,
+var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
NUMBER_STRING = /^\-?\d+$/;
/**
* @ngdoc filter
- * @name ng.filter:date
- * @function
+ * @name date
+ * @kind function
*
* @description
* Formats `date` to a string based on the requested `format`.
@@ -24301,53 +27999,63 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
* * `'EEE'`: Day in Week, (Sun-Sat)
* * `'HH'`: Hour in day, padded (00-23)
* * `'H'`: Hour in day (0-23)
- * * `'hh'`: Hour in am/pm, padded (01-12)
- * * `'h'`: Hour in am/pm, (1-12)
+ * * `'hh'`: Hour in AM/PM, padded (01-12)
+ * * `'h'`: Hour in AM/PM, (1-12)
* * `'mm'`: Minute in hour, padded (00-59)
* * `'m'`: Minute in hour (0-59)
* * `'ss'`: Second in minute, padded (00-59)
* * `'s'`: Second in minute (0-59)
- * * `'.sss' or ',sss'`: Millisecond in second, padded (000-999)
- * * `'a'`: am/pm marker
+ * * `'sss'`: Millisecond in second, padded (000-999)
+ * * `'a'`: AM/PM marker
* * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200)
+ * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year
+ * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year
+ * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD')
+ * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini')
*
* `format` string can also be one of the following predefined
* {@link guide/i18n localizable formats}:
*
* * `'medium'`: equivalent to `'MMM d, y h:mm:ss a'` for en_US locale
- * (e.g. Sep 3, 2010 12:05:08 pm)
- * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 pm)
- * * `'fullDate'`: equivalent to `'EEEE, MMMM d,y'` for en_US locale
+ * (e.g. Sep 3, 2010 12:05:08 PM)
+ * * `'short'`: equivalent to `'M/d/yy h:mm a'` for en_US locale (e.g. 9/3/10 12:05 PM)
+ * * `'fullDate'`: equivalent to `'EEEE, MMMM d, y'` for en_US locale
* (e.g. Friday, September 3, 2010)
* * `'longDate'`: equivalent to `'MMMM d, y'` for en_US locale (e.g. September 3, 2010)
* * `'mediumDate'`: equivalent to `'MMM d, y'` for en_US locale (e.g. Sep 3, 2010)
* * `'shortDate'`: equivalent to `'M/d/yy'` for en_US locale (e.g. 9/3/10)
- * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 pm)
- * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 pm)
+ * * `'mediumTime'`: equivalent to `'h:mm:ss a'` for en_US locale (e.g. 12:05:08 PM)
+ * * `'shortTime'`: equivalent to `'h:mm a'` for en_US locale (e.g. 12:05 PM)
*
- * `format` string can contain literal values. These need to be quoted with single quotes (e.g.
- * `"h 'in the morning'"`). In order to output single quote, use two single quotes in a sequence
+ * `format` string can contain literal values. These need to be escaped by surrounding with single quotes (e.g.
+ * `"h 'in the morning'"`). In order to output a single quote, escape it - i.e., two single quotes in a sequence
* (e.g. `"h 'o''clock'"`).
*
* @param {(Date|number|string)} date Date to format either as Date object, milliseconds (string or
- * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.SSSZ and its
+ * number) or various ISO 8601 datetime string formats (e.g. yyyy-MM-ddTHH:mm:ss.sssZ and its
* shorter versions like yyyy-MM-ddTHH:mmZ, yyyy-MM-dd or yyyyMMddTHHmmssZ). If no timezone is
* specified in the string input, the time is considered to be in the local timezone.
* @param {string=} format Formatting rules (see Description). If not specified,
* `mediumDate` is used.
+ * @param {string=} timezone Timezone to be used for formatting. It understands UTC/GMT and the
+ * continental US time zone abbreviations, but for general use, use a time zone offset, for
+ * example, `'+0430'` (4 hours, 30 minutes east of the Greenwich meridian)
+ * If not specified, the timezone of the browser will be used.
* @returns {string} Formatted string or the input if input is not recognized as date/millis.
*
* @example
-
-
+
+
{{1288323623006 | date:'medium'}}:
{{1288323623006 | date:'medium'}}
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}:
{{1288323623006 | date:'yyyy-MM-dd HH:mm:ss Z'}}
{{1288323623006 | date:'MM/dd/yyyy @ h:mma'}}:
{{'1288323623006' | date:'MM/dd/yyyy @ h:mma'}}
-
-
+ {{1288323623006 | date:"MM/dd/yyyy 'at' h:mma"}}:
+ {{'1288323623006' | date:"MM/dd/yyyy 'at' h:mma"}}
+
+
it('should format date', function() {
expect(element(by.binding("1288323623006 | date:'medium'")).getText()).
toMatch(/Oct 2\d, 2010 \d{1,2}:\d{2}:\d{2} (AM|PM)/);
@@ -24355,9 +28063,11 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZE']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+
toMatch(/2010\-10\-2\d \d{2}:\d{2}:\d{2} (\-|\+)?\d{4}/);
expect(element(by.binding("'1288323623006' | date:'MM/dd/yyyy @ h:mma'")).getText()).
toMatch(/10\/2\d\/2010 @ \d{1,2}:\d{2}(AM|PM)/);
+ expect(element(by.binding("'1288323623006' | date:\"MM/dd/yyyy 'at' h:mma\"")).getText()).
+ toMatch(/10\/2\d\/2010 at \d{1,2}:\d{2}(AM|PM)/);
});
-
-
+
+
*/
dateFilter.$inject = ['$locale'];
function dateFilter($locale) {
@@ -24375,14 +28085,14 @@ function dateFilter($locale) {
timeSetter = match[8] ? date.setUTCHours : date.setHours;
if (match[9]) {
- tzHour = int(match[9] + match[10]);
- tzMin = int(match[9] + match[11]);
+ tzHour = toInt(match[9] + match[10]);
+ tzMin = toInt(match[9] + match[11]);
}
- dateSetter.call(date, int(match[1]), int(match[2]) - 1, int(match[3]));
- var h = int(match[4]||0) - tzHour;
- var m = int(match[5]||0) - tzMin;
- var s = int(match[6]||0);
- var ms = Math.round(parseFloat('0.' + (match[7]||0)) * 1000);
+ dateSetter.call(date, toInt(match[1]), toInt(match[2]) - 1, toInt(match[3]));
+ var h = toInt(match[4] || 0) - tzHour;
+ var m = toInt(match[5] || 0) - tzMin;
+ var s = toInt(match[6] || 0);
+ var ms = Math.round(parseFloat('0.' + (match[7] || 0)) * 1000);
timeSetter.call(date, h, m, s, ms);
return date;
}
@@ -24390,7 +28100,7 @@ function dateFilter($locale) {
}
- return function(date, format) {
+ return function(date, format, timezone) {
var text = '',
parts = [],
fn, match;
@@ -24398,22 +28108,18 @@ function dateFilter($locale) {
format = format || 'mediumDate';
format = $locale.DATETIME_FORMATS[format] || format;
if (isString(date)) {
- if (NUMBER_STRING.test(date)) {
- date = int(date);
- } else {
- date = jsonStringToDate(date);
- }
+ date = NUMBER_STRING.test(date) ? toInt(date) : jsonStringToDate(date);
}
if (isNumber(date)) {
date = new Date(date);
}
- if (!isDate(date)) {
+ if (!isDate(date) || !isFinite(date.getTime())) {
return date;
}
- while(format) {
+ while (format) {
match = DATE_FORMATS_SPLIT.exec(format);
if (match) {
parts = concat(parts, match, 1);
@@ -24424,9 +28130,14 @@ function dateFilter($locale) {
}
}
- forEach(parts, function(value){
+ var dateTimezoneOffset = date.getTimezoneOffset();
+ if (timezone) {
+ dateTimezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
+ date = convertTimezoneToLocal(date, timezone, true);
+ }
+ forEach(parts, function(value) {
fn = DATE_FORMATS[value];
- text += fn ? fn(date, $locale.DATETIME_FORMATS)
+ text += fn ? fn(date, $locale.DATETIME_FORMATS, dateTimezoneOffset)
: value.replace(/(^'|'$)/g, '').replace(/''/g, "'");
});
@@ -24437,8 +28148,8 @@ function dateFilter($locale) {
/**
* @ngdoc filter
- * @name ng.filter:json
- * @function
+ * @name json
+ * @kind function
*
* @description
* Allows you to convert a JavaScript object into JSON string.
@@ -24447,33 +28158,39 @@ function dateFilter($locale) {
* the binding is automatically converted to JSON.
*
* @param {*} object Any JavaScript object (including arrays and primitive types) to filter.
+ * @param {number=} spacing The number of spaces to use per indentation, defaults to 2.
* @returns {string} JSON string.
*
*
- * @example:
-
-
- {{ {'name':'value'} | json }}
-
-
+ * @example
+
+
+ {{ {'name':'value'} | json }}
+ {{ {'name':'value'} | json:4 }}
+
+
it('should jsonify filtered objects', function() {
- expect(element(by.binding("{'name':'value'}")).getText()).toMatch(/\{\n "name": ?"value"\n}/);
+ expect(element(by.id('default-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
+ expect(element(by.id('custom-spacing')).getText()).toMatch(/\{\n "name": ?"value"\n}/);
});
-
-
+
+
*
*/
function jsonFilter() {
- return function(object) {
- return toJson(object, true);
+ return function(object, spacing) {
+ if (isUndefined(spacing)) {
+ spacing = 2;
+ }
+ return toJson(object, spacing);
};
}
/**
* @ngdoc filter
- * @name ng.filter:lowercase
- * @function
+ * @name lowercase
+ * @kind function
* @description
* Converts string to lowercase.
* @see angular.lowercase
@@ -24483,8 +28200,8 @@ var lowercaseFilter = valueFn(lowercase);
/**
* @ngdoc filter
- * @name ng.filter:uppercase
- * @function
+ * @name uppercase
+ * @kind function
* @description
* Converts string to uppercase.
* @see angular.uppercase
@@ -24492,163 +28209,247 @@ var lowercaseFilter = valueFn(lowercase);
var uppercaseFilter = valueFn(uppercase);
/**
- * @ngdoc function
- * @name ng.filter:limitTo
- * @function
+ * @ngdoc filter
+ * @name limitTo
+ * @kind function
*
* @description
* Creates a new array or string containing only a specified number of elements. The elements
- * are taken from either the beginning or the end of the source array or string, as specified by
- * the value and sign (positive or negative) of `limit`.
+ * are taken from either the beginning or the end of the source array, string or number, as specified by
+ * the value and sign (positive or negative) of `limit`. If a number is used as input, it is
+ * converted to a string.
*
- * @param {Array|string} input Source array or string to be limited.
- * @param {string|number} limit The length of the returned array or string. If the `limit` number
+ * @param {Array|string|number} input Source array, string or number to be limited.
+ * @param {string|number} limit The length of the returned array or string. If the `limit` number
* is positive, `limit` number of items from the beginning of the source array/string are copied.
- * If the number is negative, `limit` number of items from the end of the source array/string
- * are copied. The `limit` will be trimmed if it exceeds `array.length`
+ * If the number is negative, `limit` number of items from the end of the source array/string
+ * are copied. The `limit` will be trimmed if it exceeds `array.length`. If `limit` is undefined,
+ * the input will be returned unchanged.
+ * @param {(string|number)=} begin Index at which to begin limitation. As a negative index, `begin`
+ * indicates an offset from the end of `input`. Defaults to `0`.
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
* had less than `limit` elements.
*
* @example
-
-
+
+
-
- Limit {{numbers}} to:
+
-
-
+
+
var numLimitInput = element(by.model('numLimit'));
var letterLimitInput = element(by.model('letterLimit'));
+ var longNumberLimitInput = element(by.model('longNumberLimit'));
var limitedNumbers = element(by.binding('numbers | limitTo:numLimit'));
var limitedLetters = element(by.binding('letters | limitTo:letterLimit'));
+ var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit'));
it('should limit the number array to first three items', function() {
expect(numLimitInput.getAttribute('value')).toBe('3');
expect(letterLimitInput.getAttribute('value')).toBe('3');
+ expect(longNumberLimitInput.getAttribute('value')).toBe('3');
expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]');
expect(limitedLetters.getText()).toEqual('Output letters: abc');
+ expect(limitedLongNumber.getText()).toEqual('Output long number: 234');
});
- it('should update the output when -3 is entered', function() {
- numLimitInput.clear();
- numLimitInput.sendKeys('-3');
- letterLimitInput.clear();
- letterLimitInput.sendKeys('-3');
- expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
- expect(limitedLetters.getText()).toEqual('Output letters: ghi');
- });
+ // There is a bug in safari and protractor that doesn't like the minus key
+ // it('should update the output when -3 is entered', function() {
+ // numLimitInput.clear();
+ // numLimitInput.sendKeys('-3');
+ // letterLimitInput.clear();
+ // letterLimitInput.sendKeys('-3');
+ // longNumberLimitInput.clear();
+ // longNumberLimitInput.sendKeys('-3');
+ // expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]');
+ // expect(limitedLetters.getText()).toEqual('Output letters: ghi');
+ // expect(limitedLongNumber.getText()).toEqual('Output long number: 342');
+ // });
it('should not exceed the maximum size of input array', function() {
numLimitInput.clear();
numLimitInput.sendKeys('100');
letterLimitInput.clear();
letterLimitInput.sendKeys('100');
+ longNumberLimitInput.clear();
+ longNumberLimitInput.sendKeys('100');
expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]');
expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi');
+ expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342');
});
-
-
- */
-function limitToFilter(){
- return function(input, limit) {
- if (!isArray(input) && !isString(input)) return input;
-
- limit = int(limit);
+
+
+*/
+function limitToFilter() {
+ return function(input, limit, begin) {
+ if (Math.abs(Number(limit)) === Infinity) {
+ limit = Number(limit);
+ } else {
+ limit = toInt(limit);
+ }
+ if (isNaN(limit)) return input;
- if (isString(input)) {
- //NaN check on limit
- if (limit) {
- return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
+ if (isNumber(input)) input = input.toString();
+ if (!isArray(input) && !isString(input)) return input;
+
+ begin = (!begin || isNaN(begin)) ? 0 : toInt(begin);
+ begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin;
+
+ if (limit >= 0) {
+ return input.slice(begin, begin + limit);
+ } else {
+ if (begin === 0) {
+ return input.slice(limit, input.length);
} else {
- return "";
+ return input.slice(Math.max(0, begin + limit), begin);
}
}
-
- var out = [],
- i, n;
-
- // if abs(limit) exceeds maximum length, trim it
- if (limit > input.length)
- limit = input.length;
- else if (limit < -input.length)
- limit = -input.length;
-
- if (limit > 0) {
- i = 0;
- n = limit;
- } else {
- i = input.length + limit;
- n = input.length;
- }
-
- for (; i
} expression A predicate to be
+ * @param {function(*)|string|Array.<(function(*)|string)>=} expression A predicate to be
* used by the comparator to determine the order of elements.
*
* Can be one of:
*
* - `function`: Getter function. The result of this function will be sorted using the
- * `<`, `=`, `>` operator.
- * - `string`: An Angular expression which evaluates to an object to order by, such as 'name'
- * to sort by a property called 'name'. Optionally prefixed with `+` or `-` to control
- * ascending or descending sort order (for example, +name or -name).
+ * `<`, `===`, `>` operator.
+ * - `string`: An Angular expression. The result of this expression is used to compare elements
+ * (for example `name` to sort by a property called `name` or `name.substr(0, 3)` to sort by
+ * 3 first characters of a property called `name`). The result of a constant expression
+ * is interpreted as a property name to be used in comparisons (for example `"special name"`
+ * to sort object by the value of their `special name` property). An expression can be
+ * optionally prefixed with `+` or `-` to control ascending or descending sort order
+ * (for example, `+name` or `-name`). If no property is provided, (e.g. `'+'`) then the array
+ * element itself is used to compare where sorting.
* - `Array`: An array of function or string predicates. The first predicate in the array
* is used for sorting, but when two items are equivalent, the next predicate is used.
*
- * @param {boolean=} reverse Reverse the order the array.
+ * If the predicate is missing or empty then it defaults to `'+'`.
+ *
+ * @param {boolean=} reverse Reverse the order of the array.
* @returns {Array} Sorted copy of the source array.
*
+ *
* @example
-
-
+ * The example below demonstrates a simple ngRepeat, where the data is sorted
+ * by age in descending order (predicate is set to `'-age'`).
+ * `reverse` is not set, which means it defaults to `false`.
+
+
-
+
+
+
+ Name |
+ Phone Number |
+ Age |
+
+
+ {{friend.name}} |
+ {{friend.phone}} |
+ {{friend.age}} |
+
+
+
+
+
+ *
+ * The predicate and reverse parameters can be controlled dynamically through scope properties,
+ * as shown in the next example.
+ * @example
+
+
+
+
+
Sorting predicate = {{predicate}}; reverse = {{reverse}}
[
unsorted ]
-
-
+
+
+ *
+ * It's also possible to call the orderBy filter manually, by injecting `$filter`, retrieving the
+ * filter routine with `$filter('orderBy')`, and calling the returned filter routine with the
+ * desired parameters.
+ *
+ * Example:
+ *
+ * @example
+
+
+
+
+
+
+ angular.module('orderByExample', [])
+ .controller('ExampleController', ['$scope', '$filter', function($scope, $filter) {
+ var orderBy = $filter('orderBy');
+ $scope.friends = [
+ { name: 'John', phone: '555-1212', age: 10 },
+ { name: 'Mary', phone: '555-9876', age: 19 },
+ { name: 'Mike', phone: '555-4321', age: 21 },
+ { name: 'Adam', phone: '555-5678', age: 35 },
+ { name: 'Julie', phone: '555-8765', age: 29 }
+ ];
+ $scope.order = function(predicate, reverse) {
+ $scope.friends = orderBy($scope.friends, predicate, reverse);
+ };
+ $scope.order('-age',false);
+ }]);
+
+
*/
orderByFilter.$inject = ['$parse'];
-function orderByFilter($parse){
+function orderByFilter($parse) {
return function(array, sortPredicate, reverseOrder) {
- if (!isArray(array)) return array;
- if (!sortPredicate) return array;
- sortPredicate = isArray(sortPredicate) ? sortPredicate: [sortPredicate];
- sortPredicate = map(sortPredicate, function(predicate){
- var descending = false, get = predicate || identity;
- if (isString(predicate)) {
- if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
- descending = predicate.charAt(0) == '-';
- predicate = predicate.substring(1);
- }
- get = $parse(predicate);
- }
- return reverseComparator(function(a,b){
- return compare(get(a),get(b));
- }, descending);
- });
- var arrayCopy = [];
- for ( var i = 0; i < array.length; i++) { arrayCopy.push(array[i]); }
- return arrayCopy.sort(reverseComparator(comparator, reverseOrder));
- function comparator(o1, o2){
- for ( var i = 0; i < sortPredicate.length; i++) {
- var comp = sortPredicate[i](o1, o2);
- if (comp !== 0) return comp;
- }
- return 0;
+ if (!(isArrayLike(array))) return array;
+
+ if (!isArray(sortPredicate)) { sortPredicate = [sortPredicate]; }
+ if (sortPredicate.length === 0) { sortPredicate = ['+']; }
+
+ var predicates = processPredicates(sortPredicate, reverseOrder);
+ // Add a predicate at the end that evaluates to the element index. This makes the
+ // sort stable as it works as a tie-breaker when all the input predicates cannot
+ // distinguish between two elements.
+ predicates.push({ get: function() { return {}; }, descending: reverseOrder ? -1 : 1});
+
+ // The next three lines are a version of a Swartzian Transform idiom from Perl
+ // (sometimes called the Decorate-Sort-Undecorate idiom)
+ // See https://en.wikipedia.org/wiki/Schwartzian_transform
+ var compareValues = Array.prototype.map.call(array, getComparisonObject);
+ compareValues.sort(doComparison);
+ array = compareValues.map(function(item) { return item.value; });
+
+ return array;
+
+ function getComparisonObject(value, index) {
+ return {
+ value: value,
+ predicateValues: predicates.map(function(predicate) {
+ return getPredicateValue(predicate.get(value), index);
+ })
+ };
}
- function reverseComparator(comp, descending) {
- return toBoolean(descending)
- ? function(a,b){return comp(b,a);}
- : comp;
- }
- function compare(v1, v2){
- var t1 = typeof v1;
- var t2 = typeof v2;
- if (t1 == t2) {
- if (t1 == "string") {
- v1 = v1.toLowerCase();
- v2 = v2.toLowerCase();
- }
- if (v1 === v2) return 0;
- return v1 < v2 ? -1 : 1;
- } else {
- return t1 < t2 ? -1 : 1;
+
+ function doComparison(v1, v2) {
+ var result = 0;
+ for (var index=0, length = predicates.length; index < length; ++index) {
+ result = compare(v1.predicateValues[index], v2.predicateValues[index]) * predicates[index].descending;
+ if (result) break;
}
+ return result;
}
};
+
+ function processPredicates(sortPredicate, reverseOrder) {
+ reverseOrder = reverseOrder ? -1 : 1;
+ return sortPredicate.map(function(predicate) {
+ var descending = 1, get = identity;
+
+ if (isFunction(predicate)) {
+ get = predicate;
+ } else if (isString(predicate)) {
+ if ((predicate.charAt(0) == '+' || predicate.charAt(0) == '-')) {
+ descending = predicate.charAt(0) == '-' ? -1 : 1;
+ predicate = predicate.substring(1);
+ }
+ if (predicate !== '') {
+ get = $parse(predicate);
+ if (get.constant) {
+ var key = get();
+ get = function(value) { return value[key]; };
+ }
+ }
+ }
+ return { get: get, descending: descending * reverseOrder };
+ });
+ }
+
+ function isPrimitive(value) {
+ switch (typeof value) {
+ case 'number': /* falls through */
+ case 'boolean': /* falls through */
+ case 'string':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ function objectValue(value, index) {
+ // If `valueOf` is a valid function use that
+ if (typeof value.valueOf === 'function') {
+ value = value.valueOf();
+ if (isPrimitive(value)) return value;
+ }
+ // If `toString` is a valid function and not the one from `Object.prototype` use that
+ if (hasCustomToString(value)) {
+ value = value.toString();
+ if (isPrimitive(value)) return value;
+ }
+ // We have a basic object so we use the position of the object in the collection
+ return index;
+ }
+
+ function getPredicateValue(value, index) {
+ var type = typeof value;
+ if (value === null) {
+ type = 'string';
+ value = 'null';
+ } else if (type === 'string') {
+ value = value.toLowerCase();
+ } else if (type === 'object') {
+ value = objectValue(value, index);
+ }
+ return { value: value, type: type };
+ }
+
+ function compare(v1, v2) {
+ var result = 0;
+ if (v1.type === v2.type) {
+ if (v1.value !== v2.value) {
+ result = v1.value < v2.value ? -1 : 1;
+ }
+ } else {
+ result = v1.type < v2.type ? -1 : 1;
+ }
+ return result;
+ }
}
function ngDirective(directive) {
@@ -24724,7 +28637,7 @@ function ngDirective(directive) {
/**
* @ngdoc directive
- * @name ng.directive:a
+ * @name a
* @restrict E
*
* @description
@@ -24738,28 +28651,15 @@ function ngDirective(directive) {
var htmlAnchorDirective = valueFn({
restrict: 'E',
compile: function(element, attr) {
-
- if (msie <= 8) {
-
- // turn
link into a stylable link in IE
- // but only if it doesn't have name attribute, in which case it's an anchor
- if (!attr.href && !attr.name) {
- attr.$set('href', '');
- }
-
- // add a comment node to anchors to workaround IE bug that causes element content to be reset
- // to new attribute content if attribute is updated with value containing @ and element also
- // contains value with @
- // see issue #1949
- element.append(document.createComment('IE fix'));
- }
-
- if (!attr.href && !attr.xlinkHref && !attr.name) {
+ if (!attr.href && !attr.xlinkHref) {
return function(scope, element) {
+ // If the linked element is not an anchor tag anymore, do nothing
+ if (element[0].nodeName.toLowerCase() !== 'a') return;
+
// SVGAElement does not use the href attribute, but rather the 'xlinkHref' attribute.
var href = toString.call(element.prop('href')) === '[object SVGAnimatedString]' ?
'xlink:href' : 'href';
- element.on('click', function(event){
+ element.on('click', function(event) {
// if we have no href url, then don't navigate anywhere.
if (!element.attr(href)) {
event.preventDefault();
@@ -24772,7 +28672,7 @@ var htmlAnchorDirective = valueFn({
/**
* @ngdoc directive
- * @name ng.directive:ngHref
+ * @name ngHref
* @restrict A
* @priority 99
*
@@ -24781,19 +28681,18 @@ var htmlAnchorDirective = valueFn({
* make the link go to the wrong URL if the user clicks it before
* Angular has a chance to replace the `{{hash}}` markup with its
* value. Until Angular replaces the markup the link will be broken
- * and will most likely return a 404 error.
- *
- * The `ngHref` directive solves this problem.
+ * and will most likely return a 404 error. The `ngHref` directive
+ * solves this problem.
*
* The wrong way to write it:
- *
- *
- *
+ * ```html
+ *
link1
+ * ```
*
* The correct way to write it:
- *
- *
- *
+ * ```html
+ *
link1
+ * ```
*
* @element A
* @param {template} ngHref any string which can contain `{{}}` markup.
@@ -24801,8 +28700,8 @@ var htmlAnchorDirective = valueFn({
* @example
* This example shows various combinations of `href`, `ng-href` and `ng-click` attributes
* in links and their different behaviors:
-
-
+
+
link 1 (link, don't reload)
link 2 (link, don't reload)
@@ -24810,8 +28709,8 @@ var htmlAnchorDirective = valueFn({
anchor (link, don't reload)
anchor (no link)
link (link, change location)
-
-
+
+
it('should execute ng-click but not reload when href without value', function() {
element(by.id('link-1')).click();
expect(element(by.model('value')).getAttribute('value')).toEqual('1');
@@ -24836,7 +28735,7 @@ var htmlAnchorDirective = valueFn({
return browser.driver.getCurrentUrl().then(function(url) {
return url.match(/\/123$/);
});
- }, 1000, 'page should navigate to /123');
+ }, 5000, 'page should navigate to /123');
});
it('should execute ng-click but not reload when href empty string and name specified', function() {
@@ -24857,15 +28756,22 @@ var htmlAnchorDirective = valueFn({
expect(element(by.id('link-6')).getAttribute('href')).toMatch(/\/6$/);
element(by.id('link-6')).click();
- expect(browser.getCurrentUrl()).toMatch(/\/6$/);
+
+ // At this point, we navigate away from an Angular page, so we need
+ // to use browser.driver to get the base webdriver.
+ browser.wait(function() {
+ return browser.driver.getCurrentUrl().then(function(url) {
+ return url.match(/\/6$/);
+ });
+ }, 5000, 'page should navigate to /6');
});
-
-
+
+
*/
/**
* @ngdoc directive
- * @name ng.directive:ngSrc
+ * @name ngSrc
* @restrict A
* @priority 99
*
@@ -24876,14 +28782,14 @@ var htmlAnchorDirective = valueFn({
* `{{hash}}`. The `ngSrc` directive solves this problem.
*
* The buggy way to write it:
- *
- *
- *
+ * ```html
+ *
+ * ```
*
* The correct way to write it:
- *
- *
- *
+ * ```html
+ *
+ * ```
*
* @element IMG
* @param {template} ngSrc any string which can contain `{{}}` markup.
@@ -24891,7 +28797,7 @@ var htmlAnchorDirective = valueFn({
/**
* @ngdoc directive
- * @name ng.directive:ngSrcset
+ * @name ngSrcset
* @restrict A
* @priority 99
*
@@ -24902,14 +28808,14 @@ var htmlAnchorDirective = valueFn({
* `{{hash}}`. The `ngSrcset` directive solves this problem.
*
* The buggy way to write it:
- *
- *
- *
+ * ```html
+ *
+ * ```
*
* The correct way to write it:
- *
- *
- *
+ * ```html
+ *
+ * ```
*
* @element IMG
* @param {template} ngSrcset any string which can contain `{{}}` markup.
@@ -24917,55 +28823,66 @@ var htmlAnchorDirective = valueFn({
/**
* @ngdoc directive
- * @name ng.directive:ngDisabled
+ * @name ngDisabled
* @restrict A
* @priority 100
*
* @description
*
- * The following markup will make the button enabled on Chrome/Firefox but not on IE8 and older IEs:
- *
- *
- *
- *
- *
+ * This directive sets the `disabled` attribute on the element if the
+ * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy.
*
- * The HTML specification does not require browsers to preserve the values of boolean attributes
- * such as disabled. (Their presence means true and their absence means false.)
+ * A special directive is necessary because we cannot use interpolation inside the `disabled`
+ * attribute. The following example would make the button enabled on Chrome/Firefox
+ * but not on older IEs:
+ *
+ * ```html
+ *
+ *
+ *
+ *
+ * ```
+ *
+ * This is because the HTML specification does not require browsers to preserve the values of
+ * boolean attributes such as `disabled` (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
- * The `ngDisabled` directive solves this problem for the `disabled` attribute.
- * This complementary directive is not removed by the browser and so provides
- * a permanent reliable place to store the binding information.
*
* @example
-
-
- Click me to toggle:
+
+
+
-
-
+
+
it('should toggle button', function() {
- expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeFalsy();
+ expect(element(by.css('button')).getAttribute('disabled')).toBeFalsy();
element(by.model('checked')).click();
- expect(element(by.css('.doc-example-live button')).getAttribute('disabled')).toBeTruthy();
+ expect(element(by.css('button')).getAttribute('disabled')).toBeTruthy();
});
-
-
+
+
*
* @element INPUT
- * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
- * then special attribute "disabled" will be set on the element
+ * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
+ * then the `disabled` attribute will be set on the element
*/
/**
* @ngdoc directive
- * @name ng.directive:ngChecked
+ * @name ngChecked
* @restrict A
* @priority 100
*
* @description
+ * Sets the `checked` attribute on the element, if the expression inside `ngChecked` is truthy.
+ *
+ * Note that this directive should not be used together with {@link ngModel `ngModel`},
+ * as this can lead to unexpected behavior.
+ *
+ * ### Why do we need `ngChecked`?
+ *
* The HTML specification does not require browsers to preserve the values of boolean attributes
* such as checked. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
@@ -24974,29 +28891,29 @@ var htmlAnchorDirective = valueFn({
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
-
-
- Check me to check both:
-
-
-
+
+
+
+
+
+
it('should check both checkBoxes', function() {
expect(element(by.id('checkSlave')).getAttribute('checked')).toBeFalsy();
element(by.model('master')).click();
expect(element(by.id('checkSlave')).getAttribute('checked')).toBeTruthy();
});
-
-
+
+
*
* @element INPUT
- * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
- * then special attribute "checked" will be set on the element
+ * @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
+ * then the `checked` attribute will be set on the element
*/
/**
* @ngdoc directive
- * @name ng.directive:ngReadonly
+ * @name ngReadonly
* @restrict A
* @priority 100
*
@@ -25009,29 +28926,29 @@ var htmlAnchorDirective = valueFn({
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
-
-
- Check me to make text readonly:
-
-
-
+
+
+
+
+
+
it('should toggle readonly attr', function() {
- expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeFalsy();
+ expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeFalsy();
element(by.model('checked')).click();
- expect(element(by.css('.doc-example-live [type="text"]')).getAttribute('readonly')).toBeTruthy();
+ expect(element(by.css('[type="text"]')).getAttribute('readonly')).toBeTruthy();
});
-
-
+
+
*
* @element INPUT
- * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
+ * @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
* then special attribute "readonly" will be set on the element
*/
/**
* @ngdoc directive
- * @name ng.directive:ngSelected
+ * @name ngSelected
* @restrict A
* @priority 100
*
@@ -25040,36 +28957,36 @@ var htmlAnchorDirective = valueFn({
* such as selected. (Their presence means true and their absence means false.)
* If we put an Angular interpolation expression into such an attribute then the
* binding information would be lost when the browser removes the attribute.
- * The `ngSelected` directive solves this problem for the `selected` atttribute.
+ * The `ngSelected` directive solves this problem for the `selected` attribute.
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
- *
+ *
* @example
-
-
- Check me to select:
-
-
+
+
it('should select Greetings!', function() {
expect(element(by.id('greet')).getAttribute('selected')).toBeFalsy();
element(by.model('selected')).click();
expect(element(by.id('greet')).getAttribute('selected')).toBeTruthy();
});
-
-
+
+
*
* @element OPTION
- * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
+ * @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
* then special attribute "selected" will be set on the element
*/
/**
* @ngdoc directive
- * @name ng.directive:ngOpen
+ * @name ngOpen
* @restrict A
* @priority 100
*
@@ -25082,49 +28999,85 @@ var htmlAnchorDirective = valueFn({
* This complementary directive is not removed by the browser and so provides
* a permanent reliable place to store the binding information.
* @example
-
-
- Check me check multiple:
+
+
+
Show/Hide me
-
-
+
+
it('should toggle open', function() {
expect(element(by.id('details')).getAttribute('open')).toBeFalsy();
element(by.model('open')).click();
expect(element(by.id('details')).getAttribute('open')).toBeTruthy();
});
-
-
+
+
*
* @element DETAILS
- * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
+ * @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
* then special attribute "open" will be set on the element
*/
var ngAttributeAliasDirectives = {};
-
// boolean attrs are evaluated
forEach(BOOLEAN_ATTR, function(propName, attrName) {
// binding to multiple is not supported
if (propName == "multiple") return;
+ function defaultLinkFn(scope, element, attr) {
+ scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
+ attr.$set(attrName, !!value);
+ });
+ }
+
var normalized = directiveNormalize('ng-' + attrName);
+ var linkFn = defaultLinkFn;
+
+ if (propName === 'checked') {
+ linkFn = function(scope, element, attr) {
+ // ensuring ngChecked doesn't interfere with ngModel when both are set on the same input
+ if (attr.ngModel !== attr[normalized]) {
+ defaultLinkFn(scope, element, attr);
+ }
+ };
+ }
+
ngAttributeAliasDirectives[normalized] = function() {
+ return {
+ restrict: 'A',
+ priority: 100,
+ link: linkFn
+ };
+ };
+});
+
+// aliased input attrs are evaluated
+forEach(ALIASED_ATTR, function(htmlAttr, ngAttr) {
+ ngAttributeAliasDirectives[ngAttr] = function() {
return {
priority: 100,
link: function(scope, element, attr) {
- scope.$watch(attr[normalized], function ngBooleanAttrWatchAction(value) {
- attr.$set(attrName, !!value);
+ //special case ngPattern when a literal regular expression value
+ //is used as the expression (this way we don't have to watch anything).
+ if (ngAttr === "ngPattern" && attr.ngPattern.charAt(0) == "/") {
+ var match = attr.ngPattern.match(REGEX_STRING_REGEXP);
+ if (match) {
+ attr.$set("ngPattern", new RegExp(match[1], match[2]));
+ return;
+ }
+ }
+
+ scope.$watch(attr[ngAttr], function ngAttrAliasWatchAction(value) {
+ attr.$set(ngAttr, value);
});
}
};
};
});
-
// ng-src, ng-srcset, ng-href are interpolated
forEach(['src', 'srcset', 'href'], function(attrName) {
var normalized = directiveNormalize('ng-' + attrName);
@@ -25132,47 +29085,69 @@ forEach(['src', 'srcset', 'href'], function(attrName) {
return {
priority: 99, // it needs to run after the attributes are interpolated
link: function(scope, element, attr) {
- attr.$observe(normalized, function(value) {
- if (!value)
- return;
+ var propName = attrName,
+ name = attrName;
- attr.$set(attrName, value);
+ if (attrName === 'href' &&
+ toString.call(element.prop('href')) === '[object SVGAnimatedString]') {
+ name = 'xlinkHref';
+ attr.$attr[name] = 'xlink:href';
+ propName = null;
+ }
+
+ attr.$observe(normalized, function(value) {
+ if (!value) {
+ if (attrName === 'href') {
+ attr.$set(name, null);
+ }
+ return;
+ }
+
+ attr.$set(name, value);
// on IE, if "ng:src" directive declaration is used and "src" attribute doesn't exist
// then calling element.setAttribute('src', 'foo') doesn't do anything, so we need
// to set the property as well to achieve the desired effect.
// we use attr[attrName] value since $set can sanitize the url.
- if (msie) element.prop(attrName, attr[attrName]);
+ if (msie && propName) element.prop(propName, attr[name]);
});
}
};
};
});
-/* global -nullFormCtrl */
+/* global -nullFormCtrl, -SUBMITTED_CLASS, addSetValidityMethod: true
+ */
var nullFormCtrl = {
$addControl: noop,
+ $$renameControl: nullFormRenameControl,
$removeControl: noop,
$setValidity: noop,
$setDirty: noop,
- $setPristine: noop
-};
+ $setPristine: noop,
+ $setSubmitted: noop
+},
+SUBMITTED_CLASS = 'ng-submitted';
+
+function nullFormRenameControl(control, name) {
+ control.$name = name;
+}
/**
- * @ngdoc object
- * @name ng.directive:form.FormController
+ * @ngdoc type
+ * @name form.FormController
*
* @property {boolean} $pristine True if user has not interacted with the form yet.
* @property {boolean} $dirty True if user has already interacted with the form.
* @property {boolean} $valid True if all of the containing forms and controls are valid.
* @property {boolean} $invalid True if at least one containing control or form is invalid.
+ * @property {boolean} $submitted True if user has submitted the form even if its invalid.
*
- * @property {Object} $error Is an object hash, containing references to all invalid controls or
- * forms, where:
+ * @property {Object} $error Is an object hash, containing references to controls or
+ * forms with failing validators, where:
*
* - keys are validation tokens (error names),
- * - values are arrays of controls or forms that are invalid for given error name.
- *
+ * - values are arrays of controls or forms that have a failing validator for given error name.
*
* Built-in validation tokens:
*
@@ -25185,9 +29160,14 @@ var nullFormCtrl = {
* - `pattern`
* - `required`
* - `url`
- *
+ * - `date`
+ * - `datetimelocal`
+ * - `time`
+ * - `week`
+ * - `month`
+ *
* @description
- * `FormController` keeps track of all its controls and nested forms as well as state of them,
+ * `FormController` keeps track of all its controls and nested forms as well as the state of them,
* such as being valid/invalid or dirty/pristine.
*
* Each {@link ng.directive:form form} directive creates an instance
@@ -25195,39 +29175,63 @@ var nullFormCtrl = {
*
*/
//asks for $scope to fool the BC controller module
-FormController.$inject = ['$element', '$attrs', '$scope'];
-function FormController(element, attrs) {
+FormController.$inject = ['$element', '$attrs', '$scope', '$animate', '$interpolate'];
+function FormController(element, attrs, $scope, $animate, $interpolate) {
var form = this,
- parentForm = element.parent().controller('form') || nullFormCtrl,
- invalidCount = 0, // used to easily determine if we are valid
- errors = form.$error = {},
controls = [];
+ var parentForm = form.$$parentForm = element.parent().controller('form') || nullFormCtrl;
+
// init state
- form.$name = attrs.name || attrs.ngForm;
+ form.$error = {};
+ form.$$success = {};
+ form.$pending = undefined;
+ form.$name = $interpolate(attrs.name || attrs.ngForm || '')($scope);
form.$dirty = false;
form.$pristine = true;
form.$valid = true;
form.$invalid = false;
+ form.$submitted = false;
parentForm.$addControl(form);
- // Setup initial state of the control
- element.addClass(PRISTINE_CLASS);
- toggleValidCss(true);
-
- // convenience method for easy toggling of classes
- function toggleValidCss(isValid, validationErrorKey) {
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
- element.
- removeClass((isValid ? INVALID_CLASS : VALID_CLASS) + validationErrorKey).
- addClass((isValid ? VALID_CLASS : INVALID_CLASS) + validationErrorKey);
- }
+ /**
+ * @ngdoc method
+ * @name form.FormController#$rollbackViewValue
+ *
+ * @description
+ * Rollback all form controls pending updates to the `$modelValue`.
+ *
+ * Updates may be pending by a debounced event or because the input is waiting for a some future
+ * event defined in `ng-model-options`. This method is typically needed by the reset button of
+ * a form that uses `ng-model-options` to pend updates.
+ */
+ form.$rollbackViewValue = function() {
+ forEach(controls, function(control) {
+ control.$rollbackViewValue();
+ });
+ };
/**
- * @ngdoc function
- * @name ng.directive:form.FormController#$addControl
- * @methodOf ng.directive:form.FormController
+ * @ngdoc method
+ * @name form.FormController#$commitViewValue
+ *
+ * @description
+ * Commit all form controls pending updates to the `$modelValue`.
+ *
+ * Updates may be pending by a debounced event or because the input is waiting for a some future
+ * event defined in `ng-model-options`. This method is rarely needed as `NgModelController`
+ * usually handles calling this in response to input events.
+ */
+ form.$commitViewValue = function() {
+ forEach(controls, function(control) {
+ control.$commitViewValue();
+ });
+ };
+
+ /**
+ * @ngdoc method
+ * @name form.FormController#$addControl
*
* @description
* Register a control with the form.
@@ -25245,10 +29249,20 @@ function FormController(element, attrs) {
}
};
+ // Private API: rename a form control
+ form.$$renameControl = function(control, newName) {
+ var oldName = control.$name;
+
+ if (form[oldName] === control) {
+ delete form[oldName];
+ }
+ form[newName] = control;
+ control.$name = newName;
+ };
+
/**
- * @ngdoc function
- * @name ng.directive:form.FormController#$removeControl
- * @methodOf ng.directive:form.FormController
+ * @ngdoc method
+ * @name form.FormController#$removeControl
*
* @description
* Deregister a control from the form.
@@ -25259,65 +29273,60 @@ function FormController(element, attrs) {
if (control.$name && form[control.$name] === control) {
delete form[control.$name];
}
- forEach(errors, function(queue, validationToken) {
- form.$setValidity(validationToken, true, control);
+ forEach(form.$pending, function(value, name) {
+ form.$setValidity(name, null, control);
+ });
+ forEach(form.$error, function(value, name) {
+ form.$setValidity(name, null, control);
+ });
+ forEach(form.$$success, function(value, name) {
+ form.$setValidity(name, null, control);
});
arrayRemove(controls, control);
};
+
/**
- * @ngdoc function
- * @name ng.directive:form.FormController#$setValidity
- * @methodOf ng.directive:form.FormController
+ * @ngdoc method
+ * @name form.FormController#$setValidity
*
* @description
* Sets the validity of a form control.
*
* This method will also propagate to parent forms.
*/
- form.$setValidity = function(validationToken, isValid, control) {
- var queue = errors[validationToken];
-
- if (isValid) {
- if (queue) {
- arrayRemove(queue, control);
- if (!queue.length) {
- invalidCount--;
- if (!invalidCount) {
- toggleValidCss(isValid);
- form.$valid = true;
- form.$invalid = false;
- }
- errors[validationToken] = false;
- toggleValidCss(true, validationToken);
- parentForm.$setValidity(validationToken, true, form);
+ addSetValidityMethod({
+ ctrl: this,
+ $element: element,
+ set: function(object, property, controller) {
+ var list = object[property];
+ if (!list) {
+ object[property] = [controller];
+ } else {
+ var index = list.indexOf(controller);
+ if (index === -1) {
+ list.push(controller);
}
}
-
- } else {
- if (!invalidCount) {
- toggleValidCss(isValid);
+ },
+ unset: function(object, property, controller) {
+ var list = object[property];
+ if (!list) {
+ return;
}
- if (queue) {
- if (includes(queue, control)) return;
- } else {
- errors[validationToken] = queue = [];
- invalidCount++;
- toggleValidCss(false, validationToken);
- parentForm.$setValidity(validationToken, false, form);
+ arrayRemove(list, controller);
+ if (list.length === 0) {
+ delete object[property];
}
- queue.push(control);
-
- form.$valid = false;
- form.$invalid = true;
- }
- };
+ },
+ parentForm: parentForm,
+ $animate: $animate
+ });
/**
- * @ngdoc function
- * @name ng.directive:form.FormController#$setDirty
- * @methodOf ng.directive:form.FormController
+ * @ngdoc method
+ * @name form.FormController#$setDirty
*
* @description
* Sets the form to a dirty state.
@@ -25326,16 +29335,16 @@ function FormController(element, attrs) {
* state (ng-dirty class). This method will also propagate to parent forms.
*/
form.$setDirty = function() {
- element.removeClass(PRISTINE_CLASS).addClass(DIRTY_CLASS);
+ $animate.removeClass(element, PRISTINE_CLASS);
+ $animate.addClass(element, DIRTY_CLASS);
form.$dirty = true;
form.$pristine = false;
parentForm.$setDirty();
};
/**
- * @ngdoc function
- * @name ng.directive:form.FormController#$setPristine
- * @methodOf ng.directive:form.FormController
+ * @ngdoc method
+ * @name form.FormController#$setPristine
*
* @description
* Sets the form to its pristine state.
@@ -25347,20 +29356,52 @@ function FormController(element, attrs) {
* Setting a form back to a pristine state is often useful when we want to 'reuse' a form after
* saving or resetting it.
*/
- form.$setPristine = function () {
- element.removeClass(DIRTY_CLASS).addClass(PRISTINE_CLASS);
+ form.$setPristine = function() {
+ $animate.setClass(element, PRISTINE_CLASS, DIRTY_CLASS + ' ' + SUBMITTED_CLASS);
form.$dirty = false;
form.$pristine = true;
+ form.$submitted = false;
forEach(controls, function(control) {
control.$setPristine();
});
};
-}
+ /**
+ * @ngdoc method
+ * @name form.FormController#$setUntouched
+ *
+ * @description
+ * Sets the form to its untouched state.
+ *
+ * This method can be called to remove the 'ng-touched' class and set the form controls to their
+ * untouched state (ng-untouched class).
+ *
+ * Setting a form controls back to their untouched state is often useful when setting the form
+ * back to its pristine state.
+ */
+ form.$setUntouched = function() {
+ forEach(controls, function(control) {
+ control.$setUntouched();
+ });
+ };
+
+ /**
+ * @ngdoc method
+ * @name form.FormController#$setSubmitted
+ *
+ * @description
+ * Sets the form to its submitted state.
+ */
+ form.$setSubmitted = function() {
+ $animate.addClass(element, SUBMITTED_CLASS);
+ form.$submitted = true;
+ parentForm.$setSubmitted();
+ };
+}
/**
* @ngdoc directive
- * @name ng.directive:ngForm
+ * @name ngForm
* @restrict EAC
*
* @description
@@ -25368,6 +29409,10 @@ function FormController(element, attrs) {
* does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a
* sub-group of controls needs to be determined.
*
+ * Note: the purpose of `ngForm` is to group controls,
+ * but not to be a replacement for the `