summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRajat Vig <rajat.vig@gmail.com>2015-09-15 13:54:02 -0700
committerRajat Vig <rajat.vig@gmail.com>2015-09-15 13:54:02 -0700
commitdae416b8c8fafd8f7563169aae5c659440eba728 (patch)
tree08818d2ec4335f04c360463cde98373370df5ed3
parent5a82b2238ec093ce200724061dda30dcba69035d (diff)
downloadxstatic-angular-dae416b8c8fafd8f7563169aae5c659440eba728.tar.gz
Update Angular to 1.3.18 from 1.3.7
Change-Id: Ieab642e1b6bac7514ab743fe59ddaa1cec560a2b
-rw-r--r--xstatic/pkg/angular/__init__.py2
-rw-r--r--xstatic/pkg/angular/data/angular-animate.js33
-rw-r--r--xstatic/pkg/angular/data/angular-aria.js207
-rw-r--r--xstatic/pkg/angular/data/angular-cookies.js9
-rw-r--r--xstatic/pkg/angular/data/angular-loader.js13
-rw-r--r--xstatic/pkg/angular/data/angular-messages.js9
-rw-r--r--xstatic/pkg/angular/data/angular-mocks.js137
-rw-r--r--xstatic/pkg/angular/data/angular-resource.js3
-rw-r--r--xstatic/pkg/angular/data/angular-route.js27
-rw-r--r--xstatic/pkg/angular/data/angular-sanitize.js57
-rw-r--r--xstatic/pkg/angular/data/angular-scenario.js4385
-rw-r--r--xstatic/pkg/angular/data/angular-touch.js37
-rw-r--r--xstatic/pkg/angular/data/angular.js4297
-rw-r--r--xstatic/pkg/angular/data/errors.json2
-rw-r--r--xstatic/pkg/angular/data/version.json2
-rw-r--r--xstatic/pkg/angular/data/version.txt2
16 files changed, 5106 insertions, 4116 deletions
diff --git a/xstatic/pkg/angular/__init__.py b/xstatic/pkg/angular/__init__.py
index 518b856..1eec0a1 100644
--- a/xstatic/pkg/angular/__init__.py
+++ b/xstatic/pkg/angular/__init__.py
@@ -11,7 +11,7 @@ NAME = __name__.split('.')[-1] # package name (e.g. 'foo' or 'foo_bar')
# please use a all-lowercase valid python
# package name
-VERSION = '1.3.7' # version of the packaged files, please use the upstream
+VERSION = '1.3.18' # version of the packaged files, please use the upstream
# version number
BUILD = '1' # our package build number, so we can release new builds
# with fixes for xstatic stuff.
diff --git a/xstatic/pkg/angular/data/angular-animate.js b/xstatic/pkg/angular/data/angular-animate.js
index 7d09d78..cd835d0 100644
--- a/xstatic/pkg/angular/data/angular-animate.js
+++ b/xstatic/pkg/angular/data/angular-animate.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -438,9 +438,11 @@ angular.module('ngAnimate', ['ng'])
//so that all the animated elements within the animation frame
//will be properly updated and drawn on screen. This is
//required to perform multi-class CSS based animations with
- //Firefox. DO NOT REMOVE THIS LINE.
- var a = bod.offsetWidth + 1;
- fn();
+ //Firefox. DO NOT REMOVE THIS LINE. DO NOT OPTIMIZE THIS LINE.
+ //THE MINIFIER WILL REMOVE IT OTHERWISE WHICH WILL RESULT IN AN
+ //UNPREDICTABLE BUG THAT IS VERY HARD TO TRACK DOWN AND WILL
+ //TAKE YEARS AWAY FROM YOUR LIFE!
+ fn(bod.offsetWidth);
});
};
}])
@@ -839,7 +841,8 @@ angular.module('ngAnimate', ['ng'])
* promise that was returned when the animation was started.
*
* ```js
- * var promise = $animate.addClass(element, 'super-long-animation').then(function() {
+ * var promise = $animate.addClass(element, 'super-long-animation');
+ * promise.then(function() {
* //this will still be called even if cancelled
* });
*
@@ -1197,18 +1200,21 @@ angular.module('ngAnimate', ['ng'])
}
return cache.promise = runAnimationPostDigest(function(done) {
- var parentElement = element.parent();
- var elementNode = extractElementNode(element);
- var parentNode = elementNode.parentNode;
+ var cache, parentNode, parentElement, elementNode = extractElementNode(element);
+ if (elementNode) {
+ cache = element.data(STORAGE_KEY);
+ element.removeData(STORAGE_KEY);
+
+ parentElement = element.parent();
+ parentNode = elementNode.parentNode;
+ }
+
// TODO(matsko): move this code into the animationsDisabled() function once #8092 is fixed
if (!parentNode || parentNode['$$NG_REMOVED'] || elementNode['$$NG_REMOVED']) {
done();
return;
}
- var cache = element.data(STORAGE_KEY);
- element.removeData(STORAGE_KEY);
-
var state = element.data(NG_ANIMATE_STATE) || {};
var classes = resolveElementClasses(element, cache, state.active);
return !classes
@@ -1332,8 +1338,7 @@ angular.module('ngAnimate', ['ng'])
} else if (lastAnimation.event == 'setClass') {
animationsToCancel.push(lastAnimation);
cleanup(element, className);
- }
- else if (runningAnimations[className]) {
+ } else if (runningAnimations[className]) {
var current = runningAnimations[className];
if (current.event == animationEvent) {
skipAnimation = true;
@@ -1874,7 +1879,7 @@ angular.module('ngAnimate', ['ng'])
return;
}
- if (!staggerTime && styles) {
+ if (!staggerTime && styles && Object.keys(styles).length > 0) {
if (!timings.transitionDuration) {
element.css('transition', timings.animationDuration + 's linear all');
appliedStyles.push('transition');
diff --git a/xstatic/pkg/angular/data/angular-aria.js b/xstatic/pkg/angular/data/angular-aria.js
index 197b9c8..19bcbb6 100644
--- a/xstatic/pkg/angular/data/angular-aria.js
+++ b/xstatic/pkg/angular/data/angular-aria.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -27,13 +27,13 @@
*
* | Directive | Supported Attributes |
* |---------------------------------------------|----------------------------------------------------------------------------------------|
- * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required |
* | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled |
* | {@link ng.directive:ngShow ngShow} | aria-hidden |
* | {@link ng.directive:ngHide ngHide} | aria-hidden |
- * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event |
* | {@link ng.directive:ngDblclick ngDblclick} | tabindex |
* | {@link module:ngMessages ngMessages} | aria-live |
+ * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles |
+ * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role |
*
* Find out more information about each directive by reading the
* {@link guide/accessibility ngAria Developer Guide}.
@@ -105,7 +105,8 @@ function $AriaProvider() {
* - **ariaMultiline** – `{boolean}` – Enables/disables aria-multiline tags
* - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags
* - **tabindex** – `{boolean}` – Enables/disables tabindex tags
- * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on ng-click
+ * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `&lt;div&gt;` and
+ * `&lt;li&gt;` elements with ng-click
*
* @description
* Enables/disables various ARIA attributes
@@ -133,6 +134,7 @@ function $AriaProvider() {
* @name $aria
*
* @description
+ * @priority 200
*
* The $aria service contains helper methods for applying common
* [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives.
@@ -196,6 +198,10 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return $aria.config(normalizedAttr) && !elem.attr(attr);
}
+ function shouldAttachRole(role, elem) {
+ return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT');
+ }
+
function getShape(attr, elem) {
var type = attr.type,
role = attr.role;
@@ -209,82 +215,102 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
return {
restrict: 'A',
require: '?ngModel',
- link: function(scope, elem, attr, ngModel) {
+ priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value
+ compile: function(elem, attr) {
var shape = getShape(attr, elem);
- var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
-
- function ngAriaWatchModelValue() {
- return ngModel.$modelValue;
- }
-
- function getRadioReaction() {
- if (needsTabIndex) {
- needsTabIndex = false;
- return function ngAriaRadioReaction(newVal) {
- var boolVal = newVal === attr.value;
- elem.attr('aria-checked', boolVal);
- elem.attr('tabindex', 0 - !boolVal);
- };
- } else {
- return function ngAriaRadioReaction(newVal) {
- elem.attr('aria-checked', newVal === attr.value);
- };
- }
- }
- function ngAriaCheckboxReaction(newVal) {
- elem.attr('aria-checked', !!newVal);
- }
+ return {
+ pre: function(scope, elem, attr, ngModel) {
+ if (shape === 'checkbox' && attr.type !== 'checkbox') {
+ //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles
+ ngModel.$isEmpty = function(value) {
+ return value === false;
+ };
+ }
+ },
+ post: function(scope, elem, attr, ngModel) {
+ var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem);
- switch (shape) {
- case 'radio':
- case 'checkbox':
- if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
- scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
- getRadioReaction() : ngAriaCheckboxReaction);
+ function ngAriaWatchModelValue() {
+ return ngModel.$modelValue;
}
- break;
- case 'range':
- if ($aria.config('ariaValue')) {
- if (attr.min && !elem.attr('aria-valuemin')) {
- elem.attr('aria-valuemin', attr.min);
- }
- if (attr.max && !elem.attr('aria-valuemax')) {
- elem.attr('aria-valuemax', attr.max);
- }
- if (!elem.attr('aria-valuenow')) {
- scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
- elem.attr('aria-valuenow', newVal);
- });
+
+ function getRadioReaction() {
+ if (needsTabIndex) {
+ needsTabIndex = false;
+ return function ngAriaRadioReaction(newVal) {
+ var boolVal = (attr.value == ngModel.$viewValue);
+ elem.attr('aria-checked', boolVal);
+ elem.attr('tabindex', 0 - !boolVal);
+ };
+ } else {
+ return function ngAriaRadioReaction(newVal) {
+ elem.attr('aria-checked', (attr.value == ngModel.$viewValue));
+ };
}
}
- break;
- case 'multiline':
- if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
- elem.attr('aria-multiline', true);
+
+ function ngAriaCheckboxReaction() {
+ elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue));
}
- break;
- }
- if (needsTabIndex) {
- elem.attr('tabindex', 0);
- }
+ switch (shape) {
+ case 'radio':
+ case 'checkbox':
+ if (shouldAttachRole(shape, elem)) {
+ elem.attr('role', shape);
+ }
+ if (shouldAttachAttr('aria-checked', 'ariaChecked', elem)) {
+ scope.$watch(ngAriaWatchModelValue, shape === 'radio' ?
+ getRadioReaction() : ngAriaCheckboxReaction);
+ }
+ break;
+ case 'range':
+ if (shouldAttachRole(shape, elem)) {
+ elem.attr('role', 'slider');
+ }
+ if ($aria.config('ariaValue')) {
+ if (attr.min && !elem.attr('aria-valuemin')) {
+ elem.attr('aria-valuemin', attr.min);
+ }
+ if (attr.max && !elem.attr('aria-valuemax')) {
+ elem.attr('aria-valuemax', attr.max);
+ }
+ if (!elem.attr('aria-valuenow')) {
+ scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) {
+ elem.attr('aria-valuenow', newVal);
+ });
+ }
+ }
+ break;
+ case 'multiline':
+ if (shouldAttachAttr('aria-multiline', 'ariaMultiline', elem)) {
+ elem.attr('aria-multiline', true);
+ }
+ break;
+ }
- if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
- scope.$watch(function ngAriaRequiredWatch() {
- return ngModel.$error.required;
- }, function ngAriaRequiredReaction(newVal) {
- elem.attr('aria-required', !!newVal);
- });
- }
+ if (needsTabIndex) {
+ elem.attr('tabindex', 0);
+ }
- if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
- scope.$watch(function ngAriaInvalidWatch() {
- return ngModel.$invalid;
- }, function ngAriaInvalidReaction(newVal) {
- elem.attr('aria-invalid', !!newVal);
- });
- }
+ if (ngModel.$validators.required && shouldAttachAttr('aria-required', 'ariaRequired', elem)) {
+ scope.$watch(function ngAriaRequiredWatch() {
+ return ngModel.$error.required;
+ }, function ngAriaRequiredReaction(newVal) {
+ elem.attr('aria-required', !!newVal);
+ });
+ }
+
+ if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem)) {
+ scope.$watch(function ngAriaInvalidWatch() {
+ return ngModel.$invalid;
+ }, function ngAriaInvalidReaction(newVal) {
+ elem.attr('aria-invalid', !!newVal);
+ });
+ }
+ }
+ };
}
};
}])
@@ -302,21 +328,40 @@ ngAriaModule.directive('ngShow', ['$aria', function($aria) {
}
};
})
-.directive('ngClick',['$aria', function($aria) {
+.directive('ngClick',['$aria', '$parse', function($aria, $parse) {
return {
restrict: 'A',
- link: function(scope, elem, attr) {
- if ($aria.config('tabindex') && !elem.attr('tabindex')) {
- elem.attr('tabindex', 0);
- }
+ compile: function(elem, attr) {
+ var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true);
+ return function(scope, elem, attr) {
+
+ var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA'];
- if ($aria.config('bindKeypress') && !elem.attr('ng-keypress')) {
- elem.on('keypress', function(event) {
- if (event.keyCode === 32 || event.keyCode === 13) {
- scope.$eval(attr.ngClick);
+ function isNodeOneOf(elem, nodeTypeArray) {
+ if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) {
+ return true;
}
- });
- }
+ }
+ if (!elem.attr('role') && !isNodeOneOf(elem, nodeBlackList)) {
+ elem.attr('role', 'button');
+ }
+
+ if ($aria.config('tabindex') && !elem.attr('tabindex')) {
+ elem.attr('tabindex', 0);
+ }
+
+ if ($aria.config('bindKeypress') && !attr.ngKeypress && !isNodeOneOf(elem, nodeBlackList)) {
+ elem.on('keypress', function(event) {
+ if (event.keyCode === 32 || event.keyCode === 13) {
+ scope.$apply(callback);
+ }
+
+ function callback() {
+ fn(scope, { $event: event });
+ }
+ });
+ }
+ };
}
};
}])
diff --git a/xstatic/pkg/angular/data/angular-cookies.js b/xstatic/pkg/angular/data/angular-cookies.js
index d80d9d9..495d3ba 100644
--- a/xstatic/pkg/angular/data/angular-cookies.js
+++ b/xstatic/pkg/angular/data/angular-cookies.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -91,6 +91,7 @@ angular.module('ngCookies', ['ng']).
for (name in lastCookies) {
if (isUndefined(cookies[name])) {
$browser.cookies(name, undefined);
+ delete lastCookies[name];
}
}
@@ -103,13 +104,13 @@ angular.module('ngCookies', ['ng']).
}
if (value !== lastCookies[name]) {
$browser.cookies(name, value);
+ lastCookies[name] = value;
updated = true;
}
}
//verify what was actually stored
if (updated) {
- updated = false;
browserCookies = $browser.cookies();
for (name in cookies) {
@@ -117,10 +118,10 @@ angular.module('ngCookies', ['ng']).
//delete or reset all cookies that the browser dropped from $cookies
if (isUndefined(browserCookies[name])) {
delete cookies[name];
+ delete lastCookies[name];
} else {
- cookies[name] = browserCookies[name];
+ cookies[name] = lastCookies[name] = browserCookies[name];
}
- updated = true;
}
}
}
diff --git a/xstatic/pkg/angular/data/angular-loader.js b/xstatic/pkg/angular/data/angular-loader.js
index 2ef986c..fd9b2a1 100644
--- a/xstatic/pkg/angular/data/angular-loader.js
+++ b/xstatic/pkg/angular/data/angular-loader.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -55,7 +55,7 @@ function minErr(module, ErrorConstructor) {
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.7/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.18/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
@@ -296,10 +296,17 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#filter
* @module ng
- * @param {string} name Filter name.
+ * @param {string} name Filter name - this must be a valid angular expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
+ *
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
*/
filter: invokeLater('$filterProvider', 'register'),
diff --git a/xstatic/pkg/angular/data/angular-messages.js b/xstatic/pkg/angular/data/angular-messages.js
index b9cfedb..6d32f5c 100644
--- a/xstatic/pkg/angular/data/angular-messages.js
+++ b/xstatic/pkg/angular/data/angular-messages.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -178,7 +178,7 @@ angular.module('ngMessages', [])
* at a time and this depends on the prioritization of the messages within the template. (This can
* be changed by using the ng-messages-multiple on the directive container.)
*
- * A remote template can also be used to promote message reuseability and messages can also be
+ * A remote template can also be used to promote message reusability and messages can also be
* overridden.
*
* {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
@@ -331,6 +331,9 @@ angular.module('ngMessages', [])
* must be situated since it determines which messages are visible based on the state
* of the provided key/value map that `ngMessages` listens on.
*
+ * More information about using `ngMessage` can be found in the
+ * {@link module:ngMessages `ngMessages` module documentation}.
+ *
* @usage
* ```html
* <!-- using attribute directives -->
@@ -348,8 +351,6 @@ angular.module('ngMessages', [])
* </ng-messages>
* ```
*
- * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`.
- *
* @param {string} ngMessage a string value corresponding to the message key.
*/
.directive('ngMessage', ['$animate', function($animate) {
diff --git a/xstatic/pkg/angular/data/angular-mocks.js b/xstatic/pkg/angular/data/angular-mocks.js
index 3430bb7..dbfd1df 100644
--- a/xstatic/pkg/angular/data/angular-mocks.js
+++ b/xstatic/pkg/angular/data/angular-mocks.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -250,31 +250,31 @@ angular.mock.$ExceptionHandlerProvider = function() {
*
* @param {string} mode Mode of operation, defaults to `rethrow`.
*
- * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
- * is a bug in the application or test, so this mock will make these tests fail.
* - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log`
* mode stores an array of errors in `$exceptionHandler.errors`, to allow later
* assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and
* {@link ngMock.$log#reset reset()}
+ * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there
+ * is a bug in the application or test, so this mock will make these tests fail.
+ * For any implementations that expect exceptions to be thrown, the `rethrow` mode
+ * will also maintain a log of thrown errors.
*/
this.mode = function(mode) {
+
switch (mode) {
- case 'rethrow':
- handler = function(e) {
- throw e;
- };
- break;
case 'log':
+ case 'rethrow':
var errors = [];
-
handler = function(e) {
if (arguments.length == 1) {
errors.push(e);
} else {
errors.push([].slice.call(arguments, 0));
}
+ if (mode === "rethrow") {
+ throw e;
+ }
};
-
handler.errors = errors;
break;
default:
@@ -1283,7 +1283,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1297,7 +1297,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1311,7 +1311,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1327,7 +1327,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1343,7 +1343,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {(string|RegExp|function(string))=} data HTTP request body or function that receives
* data string and returns true if the data is as expected.
* @param {(Object|function(Object))=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1356,7 +1356,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1377,7 +1377,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* is in JSON format.
* @param {(Object|function(Object))=} headers HTTP headers or function that receives http header
* object and returns true if the headers match the current expectation.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*
@@ -1412,7 +1412,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled. See #expect for more info.
*/
@@ -1426,7 +1426,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1440,7 +1440,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1457,7 +1457,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1474,7 +1474,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1491,7 +1491,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
* receives data string and returns true if the data is as expected, or Object if request body
* is in JSON format.
* @param {Object=} headers HTTP headers.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1504,7 +1504,7 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
*
* @param {string|RegExp|function(string)} url HTTP url or function that receives the url
* and returns true if the url match the current definition.
- * @returns {requestHandler} Returns an object with `respond` method that control how a matched
+ * @returns {requestHandler} Returns an object with `respond` method that controls how a matched
* request is handled. You can save this object for later use and invoke `respond` again in
* order to change how a matched request is handled.
*/
@@ -1810,6 +1810,77 @@ angular.mock.$RootElementProvider = function() {
};
/**
+ * @ngdoc service
+ * @name $controller
+ * @description
+ * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing
+ * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}.
+ *
+ *
+ * ## Example
+ *
+ * ```js
+ *
+ * // Directive definition ...
+ *
+ * myMod.directive('myDirective', {
+ * controller: 'MyDirectiveController',
+ * bindToController: {
+ * name: '@'
+ * }
+ * });
+ *
+ *
+ * // Controller definition ...
+ *
+ * myMod.controller('MyDirectiveController', ['log', function($log) {
+ * $log.info(this.name);
+ * })];
+ *
+ *
+ * // In a test ...
+ *
+ * describe('myDirectiveController', function() {
+ * it('should write the bound name to the log', inject(function($controller, $log) {
+ * var ctrl = $controller('MyDirective', { /* no locals &#42;/ }, { name: 'Clark Kent' });
+ * expect(ctrl.name).toEqual('Clark Kent');
+ * expect($log.info.logs).toEqual(['Clark Kent']);
+ * });
+ * });
+ *
+ * ```
+ *
+ * @param {Function|string} constructor If called with a function then it's considered to be the
+ * controller constructor function. Otherwise it's considered to be a string which is used
+ * to retrieve the controller constructor using the following steps:
+ *
+ * * check if a controller with given name is registered via `$controllerProvider`
+ * * check if evaluating the string on the current scope returns a constructor
+ * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global
+ * `window` object (not recommended)
+ *
+ * The string can use the `controller as property` syntax, where the controller instance is published
+ * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this
+ * to work correctly.
+ *
+ * @param {Object} locals Injection locals for Controller.
+ * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used
+ * to simulate the `bindToController` feature and simplify certain kinds of tests.
+ * @return {Object} Instance of given controller.
+ */
+angular.mock.$ControllerDecorator = ['$delegate', function($delegate) {
+ return function(expression, locals, later, ident) {
+ if (later && typeof later === 'object') {
+ var create = $delegate(expression, locals, true, ident);
+ angular.extend(create.instance, later);
+ return create();
+ }
+ return $delegate(expression, locals, later, ident);
+ };
+}];
+
+
+/**
* @ngdoc module
* @name ngMock
* @packageName angular-mocks
@@ -1837,6 +1908,7 @@ angular.module('ngMock', ['ng']).provider({
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
+ $provide.decorator('$controller', angular.mock.$ControllerDecorator);
}]);
/**
@@ -2134,18 +2206,32 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) {
if (window.jasmine || window.mocha) {
var currentSpec = null,
+ annotatedFunctions = [],
isSpecRunning = function() {
return !!currentSpec;
};
+ angular.mock.$$annotate = angular.injector.$$annotate;
+ angular.injector.$$annotate = function(fn) {
+ if (typeof fn === 'function' && !fn.$inject) {
+ annotatedFunctions.push(fn);
+ }
+ return angular.mock.$$annotate.apply(this, arguments);
+ };
+
(window.beforeEach || window.setup)(function() {
+ annotatedFunctions = [];
currentSpec = this;
});
(window.afterEach || window.teardown)(function() {
var injector = currentSpec.$injector;
+ annotatedFunctions.forEach(function(fn) {
+ delete fn.$inject;
+ });
+
angular.forEach(currentSpec.$modules, function(module) {
if (module && module.$$hashKey) {
module.$$hashKey = undefined;
@@ -2158,7 +2244,8 @@ if (window.jasmine || window.mocha) {
if (injector) {
injector.get('$rootElement').off();
- injector.get('$browser').pollFns.length = 0;
+ var $browser = injector.get('$browser');
+ if ($browser.pollFns) $browser.pollFns.length = 0;
}
// clean up jquery's fragment cache
diff --git a/xstatic/pkg/angular/data/angular-resource.js b/xstatic/pkg/angular/data/angular-resource.js
index 7a5eac2..27dc6dc 100644
--- a/xstatic/pkg/angular/data/angular-resource.js
+++ b/xstatic/pkg/angular/data/angular-resource.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -213,6 +213,7 @@ function shallowClearAndCopy(src, dst) {
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])`
* - non-GET instance actions: `instance.$action([parameters], [success], [error])`
*
+ *
* Success callback is called with (value, responseHeaders) arguments. Error callback is called
* with (httpResponse) argument.
*
diff --git a/xstatic/pkg/angular/data/angular-route.js b/xstatic/pkg/angular/data/angular-route.js
index 834429b..6b54cb6 100644
--- a/xstatic/pkg/angular/data/angular-route.js
+++ b/xstatic/pkg/angular/data/angular-route.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -412,7 +412,9 @@ function $RouteProvider() {
* @name $route#$routeChangeSuccess
* @eventType broadcast on root scope
* @description
- * Broadcasted after a route dependencies are resolved.
+ * Broadcasted after a route change has happened successfully.
+ * The `resolve` dependencies are now available in the `current.locals` property.
+ *
* {@link ngRoute.directive:ngView ngView} listens for the directive
* to instantiate the controller and render the view.
*
@@ -440,9 +442,11 @@ function $RouteProvider() {
* @name $route#$routeUpdate
* @eventType broadcast on root scope
* @description
- *
* The `reloadOnSearch` property has been set to false, and we are reusing the same
* instance of the Controller.
+ *
+ * @param {Object} angularEvent Synthetic event object
+ * @param {Route} current Current/previous route information.
*/
var forceReload = false,
@@ -482,21 +486,15 @@ function $RouteProvider() {
* definitions will be interpolated into the location's path, while
* remaining properties will be treated as query params.
*
- * @param {Object} newParams mapping of URL parameter names to values
+ * @param {!Object<string, string>} newParams mapping of URL parameter names to values
*/
updateParams: function(newParams) {
if (this.current && this.current.$$route) {
- var searchParams = {}, self=this;
-
- angular.forEach(Object.keys(newParams), function(key) {
- if (!self.current.pathParams[key]) searchParams[key] = newParams[key];
- });
-
newParams = angular.extend({}, this.current.params, newParams);
$location.path(interpolate(this.current.$$route.originalPath, newParams));
- $location.search(angular.extend({}, $location.search(), searchParams));
- }
- else {
+ // interpolate modifies newParams, only query params are left
+ $location.search(newParams);
+ } else {
throw $routeMinErr('norout', 'Tried updating route when with no current route');
}
}
@@ -600,9 +598,8 @@ function $RouteProvider() {
if (angular.isFunction(templateUrl)) {
templateUrl = templateUrl(nextRoute.params);
}
- templateUrl = $sce.getTrustedResourceUrl(templateUrl);
if (angular.isDefined(templateUrl)) {
- nextRoute.loadedTemplateUrl = templateUrl;
+ nextRoute.loadedTemplateUrl = $sce.valueOf(templateUrl);
template = $templateRequest(templateUrl);
}
}
diff --git a/xstatic/pkg/angular/data/angular-sanitize.js b/xstatic/pkg/angular/data/angular-sanitize.js
index 4b0edac..e128391 100644
--- a/xstatic/pkg/angular/data/angular-sanitize.js
+++ b/xstatic/pkg/angular/data/angular-sanitize.js
@@ -1,10 +1,21 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
@@ -276,14 +287,14 @@ function htmlParser(html, handler) {
}
}
var index, chars, match, stack = [], last = html, text;
- stack.last = function() { return stack[ stack.length - 1 ]; };
+ stack.last = function() { return stack[stack.length - 1]; };
while (html) {
text = '';
chars = true;
// Make sure we're not in a script or style element
- if (!stack.last() || !specialElements[ stack.last() ]) {
+ if (!stack.last() || !specialElements[stack.last()]) {
// Comment
if (html.indexOf("<!--") === 0) {
@@ -341,7 +352,8 @@ function htmlParser(html, handler) {
}
} else {
- html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
+ // IE versions 9 and 10 do not understand the regex '[^]', so using a workaround with [\W\w].
+ html = html.replace(new RegExp("([\\W\\w]*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text) {
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
@@ -365,17 +377,17 @@ function htmlParser(html, handler) {
function parseStartTag(tag, tagName, rest, unary) {
tagName = angular.lowercase(tagName);
- if (blockElements[ tagName ]) {
- while (stack.last() && inlineElements[ stack.last() ]) {
+ if (blockElements[tagName]) {
+ while (stack.last() && inlineElements[stack.last()]) {
parseEndTag("", stack.last());
}
}
- if (optionalEndTagElements[ tagName ] && stack.last() == tagName) {
+ if (optionalEndTagElements[tagName] && stack.last() == tagName) {
parseEndTag("", tagName);
}
- unary = voidElements[ tagName ] || !!unary;
+ unary = voidElements[tagName] || !!unary;
if (!unary)
stack.push(tagName);
@@ -400,13 +412,13 @@ function htmlParser(html, handler) {
if (tagName)
// Find the closest opened tag of the same type
for (pos = stack.length - 1; pos >= 0; pos--)
- if (stack[ pos ] == tagName)
+ if (stack[pos] == tagName)
break;
if (pos >= 0) {
// Close all the open elements, up the stack
for (i = stack.length - 1; i >= pos; i--)
- if (handler.end) handler.end(stack[ i ]);
+ if (handler.end) handler.end(stack[i]);
// Remove the open elements from the stack
stack.length = pos;
@@ -415,7 +427,6 @@ function htmlParser(html, handler) {
}
var hiddenPre=document.createElement("pre");
-var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
@@ -424,22 +435,10 @@ var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
function decodeEntities(value) {
if (!value) { return ''; }
- // Note: IE8 does not preserve spaces at the start/end of innerHTML
- // so we must capture them and reattach them afterward
- var parts = spaceRe.exec(value);
- var spaceBefore = parts[1];
- var spaceAfter = parts[3];
- var content = parts[2];
- if (content) {
- hiddenPre.innerHTML=content.replace(/</g,"&lt;");
- // innerText depends on styling as it doesn't display hidden elements.
- // Therefore, it's better to use textContent not to cause unnecessary
- // reflows. However, IE<9 don't support textContent so the innerText
- // fallback is necessary.
- content = 'textContent' in hiddenPre ?
- hiddenPre.textContent : hiddenPre.innerText;
- }
- return spaceBefore + content + spaceAfter;
+ hiddenPre.innerHTML = value.replace(/</g,"&lt;");
+ // innerText depends on styling as it doesn't display hidden elements.
+ // Therefore, it's better to use textContent not to cause unnecessary reflows.
+ return hiddenPre.textContent;
}
/**
@@ -628,8 +627,8 @@ angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
- /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/,
- MAILTO_REGEXP = /^mailto:/;
+ /((ftp|https?):\/\/|(www\.)|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"”’]/i,
+ MAILTO_REGEXP = /^mailto:/i;
return function(text, target) {
if (!text) return text;
diff --git a/xstatic/pkg/angular/data/angular-scenario.js b/xstatic/pkg/angular/data/angular-scenario.js
index d0614ab..5a2826e 100644
--- a/xstatic/pkg/angular/data/angular-scenario.js
+++ b/xstatic/pkg/angular/data/angular-scenario.js
@@ -9190,7 +9190,7 @@ return jQuery;
}));
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -9246,7 +9246,7 @@ function minErr(module, ErrorConstructor) {
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.7/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.18/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
@@ -9341,6 +9341,7 @@ function minErr(module, ErrorConstructor) {
createMap: true,
NODE_TYPE_ELEMENT: true,
+ NODE_TYPE_ATTRIBUTE: true,
NODE_TYPE_TEXT: true,
NODE_TYPE_COMMENT: true,
NODE_TYPE_DOCUMENT: true,
@@ -9452,7 +9453,9 @@ function isArrayLike(obj) {
return false;
}
- var length = obj.length;
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // "length" in obj used to prevent JIT error (gh-11508)
+ var length = "length" in Object(obj) && obj.length;
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
@@ -9573,8 +9576,7 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
- }
- else {
+ } else {
delete obj.$$hashKey;
}
}
@@ -9657,6 +9659,8 @@ noop.$inject = [];
return (transformationFn || angular.identity)(value);
};
```
+ * @param {*} value to be returned.
+ * @returns {*} the value passed in.
*/
function identity($) {return $;}
identity.$inject = [];
@@ -9737,6 +9741,12 @@ function isString(value) {return typeof value === 'string';}
* @description
* Determines if a reference is a `Number`.
*
+ * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
+ *
+ * If you wish to exclude these then you can use the native
+ * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
+ * method.
+ *
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
@@ -9881,7 +9891,7 @@ function isElement(node) {
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for (i = 0; i < items.length; i++)
- obj[ items[i] ] = true;
+ obj[items[i]] = true;
return obj;
}
@@ -9911,7 +9921,7 @@ function arrayRemove(array, value) {
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
- * * If a destination is provided, all of its elements (for array) or properties (for objects)
+ * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
* are deleted and then all elements/properties from the source are copied to it.
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
* * If `source` is identical to 'destination' an exception will be thrown.
@@ -10105,10 +10115,11 @@ function equals(o1, o2) {
} else if (isDate(o1)) {
if (!isDate(o2)) return false;
return equals(o1.getTime(), o2.getTime());
- } else if (isRegExp(o1) && isRegExp(o2)) {
- return o1.toString() == o2.toString();
+ } else if (isRegExp(o1)) {
+ return isRegExp(o2) ? o1.toString() == o2.toString() : false;
} else {
- if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
+ if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
+ isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
keySet = {};
for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
@@ -10226,8 +10237,8 @@ function toJsonReplacer(key, value) {
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
- * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
- * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
+ * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
+ * If set to an integer, the JSON output will contain that many spaces per indentation.
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
@@ -10249,7 +10260,7 @@ function toJson(obj, pretty) {
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
- * @returns {Object|Array|string|number} Deserialized thingy.
+ * @returns {Object|Array|string|number} Deserialized JSON string.
*/
function fromJson(json) {
return isString(json)
@@ -10422,7 +10433,7 @@ function getNgAttribute(element, ngAttr) {
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
*
* You can specify an **AngularJS module** to be used as the root module for the application. This
- * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
+ * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
* should contain the application code needed or have dependencies on other modules that will
* contain the code. See {@link angular.module} for more information.
*
@@ -10430,7 +10441,7 @@ function getNgAttribute(element, ngAttr) {
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
* would not be resolved to `3`.
*
- * `ngApp` is the easiest, and most common, way to bootstrap an application.
+ * `ngApp` is the easiest, and most common way to bootstrap an application.
*
<example module="ngAppDemo">
<file name="index.html">
@@ -10592,7 +10603,7 @@ function angularInit(element, bootstrap) {
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
- * function that will be invoked by the injector as a run block.
+ * function that will be invoked by the injector as a `config` block.
* See: {@link angular.module modules}
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
@@ -10662,8 +10673,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
- doBootstrap();
+ return doBootstrap();
};
+
+ if (isFunction(angular.resumeDeferredBootstrap)) {
+ angular.resumeDeferredBootstrap();
+ }
}
/**
@@ -10855,6 +10870,7 @@ function createMap() {
}
var NODE_TYPE_ELEMENT = 1;
+var NODE_TYPE_ATTRIBUTE = 2;
var NODE_TYPE_TEXT = 3;
var NODE_TYPE_COMMENT = 8;
var NODE_TYPE_DOCUMENT = 9;
@@ -11091,10 +11107,17 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#filter
* @module ng
- * @param {string} name Filter name.
+ * @param {string} name Filter name - this must be a valid angular expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
+ *
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
*/
filter: invokeLater('$filterProvider', 'register'),
@@ -11308,11 +11331,11 @@ function toDebugString(obj) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.7', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.18', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
- dot: 7,
- codeName: 'leaky-obstruction'
+ dot: 18,
+ codeName: 'collective-penmanship'
};
@@ -11449,6 +11472,17 @@ function publishExternalAPI(angular) {
]);
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
/* global JQLitePrototype: true,
addEventListenerFn: true,
removeEventListenerFn: true,
@@ -11477,7 +11511,7 @@ function publishExternalAPI(angular) {
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
* commonly needed functionality with the goal of having a very small footprint.</div>
*
- * To use jQuery, simply load it before `DOMContentLoaded` event fired.
+ * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
*
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
* jqLite; they are never raw DOM references.</div>
@@ -11493,7 +11527,7 @@ function publishExternalAPI(angular) {
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
@@ -12036,6 +12070,10 @@ forEach({
},
attr: function(element, name, value) {
+ var nodeType = element.nodeType;
+ if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
+ return;
+ }
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
@@ -12726,7 +12764,7 @@ function annotate(fn, strictDi, name) {
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
- * @param {string} caller An optional string to provide the origin of the function call for error messages.
+ * @param {string=} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
@@ -12737,8 +12775,8 @@ function annotate(fn, strictDi, name) {
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
- * @param {!Function} fn The function to invoke. Function parameters are injected according to the
- * {@link guide/di $inject Annotation} rules.
+ * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
+ * injected according to the {@link guide/di $inject Annotation} rules.
* @param {Object=} self The `this` for the invoked method.
* @param {Object=} locals Optional object. If preset then any argument names are read from this
* object first, before the `$injector` is consulted.
@@ -13005,8 +13043,8 @@ function annotate(fn, strictDi, name) {
* configure your service in a provider.
*
* @param {string} name The name of the instance.
- * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
- * for `$provide.provider(name, {$get: $getFn})`.
+ * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
+ * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
*
* @example
@@ -13041,7 +13079,8 @@ function annotate(fn, strictDi, name) {
* as a type/class.
*
* @param {string} name The name of the instance.
- * @param {Function} constructor A class (constructor function) that will be instantiated.
+ * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
+ * that will be instantiated.
* @returns {Object} registered provider instance
*
* @example
@@ -13140,7 +13179,7 @@ function annotate(fn, strictDi, name) {
* object which replaces or wraps and delegates to the original service.
*
* @param {string} name The name of the service to decorate.
- * @param {function()} decorator This function will be invoked when the service needs to be
+ * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance. The function is called using
* the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
* Local injection arguments:
@@ -13347,7 +13386,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
- $inject = annotate(fn, strictDi, serviceName),
+ $inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -13376,7 +13415,7 @@ function createInjector(modulesToLoad, strictDi) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
- var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
@@ -13386,7 +13425,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
- annotate: annotate,
+ annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
@@ -13872,6 +13911,7 @@ var $AnimateProvider = ['$provide', function($provide) {
* @return {Promise} the animation callback promise
*/
leave: function(element, options) {
+ applyStyles(element, options);
element.remove();
return asyncPromise();
},
@@ -14106,7 +14146,7 @@ function Browser(window, document, $log, $sniffer) {
function getHash(url) {
var index = url.indexOf('#');
- return index === -1 ? '' : url.substr(index + 1);
+ return index === -1 ? '' : url.substr(index);
}
/**
@@ -14233,7 +14273,7 @@ function Browser(window, document, $log, $sniffer) {
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState;
} else {
- if (!sameBase) {
+ if (!sameBase || reloadLocation) {
reloadLocation = url;
}
if (replace) {
@@ -14276,11 +14316,19 @@ function Browser(window, document, $log, $sniffer) {
fireUrlChange();
}
+ function getCurrentState() {
+ try {
+ return history.state;
+ } catch (e) {
+ // MSIE can reportedly throw when there is no state (UNCONFIRMED).
+ }
+ }
+
// This variable should be used *only* inside the cacheState function.
var lastCachedState = null;
function cacheState() {
// This should be the only place in $browser where `history.state` is read.
- cachedState = window.history.state;
+ cachedState = getCurrentState();
cachedState = isUndefined(cachedState) ? null : cachedState;
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
@@ -14877,7 +14925,7 @@ function $CacheFactoryProvider() {
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
* element with ng-app attribute), otherwise the template will be ignored.
*
- * Adding via the $templateCache service:
+ * Adding via the `$templateCache` service:
*
* ```js
* var myApp = angular.module('myApp', []);
@@ -14905,6 +14953,17 @@ function $TemplateCacheProvider() {
}];
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
*
* DOM-related variables:
@@ -14969,7 +15028,8 @@ function $TemplateCacheProvider() {
* templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
- * controllerAs: 'stringAlias',
+ * controllerAs: 'stringIdentifier',
+ * bindToController: false,
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -15116,7 +15176,8 @@ function $TemplateCacheProvider() {
* Require another directive and inject its controller as the fourth argument to the linking function. The
* `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
* injected argument will be an array in corresponding order. If no such directive can be
- * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
+ * found, or if the directive does not have a controller, then an error is raised (unless no link function
+ * is specified, in which case error checking is skipped). The name can be prefixed with:
*
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
@@ -15287,9 +15348,15 @@ function $TemplateCacheProvider() {
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
* between all directive linking functions.
*
- * * `controller` - a controller instance - A controller instance if at least one directive on the
- * element defines a controller. The controller is shared among all the directives, which allows
- * the directives to use the controllers as a communication channel.
+ * * `controller` - the directive's required controller instance(s) - Instances are shared
+ * among all directives, which allows the directives to use the controllers as a communication
+ * channel. The exact value depends on the directive's `require` property:
+ * * `string`: the controller instance
+ * * `array`: array of controller instances
+ * * no controller(s) required: `undefined`
+ *
+ * If a required controller cannot be found, and it is optional, the instance is `null`,
+ * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
*
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
* This is the same as the `$transclude`
@@ -15315,7 +15382,7 @@ function $TemplateCacheProvider() {
*
* ### Transclusion
*
- * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
+ * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
* copying them to another part of the DOM, while maintaining their connection to the original AngularJS
* scope from where they were taken.
*
@@ -15643,6 +15710,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return bindings;
}
+ function assertValidDirectiveName(name) {
+ var letter = name.charAt(0);
+ if (!letter || letter !== lowercase(letter)) {
+ throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
+ }
+ return name;
+ }
+
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -15661,6 +15736,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
this.directive = function registerDirective(name, directiveFactory) {
assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
+ assertValidDirectiveName(name);
assertArg(directiveFactory, 'directiveFactory');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
@@ -16355,6 +16431,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// use class as directive
className = node.className;
+ if (isObject(className)) {
+ // Maybe SVGAnimatedString
+ className = className.animVal;
+ }
if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
@@ -17056,8 +17136,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
- // The fact that we have to copy and patch the directive seems wrong!
- derivedSyncDirective = extend({}, origAsyncDirective, {
+ derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
@@ -17067,7 +17146,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$compileNode.empty();
- $templateRequest($sce.getTrustedResourceUrl(templateUrl))
+ $templateRequest(templateUrl)
.then(function(content) {
var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
@@ -17510,6 +17589,8 @@ function removeComments(jqNodes) {
return jqNodes;
}
+var $controllerMinErr = minErr('$controller');
+
/**
* @ngdoc provider
* @name $controllerProvider
@@ -17597,7 +17678,12 @@ function $ControllerProvider() {
}
if (isString(expression)) {
- match = expression.match(CNTRL_REG),
+ match = expression.match(CNTRL_REG);
+ if (!match) {
+ throw $controllerMinErr('ctrlfmt',
+ "Badly formed controller string '{0}'. " +
+ "Must match `__name__ as __id__` or `__name__`.", expression);
+ }
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
@@ -17621,7 +17707,7 @@ function $ControllerProvider() {
// Object creation: http://jsperf.com/create-constructor/2
var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
- instance = Object.create(controllerPrototype);
+ instance = Object.create(controllerPrototype || null);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
@@ -18107,7 +18193,7 @@ function $HttpProvider() {
* headers: {
* 'Content-Type': undefined
* },
- * data: { test: 'test' },
+ * data: { test: 'test' }
* }
*
* $http(req).success(function(){...}).error(function(){...});
@@ -18117,7 +18203,7 @@ function $HttpProvider() {
*
* Both requests and responses can be transformed using transformation functions: `transformRequest`
* and `transformResponse`. These properties can be a single function that returns
- * the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions,
+ * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
*
* ### Default Transformations
@@ -18542,6 +18628,8 @@ function $HttpProvider() {
}
promise.success = function(fn) {
+ assertArgFn(fn, 'fn');
+
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -18549,6 +18637,8 @@ function $HttpProvider() {
};
promise.error = function(fn) {
+ assertArgFn(fn, 'fn');
+
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -18950,7 +19040,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
xhr.onload = function requestLoaded() {
var statusText = xhr.statusText || '';
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
+ // responseText is the old-school way of retrieving response (supported by IE9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
var response = ('response' in xhr) ? xhr.response : xhr.responseText;
@@ -19029,7 +19119,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
};
function jsonpReq(url, callbackId, done) {
- // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
+ // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'), callback = null;
@@ -19656,7 +19746,15 @@ function $LocaleProvider() {
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
- shortTime: 'h:mm a'
+ shortTime: 'h:mm a',
+ ERANAMES: [
+ "Before Christ",
+ "Anno Domini"
+ ],
+ ERAS: [
+ "BC",
+ "AD"
+ ]
},
pluralCat: function(num) {
@@ -19854,7 +19952,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
var withoutHashUrl;
- if (withoutBaseUrl.charAt(0) === '#') {
+ if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
// The rest of the url starts with a hash so we have
// got either a hashbang path or a plain hash fragment
@@ -19868,7 +19966,15 @@ function LocationHashbangUrl(appBase, hashPrefix) {
// There was no hashbang path nor hash fragment:
// If we are in HTML5 mode we use what is left as the path;
// Otherwise we ignore what is left
- withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
+ if (this.$$html5) {
+ withoutHashUrl = withoutBaseUrl;
+ } else {
+ withoutHashUrl = '';
+ if (isUndefined(withoutBaseUrl)) {
+ appBase = url;
+ this.replace();
+ }
+ }
}
parseAppUrl(withoutHashUrl, this);
@@ -19978,7 +20084,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#'
+ // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
this.$$absUrl = appBase + hashPrefix + this.$$url;
};
@@ -20082,11 +20188,19 @@ var locationPrototype = {
*
* Return host of current url.
*
+ * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
+ *
*
* ```js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var host = $location.host();
* // => "example.com"
+ *
+ * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
+ * host = $location.host();
+ * // => "example.com"
+ * host = location.host;
+ * // => "example.com:8080"
* ```
*
* @return {string} host of current url.
@@ -20506,7 +20620,7 @@ function $LocationProvider() {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
- if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
+ if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
var elm = jqLite(event.target);
@@ -20548,7 +20662,7 @@ function $LocationProvider() {
// rewrite hashbang url <> html5 url
- if ($location.absUrl() != initialUrl) {
+ if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
$browser.url($location.absUrl(), true);
}
@@ -20664,6 +20778,7 @@ function $LocationProvider() {
<button ng-click="$log.warn(message)">warn</button>
<button ng-click="$log.info(message)">info</button>
<button ng-click="$log.error(message)">error</button>
+ <button ng-click="$log.debug(message)">debug</button>
</div>
</file>
</example>
@@ -20794,6 +20909,17 @@ function $LogProvider() {
}];
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $parseMinErr = minErr('$parse');
// Sandboxing Angular Expressions
@@ -21458,7 +21584,7 @@ Parser.prototype = {
}, {
assign: function(scope, value, locals) {
var o = object(scope, locals);
- if (!o) object.assign(scope, o = {});
+ if (!o) object.assign(scope, o = {}, locals);
return getter.assign(o, value);
}
});
@@ -21484,7 +21610,7 @@ Parser.prototype = {
var key = ensureSafeMemberName(indexFn(self, locals), expression);
// prevent overwriting of Function.constructor which would break ensureSafeObject check
var o = ensureSafeObject(obj(self, locals), expression);
- if (!o) obj.assign(self, o = {});
+ if (!o) obj.assign(self, o = {}, locals);
return o[key] = value;
}
});
@@ -21522,6 +21648,11 @@ Parser.prototype = {
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
+ if (args) {
+ // Free-up the memory (arguments of the last function call).
+ args.length = 0;
+ }
+
return ensureSafeObject(v, expressionText);
};
},
@@ -21594,18 +21725,19 @@ Parser.prototype = {
// Parser helper functions
//////////////////////////////////////////////////
-function setter(obj, path, setValue, fullExp) {
+function setter(obj, locals, path, setValue, fullExp) {
ensureSafeObject(obj, fullExp);
+ ensureSafeObject(locals, fullExp);
var element = path.split('.'), key;
for (var i = 0; element.length > 1; i++) {
key = ensureSafeMemberName(element.shift(), fullExp);
- var propertyObj = ensureSafeObject(obj[key], fullExp);
+ var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
if (!propertyObj) {
propertyObj = {};
obj[key] = propertyObj;
}
- obj = propertyObj;
+ obj = ensureSafeObject(propertyObj, fullExp);
}
key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj[key], fullExp);
@@ -21732,8 +21864,8 @@ function getterFn(path, options, fullExp) {
}
fn.sharedGetter = true;
- fn.assign = function(self, value) {
- return setter(self, path, value, path);
+ fn.assign = function(self, value, locals) {
+ return setter(self, locals, path, value, path);
};
getterFnCache[path] = fn;
return fn;
@@ -22392,8 +22524,7 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
- }
- else {
+ } else {
this.$$resolve(val);
}
@@ -22624,7 +22755,7 @@ function $$RAFProvider() { //rAF
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
- var raf = rafSupported
+ var rafFn = rafSupported
? function(fn) {
var id = requestAnimationFrame(fn);
return function() {
@@ -22638,9 +22769,47 @@ function $$RAFProvider() { //rAF
};
};
- raf.supported = rafSupported;
+ queueFn.supported = rafSupported;
+
+ var cancelLastRAF;
+ var taskCount = 0;
+ var taskQueue = [];
+ return queueFn;
+
+ function flush() {
+ for (var i = 0; i < taskQueue.length; i++) {
+ var task = taskQueue[i];
+ if (task) {
+ taskQueue[i] = null;
+ task();
+ }
+ }
+ taskCount = taskQueue.length = 0;
+ }
+
+ function queueFn(asyncFn) {
+ var index = taskQueue.length;
- return raf;
+ taskCount++;
+ taskQueue.push(asyncFn);
+
+ if (index === 0) {
+ cancelLastRAF = rafFn(flush);
+ }
+
+ return function cancelQueueFn() {
+ if (index >= 0) {
+ taskQueue[index] = null;
+ index = null;
+
+ if (--taskCount === 0 && cancelLastRAF) {
+ cancelLastRAF();
+ cancelLastRAF = null;
+ taskQueue.length = 0;
+ }
+ }
+ };
+ }
}];
}
@@ -22724,9 +22893,26 @@ function $RootScopeProvider() {
return TTL;
};
+ function createChildScopeClass(parent) {
+ function ChildScope() {
+ this.$$watchers = this.$$nextSibling =
+ this.$$childHead = this.$$childTail = null;
+ this.$$listeners = {};
+ this.$$listenerCount = {};
+ this.$id = nextUid();
+ this.$$ChildScope = null;
+ }
+ ChildScope.prototype = parent;
+ return ChildScope;
+ }
+
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
function($injector, $exceptionHandler, $parse, $browser) {
+ function destroyChildScope($event) {
+ $event.currentScope.$$destroyed = true;
+ }
+
/**
* @ngdoc type
* @name $rootScope.Scope
@@ -22735,12 +22921,9 @@ function $RootScopeProvider() {
* A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
* {@link auto.$injector $injector}. Child scopes are created using the
* {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
- * compiled HTML template is executed.)
+ * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
+ * an in-depth introduction and usage examples.
*
- * Here is a simple scope snippet to show how you can interact with the scope.
- * ```html
- * <file src="./test/ng/rootScopeSpec.js" tag="docs1" />
- * ```
*
* # Inheritance
* A scope can inherit from a parent scope, as in this example:
@@ -22849,15 +23032,7 @@ function $RootScopeProvider() {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
- this.$$ChildScope = function ChildScope() {
- this.$$watchers = this.$$nextSibling =
- this.$$childHead = this.$$childTail = null;
- this.$$listeners = {};
- this.$$listenerCount = {};
- this.$id = nextUid();
- this.$$ChildScope = null;
- };
- this.$$ChildScope.prototype = this;
+ this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
@@ -22875,13 +23050,9 @@ function $RootScopeProvider() {
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
- if (isolate || parent != this) child.$on('$destroy', destroyChild);
+ if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
return child;
-
- function destroyChild() {
- child.$$destroyed = true;
- }
},
/**
@@ -22913,9 +23084,9 @@ function $RootScopeProvider() {
*
*
* If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
- * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
- * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a
- * change is detected, be prepared for multiple calls to your listener.)
+ * you can register a `watchExpression` function with no `listener`. (Be prepared for
+ * multiple calls to your `watchExpression` because it will execute multiple times in a
+ * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
*
* After a watcher is registered with the scope, the `listener` fn is called asynchronously
* (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
@@ -23689,7 +23860,7 @@ function $RootScopeProvider() {
* @kind function
*
* @description
- * Schedule the invokation of $apply to occur at a later time. The actual time difference
+ * Schedule the invocation of $apply to occur at a later time. The actual time difference
* varies across browsers, but is typically around ~10 milliseconds.
*
* This can be used to queue up multiple expressions which need to be evaluated in the same
@@ -24042,6 +24213,17 @@ function $$SanitizeUriProvider() {
};
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $sceMinErr = minErr('$sce');
var SCE_CONTEXTS = {
@@ -24576,7 +24758,7 @@ function $SceDelegateProvider() {
* characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
* in a whitelist.
* - `**`: matches zero or more occurrences 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.
+ * 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.) Its usage at the very end of the path is ok. (e.g.
* http://foo.example.com/templates/**).
@@ -24584,11 +24766,11 @@ function $SceDelegateProvider() {
* - *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
@@ -24598,7 +24780,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
@@ -25184,12 +25366,14 @@ var $compileMinErr = minErr('$compile');
* @name $templateRequest
*
* @description
- * The `$templateRequest` service 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 then a `$compile` error will be thrown (the exception can be thwarted
- * by setting the 2nd parameter of the function to true).
- *
- * @param {string} tpl The HTTP request template URL
+ * 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} the HTTP Promise for the given.
@@ -25197,10 +25381,18 @@ var $compileMinErr = minErr('$compile');
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
*/
function $TemplateRequestProvider() {
- this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) {
+ this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
function handleRequestFn(tpl, ignoreRequestError) {
- var self = handleRequestFn;
- self.totalPendingRequests++;
+ 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;
@@ -25218,13 +25410,14 @@ function $TemplateRequestProvider() {
};
return $http.get(tpl, httpOptions)
+ ['finally'](function() {
+ handleRequestFn.totalPendingRequests--;
+ })
.then(function(response) {
- self.totalPendingRequests--;
return response.data;
}, handleError);
function handleError(resp) {
- self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
@@ -25461,20 +25654,13 @@ var originUrl = urlResolve(window.location.href);
*
* 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
@@ -25604,6 +25790,13 @@ function $WindowProvider() {
* 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.
*
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
+ *
* ```js
* // Filter registration
* function MyModule($provide, $filterProvider) {
@@ -25685,6 +25878,13 @@ function $FilterProvider($provide) {
* @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.
+ *
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
* of the registered filter instances.
*/
@@ -25746,19 +25946,26 @@ 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. 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`
+ * 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".
*
+ * 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)`: 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.
@@ -25771,10 +25978,10 @@ function $FilterProvider($provide) {
*
* - `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.
+ * 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.
@@ -25851,14 +26058,16 @@ function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
+ var expressionType = (expression !== null) ? typeof expression : 'null';
var predicateFn;
var matchAgainstAnyProp;
- switch (typeof expression) {
+ switch (expressionType) {
case 'function':
predicateFn = expression;
break;
case 'boolean':
+ case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
@@ -25877,12 +26086,21 @@ function filterFilter() {
// 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(actual) || isObject(expected)) {
// Prevent an object to be considered equal to a string like `'[object'`
return false;
@@ -25895,19 +26113,22 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
}
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) {
- var actualType = typeof actual;
- var expectedType = typeof expected;
+function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
+ var actualType = (actual !== null) ? typeof actual : 'null';
+ var expectedType = (expected !== null) ? typeof expected : 'null';
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
- } else if (actualType === 'array') {
+ } 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) {
@@ -25920,21 +26141,21 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
var key;
if (matchAgainstAnyProp) {
for (key in actual) {
- if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
return true;
}
}
- return false;
+ return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
} else if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
- if (isFunction(expectedVal)) {
+ if (isFunction(expectedVal) || isUndefined(expectedVal)) {
continue;
}
- var keyIsDollar = key === '$';
- var actualVal = keyIsDollar ? actual : actual[key];
- if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
+ var matchAnyProperty = key === '$';
+ var actualVal = matchAnyProperty ? actual : actual[key];
+ if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
return false;
}
}
@@ -26030,6 +26251,8 @@ function currencyFilter($locale) {
* @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.
@@ -26242,6 +26465,14 @@ 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),
@@ -26268,10 +26499,14 @@ var DATE_FORMATS = {
a: ampmGetter,
Z: timeZoneGetter,
ww: weekGetter(2),
- w: weekGetter(1)
+ w: weekGetter(1),
+ G: eraGetter,
+ GG: eraGetter,
+ GGG: eraGetter,
+ GGGG: longEraGetter
};
-var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
+var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
NUMBER_STRING = /^\-?\d+$/;
/**
@@ -26303,11 +26538,13 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'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)
+ * * `'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}:
@@ -26598,37 +26835,12 @@ function limitToFilter() {
limit = int(limit);
}
- if (isString(input)) {
- //NaN check on limit
- if (limit) {
- return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
- } else {
- return "";
- }
- }
-
- 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;
+ //NaN check on limit
+ if (limit) {
+ return limit > 0 ? input.slice(0, limit) : input.slice(limit);
} else {
- i = input.length + limit;
- n = input.length;
- }
-
- for (; i < n; i++) {
- out.push(input[i]);
+ return isString(input) ? "" : [];
}
-
- return out;
};
}
@@ -26649,7 +26861,7 @@ function limitToFilter() {
* Can be one of:
*
* - `function`: Getter function. The result of this function will be sorted using the
- * `<`, `=`, `>` operator.
+ * `<`, `===`, `>` 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
@@ -26666,6 +26878,43 @@ function limitToFilter() {
* @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`.
+ <example module="orderByExample">
+ <file name="index.html">
+ <script>
+ angular.module('orderByExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $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}];
+ }]);
+ </script>
+ <div ng-controller="ExampleController">
+ <table class="friend">
+ <tr>
+ <th>Name</th>
+ <th>Phone Number</th>
+ <th>Age</th>
+ </tr>
+ <tr ng-repeat="friend in friends | orderBy:'-age'">
+ <td>{{friend.name}}</td>
+ <td>{{friend.phone}}</td>
+ <td>{{friend.age}}</td>
+ </tr>
+ </table>
+ </div>
+ </file>
+ </example>
+ *
+ * The predicate and reverse parameters can be controlled dynamically through scope properties,
+ * as shown in the next example.
* @example
<example module="orderByExample">
<file name="index.html">
@@ -26762,9 +27011,7 @@ function orderByFilter($parse) {
}
if (predicate === '') {
// Effectively no predicate was passed so we compare identity
- return reverseComparator(function(a, b) {
- return compare(a, b);
- }, descending);
+ return reverseComparator(compare, descending);
}
get = $parse(predicate);
if (get.constant) {
@@ -26806,14 +27053,14 @@ function orderByFilter($parse) {
function objectToString(value) {
if (value === null) return 'null';
- if (typeof value.toString === 'function') {
- value = value.toString();
- if (isPrimitive(value)) return value;
- }
if (typeof value.valueOf === 'function') {
value = value.valueOf();
if (isPrimitive(value)) return value;
}
+ if (typeof value.toString === 'function') {
+ value = value.toString();
+ if (isPrimitive(value)) return value;
+ }
return '';
}
@@ -26866,6 +27113,9 @@ var htmlAnchorDirective = valueFn({
compile: function(element, attr) {
if (!attr.href && !attr.xlinkHref && !attr.name) {
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';
@@ -27039,20 +27289,24 @@ var htmlAnchorDirective = valueFn({
*
* @description
*
- * We shouldn't do this, because it 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.
+ *
+ * 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
- * <div ng-init="scope = { isDisabled: false }">
- * <button disabled="{{scope.isDisabled}}">Disabled</button>
+ * <!-- See below for an example of ng-disabled being used correctly -->
+ * <div ng-init="isDisabled = false">
+ * <button disabled="{{isDisabled}}">Disabled</button>
* </div>
* ```
*
- * 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.)
+ * 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
<example>
@@ -27071,7 +27325,7 @@ var htmlAnchorDirective = valueFn({
*
* @element INPUT
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
- * then special attribute "disabled" will be set on the element
+ * then the `disabled` attribute will be set on the element
*/
@@ -27466,6 +27720,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
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);
};
@@ -27483,23 +27740,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
addSetValidityMethod({
ctrl: this,
$element: element,
- set: function(object, property, control) {
+ set: function(object, property, controller) {
var list = object[property];
if (!list) {
- object[property] = [control];
+ object[property] = [controller];
} else {
- var index = list.indexOf(control);
+ var index = list.indexOf(controller);
if (index === -1) {
- list.push(control);
+ list.push(controller);
}
}
},
- unset: function(object, property, control) {
+ unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
- arrayRemove(list, control);
+ arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
@@ -27616,7 +27873,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
*
* # Alias: {@link ng.directive:ngForm `ngForm`}
*
- * In Angular forms can be nested. This means that the outer form is valid when all of the child
+ * In Angular, forms can be nested. This means that the outer form is valid when all of the child
* forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
* `<form>` but can be nested. This allows you to have nested forms, which is very useful when
@@ -27715,11 +27972,11 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
<form name="myForm" ng-controller="FormController" class="my-form">
userType: <input name="input" ng-model="userType" required>
<span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
- <tt>userType = {{userType}}</tt><br>
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ <code>userType = {{userType}}</code><br>
+ <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
+ <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
+ <code>myForm.$valid = {{myForm.$valid}}</code><br>
+ <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
</form>
</file>
<file name="protractor.js" type="protractor">
@@ -27754,10 +28011,12 @@ var formDirectiveFactory = function(isNgForm) {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
controller: FormController,
- compile: function ngFormCompile(formElement) {
+ compile: function ngFormCompile(formElement, attr) {
// Setup initial state of the control
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
+ var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
+
return {
pre: function ngFormPreLink(scope, formElement, attr, controller) {
// if `action` attr is not present on the form, prevent the default action (submission)
@@ -27788,23 +28047,21 @@ var formDirectiveFactory = function(isNgForm) {
});
}
- var parentFormCtrl = controller.$$parentForm,
- alias = controller.$name;
-
- if (alias) {
- setter(scope, alias, controller, alias);
- attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
- if (alias === newValue) return;
- setter(scope, alias, undefined, alias);
- alias = newValue;
- setter(scope, alias, controller, alias);
- parentFormCtrl.$$renameControl(controller, alias);
+ var parentFormCtrl = controller.$$parentForm;
+
+ if (nameAttr) {
+ setter(scope, null, controller.$name, controller, controller.$name);
+ attr.$observe(nameAttr, function(newValue) {
+ if (controller.$name === newValue) return;
+ setter(scope, null, controller.$name, undefined, controller.$name);
+ parentFormCtrl.$$renameControl(controller, newValue);
+ setter(scope, null, controller.$name, controller, controller.$name);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
- if (alias) {
- setter(scope, alias, undefined, alias);
+ if (nameAttr) {
+ setter(scope, null, attr[nameAttr], undefined, controller.$name);
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
@@ -27820,12 +28077,13 @@ var formDirectiveFactory = function(isNgForm) {
var formDirective = formDirectiveFactory();
var ngFormDirective = formDirectiveFactory(true);
-/* global VALID_CLASS: true,
- INVALID_CLASS: true,
- PRISTINE_CLASS: true,
- DIRTY_CLASS: true,
- UNTOUCHED_CLASS: true,
- TOUCHED_CLASS: true,
+/* global VALID_CLASS: false,
+ INVALID_CLASS: false,
+ PRISTINE_CLASS: false,
+ DIRTY_CLASS: false,
+ UNTOUCHED_CLASS: false,
+ TOUCHED_CLASS: false,
+ ngModelMinErr: false,
*/
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
@@ -27838,9 +28096,6 @@ var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
-var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
-
-var $ngModelMinErr = new minErr('ngModel');
var inputType = {
@@ -27883,19 +28138,21 @@ var inputType = {
<script>
angular.module('textInputExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'guest';
- $scope.word = /^\s*\w*\s*$/;
+ $scope.example = {
+ text: 'guest',
+ word: /^\s*\w*\s*$/
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Single word: <input type="text" name="input" ng-model="text"
- ng-pattern="word" required ng-trim="false">
+ Single word: <input type="text" name="input" ng-model="example.text"
+ ng-pattern="example.word" required ng-trim="false">
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.pattern">
Single word only!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{example.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -27903,9 +28160,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('example.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('example.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('guest');
@@ -27967,18 +28224,20 @@ var inputType = {
<script>
angular.module('dateInputExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 9, 22);
+ $scope.example = {
+ value: new Date(2013, 9, 22)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date in 2013:
- <input type="date" id="exampleInput" name="input" ng-model="value"
+ <input type="date" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.date">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-MM-dd"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -27986,9 +28245,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM-dd"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -28058,18 +28317,20 @@ var inputType = {
<script>
angular.module('dateExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2010, 11, 28, 14, 57);
+ $scope.example = {
+ value: new Date(2010, 11, 28, 14, 57)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
- <input type="datetime-local" id="exampleInput" name="input" ng-model="value"
+ <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.datetimelocal">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28077,9 +28338,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm:ss"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -28150,18 +28411,20 @@ var inputType = {
<script>
angular.module('timeExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(1970, 0, 1, 14, 57, 0);
+ $scope.example = {
+ value: new Date(1970, 0, 1, 14, 57, 0)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a between 8am and 5pm:
- <input type="time" id="exampleInput" name="input" ng-model="value"
+ <input type="time" id="exampleInput" name="input" ng-model="example.value"
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.time">
Not a valid date!</span>
- <tt>value = {{value | date: "HH:mm:ss"}}</tt><br/>
+ <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28169,9 +28432,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "HH:mm:ss"'));
+ var value = element(by.binding('example.value | date: "HH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -28241,18 +28504,20 @@ var inputType = {
<script>
angular.module('weekExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 0, 3);
+ $scope.example = {
+ value: new Date(2013, 0, 3)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
- <input id="exampleInput" type="week" name="input" ng-model="value"
+ <input id="exampleInput" type="week" name="input" ng-model="example.value"
placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.week">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-Www"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28260,9 +28525,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-Www"'));
+ var value = element(by.binding('example.value | date: "yyyy-Www"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -28332,18 +28597,20 @@ var inputType = {
<script>
angular.module('monthExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 9, 1);
+ $scope.example = {
+ value: new Date(2013, 9, 1)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
- Pick a month int 2013:
- <input id="exampleInput" type="month" name="input" ng-model="value"
+ Pick a month in 2013:
+ <input id="exampleInput" type="month" name="input" ng-model="example.value"
placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.month">
Not a valid month!</span>
- <tt>value = {{value | date: "yyyy-MM"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28351,9 +28618,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -28397,7 +28664,11 @@ var inputType = {
* Text input with number validation and transformation. Sets the `number` validation
* error if not a valid number.
*
- * The model must always be a number, otherwise Angular will throw an error.
+ * <div class="alert alert-warning">
+ * The model must always be of type `number` otherwise Angular will throw an error.
+ * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
+ * error docs for more information and an example of how to convert your model if necessary.
+ * </div>
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -28429,17 +28700,19 @@ var inputType = {
<script>
angular.module('numberExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.value = 12;
+ $scope.example = {
+ value: 12
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Number: <input type="number" name="input" ng-model="value"
+ Number: <input type="number" name="input" ng-model="example.value"
min="0" max="99" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.number">
Not valid number!</span>
- <tt>value = {{value}}</tt><br/>
+ <tt>value = {{example.value}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28447,9 +28720,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value'));
+ var value = element(by.binding('example.value'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
it('should initialize to model', function() {
expect(value.getText()).toContain('12');
@@ -28517,16 +28790,18 @@ var inputType = {
<script>
angular.module('urlExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'http://google.com';
+ $scope.url = {
+ text: 'http://google.com'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- URL: <input type="url" name="input" ng-model="text" required>
+ URL: <input type="url" name="input" ng-model="url.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.url">
Not valid url!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{url.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28535,9 +28810,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('url.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('url.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('http://google.com');
@@ -28606,16 +28881,18 @@ var inputType = {
<script>
angular.module('emailExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'me@example.com';
+ $scope.email = {
+ text: 'me@example.com'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Email: <input type="email" name="input" ng-model="text" required>
+ Email: <input type="email" name="input" ng-model="email.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.email">
Not valid email!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{email.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -28624,9 +28901,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('email.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('email.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('me@example.com');
@@ -28673,7 +28950,9 @@ var inputType = {
<script>
angular.module('radioExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.color = 'blue';
+ $scope.color = {
+ name: 'blue'
+ };
$scope.specialValue = {
"id": "12345",
"value": "green"
@@ -28681,20 +28960,20 @@ var inputType = {
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- <input type="radio" ng-model="color" value="red"> Red <br/>
- <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
- <input type="radio" ng-model="color" value="blue"> Blue <br/>
- <tt>color = {{color | json}}</tt><br/>
+ <input type="radio" ng-model="color.name" value="red"> Red <br/>
+ <input type="radio" ng-model="color.name" ng-value="specialValue"> Green <br/>
+ <input type="radio" ng-model="color.name" value="blue"> Blue <br/>
+ <tt>color = {{color.name | json}}</tt><br/>
</form>
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
- var color = element(by.binding('color'));
+ var color = element(by.binding('color.name'));
expect(color.getText()).toContain('blue');
- element.all(by.model('color')).get(0).click();
+ element.all(by.model('color.name')).get(0).click();
expect(color.getText()).toContain('red');
});
@@ -28724,28 +29003,30 @@ var inputType = {
<script>
angular.module('checkboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.value1 = true;
- $scope.value2 = 'YES'
+ $scope.checkboxModel = {
+ value1 : true,
+ value2 : 'YES'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Value1: <input type="checkbox" ng-model="value1"> <br/>
- Value2: <input type="checkbox" ng-model="value2"
+ Value1: <input type="checkbox" ng-model="checkboxModel.value1"> <br/>
+ Value2: <input type="checkbox" ng-model="checkboxModel.value2"
ng-true-value="'YES'" ng-false-value="'NO'"> <br/>
- <tt>value1 = {{value1}}</tt><br/>
- <tt>value2 = {{value2}}</tt><br/>
+ <tt>value1 = {{checkboxModel.value1}}</tt><br/>
+ <tt>value2 = {{checkboxModel.value2}}</tt><br/>
</form>
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
- var value1 = element(by.binding('value1'));
- var value2 = element(by.binding('value2'));
+ var value1 = element(by.binding('checkboxModel.value1'));
+ var value2 = element(by.binding('checkboxModel.value2'));
expect(value1.getText()).toContain('true');
expect(value2.getText()).toContain('YES');
- element(by.model('value1')).click();
- element(by.model('value2')).click();
+ element(by.model('checkboxModel.value1')).click();
+ element(by.model('checkboxModel.value2')).click();
expect(value1.getText()).toContain('false');
expect(value2.getText()).toContain('NO');
@@ -28966,7 +29247,7 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$formatters.push(function(value) {
if (value && !isDate(value)) {
- throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
+ throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
}
if (isValidDate(value)) {
previousDate = value;
@@ -29043,14 +29324,14 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
ctrl.$formatters.push(function(value) {
if (!ctrl.$isEmpty(value)) {
if (!isNumber(value)) {
- throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
+ throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
}
value = value.toString();
}
return value;
});
- if (attr.min || attr.ngMin) {
+ if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
@@ -29066,7 +29347,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
- if (attr.max || attr.ngMax) {
+ if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
@@ -29136,7 +29417,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
if (isDefined(expression)) {
parseFn = $parse(expression);
if (!parseFn.constant) {
- throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' +
+ throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
'`{1}`.', name, expression);
}
return parseFn(context);
@@ -29342,1344 +29623,6 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
};
}];
-var VALID_CLASS = 'ng-valid',
- INVALID_CLASS = 'ng-invalid',
- PRISTINE_CLASS = 'ng-pristine',
- DIRTY_CLASS = 'ng-dirty',
- UNTOUCHED_CLASS = 'ng-untouched',
- TOUCHED_CLASS = 'ng-touched',
- PENDING_CLASS = 'ng-pending';
-
-/**
- * @ngdoc type
- * @name ngModel.NgModelController
- *
- * @property {string} $viewValue Actual string value in the view.
- * @property {*} $modelValue The value in the model that the control is bound to.
- * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
- the control reads value from the DOM. The functions are called in array order, each passing
- its return value through to the next. The last return value is forwarded to the
- {@link ngModel.NgModelController#$validators `$validators`} collection.
-
-Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
-`$viewValue`}.
-
-Returning `undefined` from a parser means a parse error occurred. In that case,
-no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
-will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
-is set to `true`. The parse error is stored in `ngModel.$error.parse`.
-
- *
- * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
- the model value changes. The functions are called in reverse array order, each passing the value through to the
- next. The last return value is used as the actual DOM value.
- Used to format / convert values for display in the control.
- * ```js
- * function formatter(value) {
- * if (value) {
- * return value.toUpperCase();
- * }
- * }
- * ngModel.$formatters.push(formatter);
- * ```
- *
- * @property {Object.<string, function>} $validators A collection of validators that are applied
- * whenever the model value changes. The key value within the object refers to the name of the
- * validator while the function refers to the validation operation. The validation operation is
- * provided with the model value as an argument and must return a true or false value depending
- * on the response of that validation.
- *
- * ```js
- * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
- * var value = modelValue || viewValue;
- * return /[0-9]+/.test(value) &&
- * /[a-z]+/.test(value) &&
- * /[A-Z]+/.test(value) &&
- * /\W+/.test(value);
- * };
- * ```
- *
- * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
- * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
- * is expected to return a promise when it is run during the model validation process. Once the promise
- * is delivered then the validation status will be set to true when fulfilled and false when rejected.
- * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
- * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
- * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
- * will only run once all synchronous validators have passed.
- *
- * Please note that if $http is used then it is important that the server returns a success HTTP response code
- * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
- *
- * ```js
- * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
- * var value = modelValue || viewValue;
- *
- * // Lookup user by username
- * return $http.get('/api/users/' + value).
- * then(function resolved() {
- * //username exists, this means validation fails
- * return $q.reject('exists');
- * }, function rejected() {
- * //username does not exist, therefore this validation passes
- * return true;
- * });
- * };
- * ```
- *
- * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
- * view value has changed. It is called with no arguments, and its return value is ignored.
- * This can be used in place of additional $watches against the model value.
- *
- * @property {Object} $error An object hash with all failing validator ids as keys.
- * @property {Object} $pending An object hash with all pending validator ids as keys.
- *
- * @property {boolean} $untouched True if control has not lost focus yet.
- * @property {boolean} $touched True if control has lost focus.
- * @property {boolean} $pristine True if user has not interacted with the control yet.
- * @property {boolean} $dirty True if user has already interacted with the control.
- * @property {boolean} $valid True if there is no error.
- * @property {boolean} $invalid True if at least one error on the control.
- * @property {string} $name The name attribute of the control.
- *
- * @description
- *
- * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
- * The controller contains services for data-binding, validation, CSS updates, and value formatting
- * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
- * listening to DOM events.
- * Such DOM related logic should be provided by other directives which make use of
- * `NgModelController` for data-binding to control elements.
- * Angular provides this DOM logic for most {@link input `input`} elements.
- * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
- * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
- *
- * @example
- * ### Custom Control Example
- * This example shows how to use `NgModelController` with a custom control to achieve
- * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
- * collaborate together to achieve the desired result.
- *
- * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
- * contents be edited in place by the user. This will not work on older browsers.
- *
- * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
- * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
- * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
- * that content using the `$sce` service.
- *
- * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
- <file name="style.css">
- [contenteditable] {
- border: 1px solid black;
- background-color: white;
- min-height: 20px;
- }
-
- .ng-invalid {
- border: 1px solid red;
- }
-
- </file>
- <file name="script.js">
- angular.module('customControl', ['ngSanitize']).
- directive('contenteditable', ['$sce', function($sce) {
- return {
- restrict: 'A', // only activate on element attribute
- require: '?ngModel', // get a hold of NgModelController
- link: function(scope, element, attrs, ngModel) {
- if (!ngModel) return; // do nothing if no ng-model
-
- // Specify how UI should be updated
- ngModel.$render = function() {
- element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
- };
-
- // Listen for change events to enable binding
- element.on('blur keyup change', function() {
- scope.$evalAsync(read);
- });
- read(); // initialize
-
- // Write data to the model
- function read() {
- var html = element.html();
- // When we clear the content editable the browser leaves a <br> behind
- // If strip-br attribute is provided then we strip this out
- if ( attrs.stripBr && html == '<br>' ) {
- html = '';
- }
- ngModel.$setViewValue(html);
- }
- }
- };
- }]);
- </file>
- <file name="index.html">
- <form name="myForm">
- <div contenteditable
- name="myWidget" ng-model="userContent"
- strip-br="true"
- required>Change me!</div>
- <span ng-show="myForm.myWidget.$error.required">Required!</span>
- <hr>
- <textarea ng-model="userContent"></textarea>
- </form>
- </file>
- <file name="protractor.js" type="protractor">
- it('should data-bind and become invalid', function() {
- if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
- // SafariDriver can't handle contenteditable
- // and Firefox driver can't clear contenteditables very well
- return;
- }
- var contentEditable = element(by.css('[contenteditable]'));
- var content = 'Change me!';
-
- expect(contentEditable.getText()).toEqual(content);
-
- contentEditable.clear();
- contentEditable.sendKeys(protractor.Key.BACK_SPACE);
- expect(contentEditable.getText()).toEqual('');
- expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
- });
- </file>
- * </example>
- *
- *
- */
-var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
- function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
- this.$viewValue = Number.NaN;
- this.$modelValue = Number.NaN;
- this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
- this.$validators = {};
- this.$asyncValidators = {};
- this.$parsers = [];
- this.$formatters = [];
- this.$viewChangeListeners = [];
- this.$untouched = true;
- this.$touched = false;
- this.$pristine = true;
- this.$dirty = false;
- this.$valid = true;
- this.$invalid = false;
- this.$error = {}; // keep invalid keys here
- this.$$success = {}; // keep valid keys here
- this.$pending = undefined; // keep pending keys here
- this.$name = $interpolate($attr.name || '', false)($scope);
-
-
- var parsedNgModel = $parse($attr.ngModel),
- parsedNgModelAssign = parsedNgModel.assign,
- ngModelGet = parsedNgModel,
- ngModelSet = parsedNgModelAssign,
- pendingDebounce = null,
- ctrl = this;
-
- this.$$setOptions = function(options) {
- ctrl.$options = options;
- if (options && options.getterSetter) {
- var invokeModelGetter = $parse($attr.ngModel + '()'),
- invokeModelSetter = $parse($attr.ngModel + '($$$p)');
-
- ngModelGet = function($scope) {
- var modelValue = parsedNgModel($scope);
- if (isFunction(modelValue)) {
- modelValue = invokeModelGetter($scope);
- }
- return modelValue;
- };
- ngModelSet = function($scope, newValue) {
- if (isFunction(parsedNgModel($scope))) {
- invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
- } else {
- parsedNgModelAssign($scope, ctrl.$modelValue);
- }
- };
- } else if (!parsedNgModel.assign) {
- throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
- $attr.ngModel, startingTag($element));
- }
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$render
- *
- * @description
- * Called when the view needs to be updated. It is expected that the user of the ng-model
- * directive will implement this method.
- *
- * The `$render()` method is invoked in the following situations:
- *
- * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
- * committed value then `$render()` is called to update the input control.
- * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
- * the `$viewValue` are different to last time.
- *
- * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
- * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
- * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
- * invoked if you only change a property on the objects.
- */
- this.$render = noop;
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$isEmpty
- *
- * @description
- * This is called when we need to determine if the value of an input is empty.
- *
- * For instance, the required directive does this to work out if the input has data or not.
- *
- * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
- *
- * You can override this for input directives whose concept of being empty is different to the
- * default. The `checkboxInputType` directive does this because in its case a value of `false`
- * implies empty.
- *
- * @param {*} value The value of the input to check for emptiness.
- * @returns {boolean} True if `value` is "empty".
- */
- this.$isEmpty = function(value) {
- return isUndefined(value) || value === '' || value === null || value !== value;
- };
-
- var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
- currentValidationRunId = 0;
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setValidity
- *
- * @description
- * Change the validity state, and notify the form.
- *
- * This method can be called within $parsers/$formatters or a custom validation implementation.
- * However, in most cases it should be sufficient to use the `ngModel.$validators` and
- * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
- *
- * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
- * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
- * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
- * The `validationErrorKey` should be in camelCase and will get converted into dash-case
- * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
- * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
- * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
- * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
- * Skipped is used by Angular when validators do not run because of parse errors and
- * when `$asyncValidators` do not run because any of the `$validators` failed.
- */
- addSetValidityMethod({
- ctrl: this,
- $element: $element,
- set: function(object, property) {
- object[property] = true;
- },
- unset: function(object, property) {
- delete object[property];
- },
- parentForm: parentForm,
- $animate: $animate
- });
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setPristine
- *
- * @description
- * Sets the control to its pristine state.
- *
- * This method can be called to remove the `ng-dirty` class and set the control to its pristine
- * state (`ng-pristine` class). A model is considered to be pristine when the control
- * has not been changed from when first compiled.
- */
- this.$setPristine = function() {
- ctrl.$dirty = false;
- ctrl.$pristine = true;
- $animate.removeClass($element, DIRTY_CLASS);
- $animate.addClass($element, PRISTINE_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setDirty
- *
- * @description
- * Sets the control to its dirty state.
- *
- * This method can be called to remove the `ng-pristine` class and set the control to its dirty
- * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
- * from when first compiled.
- */
- this.$setDirty = function() {
- ctrl.$dirty = true;
- ctrl.$pristine = false;
- $animate.removeClass($element, PRISTINE_CLASS);
- $animate.addClass($element, DIRTY_CLASS);
- parentForm.$setDirty();
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setUntouched
- *
- * @description
- * Sets the control to its untouched state.
- *
- * This method can be called to remove the `ng-touched` class and set the control to its
- * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
- * by default, however this function can be used to restore that state if the model has
- * already been touched by the user.
- */
- this.$setUntouched = function() {
- ctrl.$touched = false;
- ctrl.$untouched = true;
- $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setTouched
- *
- * @description
- * Sets the control to its touched state.
- *
- * This method can be called to remove the `ng-untouched` class and set the control to its
- * touched state (`ng-touched` class). A model is considered to be touched when the user has
- * first focused the control element and then shifted focus away from the control (blur event).
- */
- this.$setTouched = function() {
- ctrl.$touched = true;
- ctrl.$untouched = false;
- $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$rollbackViewValue
- *
- * @description
- * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
- * which may be caused by a pending debounced event or because the input is waiting for a some
- * future event.
- *
- * If you have an input that uses `ng-model-options` to set up debounced events or events such
- * as blur you can have a situation where there is a period when the `$viewValue`
- * is out of synch with the ngModel's `$modelValue`.
- *
- * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
- * programmatically before these debounced/future events have resolved/occurred, because Angular's
- * dirty checking mechanism is not able to tell whether the model has actually changed or not.
- *
- * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
- * input which may have such events pending. This is important in order to make sure that the
- * input field will be updated with the new model value and any pending operations are cancelled.
- *
- * <example name="ng-model-cancel-update" module="cancel-update-example">
- * <file name="app.js">
- * angular.module('cancel-update-example', [])
- *
- * .controller('CancelUpdateController', ['$scope', function($scope) {
- * $scope.resetWithCancel = function(e) {
- * if (e.keyCode == 27) {
- * $scope.myForm.myInput1.$rollbackViewValue();
- * $scope.myValue = '';
- * }
- * };
- * $scope.resetWithoutCancel = function(e) {
- * if (e.keyCode == 27) {
- * $scope.myValue = '';
- * }
- * };
- * }]);
- * </file>
- * <file name="index.html">
- * <div ng-controller="CancelUpdateController">
- * <p>Try typing something in each input. See that the model only updates when you
- * blur off the input.
- * </p>
- * <p>Now see what happens if you start typing then press the Escape key</p>
- *
- * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
- * <p>With $rollbackViewValue()</p>
- * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/>
- * myValue: "{{ myValue }}"
- *
- * <p>Without $rollbackViewValue()</p>
- * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/>
- * myValue: "{{ myValue }}"
- * </form>
- * </div>
- * </file>
- * </example>
- */
- this.$rollbackViewValue = function() {
- $timeout.cancel(pendingDebounce);
- ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
- ctrl.$render();
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$validate
- *
- * @description
- * Runs each of the registered validators (first synchronous validators and then
- * asynchronous validators).
- * If the validity changes to invalid, the model will be set to `undefined`,
- * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
- * If the validity changes to valid, it will set the model to the last available valid
- * modelValue, i.e. either the last parsed value or the last value set from the scope.
- */
- this.$validate = function() {
- // ignore $validate before model is initialized
- if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
- return;
- }
-
- var viewValue = ctrl.$$lastCommittedViewValue;
- // Note: we use the $$rawModelValue as $modelValue might have been
- // set to undefined during a view -> model update that found validation
- // errors. We can't parse the view here, since that could change
- // the model although neither viewValue nor the model on the scope changed
- var modelValue = ctrl.$$rawModelValue;
-
- // Check if the there's a parse error, so we don't unset it accidentially
- var parserName = ctrl.$$parserName || 'parse';
- var parserValid = ctrl.$error[parserName] ? false : undefined;
-
- var prevValid = ctrl.$valid;
- var prevModelValue = ctrl.$modelValue;
-
- var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
-
- ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
- // If there was no change in validity, don't update the model
- // This prevents changing an invalid modelValue to undefined
- if (!allowInvalid && prevValid !== allValid) {
- // Note: Don't check ctrl.$valid here, as we could have
- // external validators (e.g. calculated on the server),
- // that just call $setValidity and need the model value
- // to calculate their validity.
- ctrl.$modelValue = allValid ? modelValue : undefined;
-
- if (ctrl.$modelValue !== prevModelValue) {
- ctrl.$$writeModelToScope();
- }
- }
- });
-
- };
-
- this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
- currentValidationRunId++;
- var localValidationRunId = currentValidationRunId;
-
- // check parser error
- if (!processParseErrors(parseValid)) {
- validationDone(false);
- return;
- }
- if (!processSyncValidators()) {
- validationDone(false);
- return;
- }
- processAsyncValidators();
-
- function processParseErrors(parseValid) {
- var errorKey = ctrl.$$parserName || 'parse';
- if (parseValid === undefined) {
- setValidity(errorKey, null);
- } else {
- setValidity(errorKey, parseValid);
- if (!parseValid) {
- forEach(ctrl.$validators, function(v, name) {
- setValidity(name, null);
- });
- forEach(ctrl.$asyncValidators, function(v, name) {
- setValidity(name, null);
- });
- return false;
- }
- }
- return true;
- }
-
- function processSyncValidators() {
- var syncValidatorsValid = true;
- forEach(ctrl.$validators, function(validator, name) {
- var result = validator(modelValue, viewValue);
- syncValidatorsValid = syncValidatorsValid && result;
- setValidity(name, result);
- });
- if (!syncValidatorsValid) {
- forEach(ctrl.$asyncValidators, function(v, name) {
- setValidity(name, null);
- });
- return false;
- }
- return true;
- }
-
- function processAsyncValidators() {
- var validatorPromises = [];
- var allValid = true;
- forEach(ctrl.$asyncValidators, function(validator, name) {
- var promise = validator(modelValue, viewValue);
- if (!isPromiseLike(promise)) {
- throw $ngModelMinErr("$asyncValidators",
- "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
- }
- setValidity(name, undefined);
- validatorPromises.push(promise.then(function() {
- setValidity(name, true);
- }, function(error) {
- allValid = false;
- setValidity(name, false);
- }));
- });
- if (!validatorPromises.length) {
- validationDone(true);
- } else {
- $q.all(validatorPromises).then(function() {
- validationDone(allValid);
- }, noop);
- }
- }
-
- function setValidity(name, isValid) {
- if (localValidationRunId === currentValidationRunId) {
- ctrl.$setValidity(name, isValid);
- }
- }
-
- function validationDone(allValid) {
- if (localValidationRunId === currentValidationRunId) {
-
- doneCallback(allValid);
- }
- }
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$commitViewValue
- *
- * @description
- * Commit a pending update 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.
- */
- this.$commitViewValue = function() {
- var viewValue = ctrl.$viewValue;
-
- $timeout.cancel(pendingDebounce);
-
- // If the view value has not changed then we should just exit, except in the case where there is
- // a native validator on the element. In this case the validation state may have changed even though
- // the viewValue has stayed empty.
- if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
- return;
- }
- ctrl.$$lastCommittedViewValue = viewValue;
-
- // change to dirty
- if (ctrl.$pristine) {
- this.$setDirty();
- }
- this.$$parseAndValidate();
- };
-
- this.$$parseAndValidate = function() {
- var viewValue = ctrl.$$lastCommittedViewValue;
- var modelValue = viewValue;
- var parserValid = isUndefined(modelValue) ? undefined : true;
-
- if (parserValid) {
- for (var i = 0; i < ctrl.$parsers.length; i++) {
- modelValue = ctrl.$parsers[i](modelValue);
- if (isUndefined(modelValue)) {
- parserValid = false;
- break;
- }
- }
- }
- if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
- // ctrl.$modelValue has not been touched yet...
- ctrl.$modelValue = ngModelGet($scope);
- }
- var prevModelValue = ctrl.$modelValue;
- var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
- ctrl.$$rawModelValue = modelValue;
-
- if (allowInvalid) {
- ctrl.$modelValue = modelValue;
- writeToModelIfNeeded();
- }
-
- // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
- // This can happen if e.g. $setViewValue is called from inside a parser
- ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
- if (!allowInvalid) {
- // Note: Don't check ctrl.$valid here, as we could have
- // external validators (e.g. calculated on the server),
- // that just call $setValidity and need the model value
- // to calculate their validity.
- ctrl.$modelValue = allValid ? modelValue : undefined;
- writeToModelIfNeeded();
- }
- });
-
- function writeToModelIfNeeded() {
- if (ctrl.$modelValue !== prevModelValue) {
- ctrl.$$writeModelToScope();
- }
- }
- };
-
- this.$$writeModelToScope = function() {
- ngModelSet($scope, ctrl.$modelValue);
- forEach(ctrl.$viewChangeListeners, function(listener) {
- try {
- listener();
- } catch (e) {
- $exceptionHandler(e);
- }
- });
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setViewValue
- *
- * @description
- * Update the view value.
- *
- * This method should be called when an input directive want to change the view value; typically,
- * this is done from within a DOM event handler.
- *
- * For example {@link ng.directive:input input} calls it when the value of the input changes and
- * {@link ng.directive:select select} calls it when an option is selected.
- *
- * If the new `value` is an object (rather than a string or a number), we should make a copy of the
- * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
- * watch of objects, it only looks for a change of identity. If you only change the property of
- * the object then ngModel will not realise that the object has changed and will not invoke the
- * `$parsers` and `$validators` pipelines.
- *
- * For this reason, you should not change properties of the copy once it has been passed to
- * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
- *
- * When this method is called, the new `value` will be staged for committing through the `$parsers`
- * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
- * value sent directly for processing, finally to be applied to `$modelValue` and then the
- * **expression** specified in the `ng-model` attribute.
- *
- * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
- *
- * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
- * and the `default` trigger is not listed, all those actions will remain pending until one of the
- * `updateOn` events is triggered on the DOM element.
- * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
- * directive is used with a custom debounce for this particular event.
- *
- * Note that calling this function does not trigger a `$digest`.
- *
- * @param {string} value Value from the view.
- * @param {string} trigger Event that triggered the update.
- */
- this.$setViewValue = function(value, trigger) {
- ctrl.$viewValue = value;
- if (!ctrl.$options || ctrl.$options.updateOnDefault) {
- ctrl.$$debounceViewValueCommit(trigger);
- }
- };
-
- this.$$debounceViewValueCommit = function(trigger) {
- var debounceDelay = 0,
- options = ctrl.$options,
- debounce;
-
- if (options && isDefined(options.debounce)) {
- debounce = options.debounce;
- if (isNumber(debounce)) {
- debounceDelay = debounce;
- } else if (isNumber(debounce[trigger])) {
- debounceDelay = debounce[trigger];
- } else if (isNumber(debounce['default'])) {
- debounceDelay = debounce['default'];
- }
- }
-
- $timeout.cancel(pendingDebounce);
- if (debounceDelay) {
- pendingDebounce = $timeout(function() {
- ctrl.$commitViewValue();
- }, debounceDelay);
- } else if ($rootScope.$$phase) {
- ctrl.$commitViewValue();
- } else {
- $scope.$apply(function() {
- ctrl.$commitViewValue();
- });
- }
- };
-
- // model -> value
- // Note: we cannot use a normal scope.$watch as we want to detect the following:
- // 1. scope value is 'a'
- // 2. user enters 'b'
- // 3. ng-change kicks in and reverts scope value to 'a'
- // -> scope value did not change since the last digest as
- // ng-change executes in apply phase
- // 4. view should be changed back to 'a'
- $scope.$watch(function ngModelWatch() {
- var modelValue = ngModelGet($scope);
-
- // if scope model value and ngModel value are out of sync
- // TODO(perf): why not move this to the action fn?
- if (modelValue !== ctrl.$modelValue) {
- ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
-
- var formatters = ctrl.$formatters,
- idx = formatters.length;
-
- var viewValue = modelValue;
- while (idx--) {
- viewValue = formatters[idx](viewValue);
- }
- if (ctrl.$viewValue !== viewValue) {
- ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
- ctrl.$render();
-
- ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
- }
- }
-
- return modelValue;
- });
-}];
-
-
-/**
- * @ngdoc directive
- * @name ngModel
- *
- * @element input
- * @priority 1
- *
- * @description
- * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
- * property on the scope using {@link ngModel.NgModelController NgModelController},
- * which is created and exposed by this directive.
- *
- * `ngModel` is responsible for:
- *
- * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
- * require.
- * - Providing validation behavior (i.e. required, number, email, url).
- * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
- * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
- * - Registering the control with its parent {@link ng.directive:form form}.
- *
- * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
- * current scope. If the property doesn't already exist on this scope, it will be created
- * implicitly and added to the scope.
- *
- * For best practices on using `ngModel`, see:
- *
- * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
- *
- * For basic examples, how to use `ngModel`, see:
- *
- * - {@link ng.directive:input input}
- * - {@link input[text] text}
- * - {@link input[checkbox] checkbox}
- * - {@link input[radio] radio}
- * - {@link input[number] number}
- * - {@link input[email] email}
- * - {@link input[url] url}
- * - {@link input[date] date}
- * - {@link input[datetime-local] datetime-local}
- * - {@link input[time] time}
- * - {@link input[month] month}
- * - {@link input[week] week}
- * - {@link ng.directive:select select}
- * - {@link ng.directive:textarea textarea}
- *
- * # CSS classes
- * The following CSS classes are added and removed on the associated input/select/textarea element
- * depending on the validity of the model.
- *
- * - `ng-valid`: the model is valid
- * - `ng-invalid`: the model is invalid
- * - `ng-valid-[key]`: for each valid key added by `$setValidity`
- * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
- * - `ng-pristine`: the control hasn't been interacted with yet
- * - `ng-dirty`: the control has been interacted with
- * - `ng-touched`: the control has been blurred
- * - `ng-untouched`: the control hasn't been blurred
- * - `ng-pending`: any `$asyncValidators` are unfulfilled
- *
- * Keep in mind that ngAnimate can detect each of these classes when added and removed.
- *
- * ## Animation Hooks
- *
- * Animations within models are triggered when any of the associated CSS classes are added and removed
- * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
- * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
- * The animations that are triggered within ngModel are similar to how they work in ngClass and
- * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
- *
- * The following example shows a simple way to utilize CSS transitions to style an input element
- * that has been rendered as invalid after it has been validated:
- *
- * <pre>
- * //be sure to include ngAnimate as a module to hook into more
- * //advanced animations
- * .my-input {
- * transition:0.5s linear all;
- * background: white;
- * }
- * .my-input.ng-invalid {
- * background: red;
- * color:white;
- * }
- * </pre>
- *
- * @example
- * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
- <file name="index.html">
- <script>
- angular.module('inputExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.val = '1';
- }]);
- </script>
- <style>
- .my-input {
- -webkit-transition:all linear 0.5s;
- transition:all linear 0.5s;
- background: transparent;
- }
- .my-input.ng-invalid {
- color:white;
- background: red;
- }
- </style>
- Update input to see transitions when valid/invalid.
- Integer is a valid value.
- <form name="testForm" ng-controller="ExampleController">
- <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" />
- </form>
- </file>
- * </example>
- *
- * ## Binding to a getter/setter
- *
- * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
- * function that returns a representation of the model when called with zero arguments, and sets
- * the internal state of a model when called with an argument. It's sometimes useful to use this
- * for models that have an internal representation that's different than what the model exposes
- * to the view.
- *
- * <div class="alert alert-success">
- * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
- * frequently than other parts of your code.
- * </div>
- *
- * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
- * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
- * a `<form>`, which will enable this behavior for all `<input>`s within it. See
- * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
- *
- * The following example shows how to use `ngModel` with a getter/setter:
- *
- * @example
- * <example name="ngModel-getter-setter" module="getterSetterExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ getterSetter: true }" />
- </form>
- <pre>user.name = <span ng-bind="user.name()"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('getterSetterExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- var _name = 'Brian';
- $scope.user = {
- name: function(newName) {
- if (angular.isDefined(newName)) {
- _name = newName;
- }
- return _name;
- }
- };
- }]);
- </file>
- * </example>
- */
-var ngModelDirective = ['$rootScope', function($rootScope) {
- return {
- restrict: 'A',
- require: ['ngModel', '^?form', '^?ngModelOptions'],
- controller: NgModelController,
- // Prelink needs to run before any input directive
- // so that we can set the NgModelOptions in NgModelController
- // before anyone else uses it.
- priority: 1,
- compile: function ngModelCompile(element) {
- // Setup initial state of the control
- element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
-
- return {
- pre: function ngModelPreLink(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0],
- formCtrl = ctrls[1] || nullFormCtrl;
-
- modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
-
- // notify others, especially parent forms
- formCtrl.$addControl(modelCtrl);
-
- attr.$observe('name', function(newValue) {
- if (modelCtrl.$name !== newValue) {
- formCtrl.$$renameControl(modelCtrl, newValue);
- }
- });
-
- scope.$on('$destroy', function() {
- formCtrl.$removeControl(modelCtrl);
- });
- },
- post: function ngModelPostLink(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0];
- if (modelCtrl.$options && modelCtrl.$options.updateOn) {
- element.on(modelCtrl.$options.updateOn, function(ev) {
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
- });
- }
-
- element.on('blur', function(ev) {
- if (modelCtrl.$touched) return;
-
- if ($rootScope.$$phase) {
- scope.$evalAsync(modelCtrl.$setTouched);
- } else {
- scope.$apply(modelCtrl.$setTouched);
- }
- });
- }
- };
- }
- };
-}];
-
-
-/**
- * @ngdoc directive
- * @name ngChange
- *
- * @description
- * Evaluate the given expression when the user changes the input.
- * The expression is evaluated immediately, unlike the JavaScript onchange event
- * which only triggers at the end of a change (usually, when the user leaves the
- * form element or presses the return key).
- *
- * The `ngChange` expression is only evaluated when a change in the input value causes
- * a new value to be committed to the model.
- *
- * It will not be evaluated:
- * * if the value returned from the `$parsers` transformation pipeline has not changed
- * * if the input has continued to be invalid since the model will stay `null`
- * * if the model is changed programmatically and not by a change to the input value
- *
- *
- * Note, this directive requires `ngModel` to be present.
- *
- * @element input
- * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
- * in input value.
- *
- * @example
- * <example name="ngChange-directive" module="changeExample">
- * <file name="index.html">
- * <script>
- * angular.module('changeExample', [])
- * .controller('ExampleController', ['$scope', function($scope) {
- * $scope.counter = 0;
- * $scope.change = function() {
- * $scope.counter++;
- * };
- * }]);
- * </script>
- * <div ng-controller="ExampleController">
- * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
- * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
- * <label for="ng-change-example2">Confirmed</label><br />
- * <tt>debug = {{confirmed}}</tt><br/>
- * <tt>counter = {{counter}}</tt><br/>
- * </div>
- * </file>
- * <file name="protractor.js" type="protractor">
- * var counter = element(by.binding('counter'));
- * var debug = element(by.binding('confirmed'));
- *
- * it('should evaluate the expression if changing from view', function() {
- * expect(counter.getText()).toContain('0');
- *
- * element(by.id('ng-change-example1')).click();
- *
- * expect(counter.getText()).toContain('1');
- * expect(debug.getText()).toContain('true');
- * });
- *
- * it('should not evaluate the expression if changing from model', function() {
- * element(by.id('ng-change-example2')).click();
-
- * expect(counter.getText()).toContain('0');
- * expect(debug.getText()).toContain('true');
- * });
- * </file>
- * </example>
- */
-var ngChangeDirective = valueFn({
- restrict: 'A',
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- ctrl.$viewChangeListeners.push(function() {
- scope.$eval(attr.ngChange);
- });
- }
-});
-
-
-var requiredDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
- attr.required = true; // force truthy in case we are on non input element
-
- ctrl.$validators.required = function(modelValue, viewValue) {
- return !attr.required || !ctrl.$isEmpty(viewValue);
- };
-
- attr.$observe('required', function() {
- ctrl.$validate();
- });
- }
- };
-};
-
-
-var patternDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var regexp, patternExp = attr.ngPattern || attr.pattern;
- attr.$observe('pattern', function(regex) {
- if (isString(regex) && regex.length > 0) {
- regex = new RegExp('^' + regex + '$');
- }
-
- if (regex && !regex.test) {
- throw minErr('ngPattern')('noregexp',
- 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
- regex, startingTag(elm));
- }
-
- regexp = regex || undefined;
- ctrl.$validate();
- });
-
- ctrl.$validators.pattern = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
- };
- }
- };
-};
-
-
-var maxlengthDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var maxlength = -1;
- attr.$observe('maxlength', function(value) {
- var intVal = int(value);
- maxlength = isNaN(intVal) ? -1 : intVal;
- ctrl.$validate();
- });
- ctrl.$validators.maxlength = function(modelValue, viewValue) {
- return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
- };
- }
- };
-};
-
-var minlengthDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var minlength = 0;
- attr.$observe('minlength', function(value) {
- minlength = int(value) || 0;
- ctrl.$validate();
- });
- ctrl.$validators.minlength = function(modelValue, viewValue) {
- return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
- };
- }
- };
-};
-
-
-/**
- * @ngdoc directive
- * @name ngList
- *
- * @description
- * Text input that converts between a delimited string and an array of strings. The default
- * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
- * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
- *
- * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
- * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
- * list item is respected. This implies that the user of the directive is responsible for
- * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
- * tab or newline character.
- * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
- * when joining the list items back together) and whitespace around each list item is stripped
- * before it is added to the model.
- *
- * ### Example with Validation
- *
- * <example name="ngList-directive" module="listExample">
- * <file name="app.js">
- * angular.module('listExample', [])
- * .controller('ExampleController', ['$scope', function($scope) {
- * $scope.names = ['morpheus', 'neo', 'trinity'];
- * }]);
- * </file>
- * <file name="index.html">
- * <form name="myForm" ng-controller="ExampleController">
- * List: <input name="namesInput" ng-model="names" ng-list required>
- * <span class="error" ng-show="myForm.namesInput.$error.required">
- * Required!</span>
- * <br>
- * <tt>names = {{names}}</tt><br/>
- * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
- * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
- * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
- * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
- * </form>
- * </file>
- * <file name="protractor.js" type="protractor">
- * var listInput = element(by.model('names'));
- * var names = element(by.exactBinding('names'));
- * var valid = element(by.binding('myForm.namesInput.$valid'));
- * var error = element(by.css('span.error'));
- *
- * it('should initialize to model', function() {
- * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
- * expect(valid.getText()).toContain('true');
- * expect(error.getCssValue('display')).toBe('none');
- * });
- *
- * it('should be invalid if empty', function() {
- * listInput.clear();
- * listInput.sendKeys('');
- *
- * expect(names.getText()).toContain('');
- * expect(valid.getText()).toContain('false');
- * expect(error.getCssValue('display')).not.toBe('none');
- * });
- * </file>
- * </example>
- *
- * ### Example - splitting on whitespace
- * <example name="ngList-directive-newlines">
- * <file name="index.html">
- * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
- * <pre>{{ list | json }}</pre>
- * </file>
- * <file name="protractor.js" type="protractor">
- * it("should split the text by newlines", function() {
- * var listInput = element(by.model('list'));
- * var output = element(by.binding('list | json'));
- * listInput.sendKeys('abc\ndef\nghi');
- * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
- * });
- * </file>
- * </example>
- *
- * @element input
- * @param {string=} ngList optional delimiter that should be used to split the value.
- */
-var ngListDirective = function() {
- return {
- restrict: 'A',
- priority: 100,
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- // We want to control whitespace trimming so we use this convoluted approach
- // to access the ngList attribute, which doesn't pre-trim the attribute
- var ngList = element.attr(attr.$attr.ngList) || ', ';
- var trimValues = attr.ngTrim !== 'false';
- var separator = trimValues ? trim(ngList) : ngList;
-
- var parse = function(viewValue) {
- // If the viewValue is invalid (say required but empty) it will be `undefined`
- if (isUndefined(viewValue)) return;
-
- var list = [];
-
- if (viewValue) {
- forEach(viewValue.split(separator), function(value) {
- if (value) list.push(trimValues ? trim(value) : value);
- });
- }
-
- return list;
- };
-
- ctrl.$parsers.push(parse);
- ctrl.$formatters.push(function(value) {
- if (isArray(value)) {
- return value.join(ngList);
- }
-
- return undefined;
- });
-
- // Override the standard $isEmpty because an empty array means the input is empty.
- ctrl.$isEmpty = function(value) {
- return !value || !value.length;
- };
- }
- };
-};
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
@@ -30762,281 +29705,6 @@ var ngValueDirective = function() {
/**
* @ngdoc directive
- * @name ngModelOptions
- *
- * @description
- * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
- * events that will trigger a model update and/or a debouncing delay so that the actual update only
- * takes place when a timer expires; this timer will be reset after another change takes place.
- *
- * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
- * be different than the value in the actual model. This means that if you update the model you
- * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
- * order to make sure it is synchronized with the model and that any debounced action is canceled.
- *
- * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
- * method is by making sure the input is placed inside a form that has a `name` attribute. This is
- * important because `form` controllers are published to the related scope under the name in their
- * `name` attribute.
- *
- * Any pending changes will take place immediately when an enclosing form is submitted via the
- * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
- * to have access to the updated model.
- *
- * `ngModelOptions` has an effect on the element it's declared on and its descendants.
- *
- * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
- * - `updateOn`: string specifying which event should the input be bound to. You can set several
- * events using an space delimited list. There is a special event called `default` that
- * matches the default events belonging of the control.
- * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
- * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
- * custom value for each event. For example:
- * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
- * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
- * not validate correctly instead of the default behavior of setting the model to undefined.
- * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
- `ngModel` as getters/setters.
- * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
- * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
- * otherwise the default timezone of the browser will be used.
- *
- * @example
-
- The following example shows how to override immediate updates. Changes on the inputs within the
- form will update the model only when the control loses focus (blur event). If `escape` key is
- pressed while the input field is focused, the value is reset to the value in the current model.
-
- <example name="ngModelOptions-directive-blur" module="optionsExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ updateOn: 'blur' }"
- ng-keyup="cancel($event)" /><br />
-
- Other data:
- <input type="text" ng-model="user.data" /><br />
- </form>
- <pre>user.name = <span ng-bind="user.name"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('optionsExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say', data: '' };
-
- $scope.cancel = function(e) {
- if (e.keyCode == 27) {
- $scope.userForm.userName.$rollbackViewValue();
- }
- };
- }]);
- </file>
- <file name="protractor.js" type="protractor">
- var model = element(by.binding('user.name'));
- var input = element(by.model('user.name'));
- var other = element(by.model('user.data'));
-
- it('should allow custom events', function() {
- input.sendKeys(' hello');
- input.click();
- expect(model.getText()).toEqual('say');
- other.click();
- expect(model.getText()).toEqual('say hello');
- });
-
- it('should $rollbackViewValue when model changes', function() {
- input.sendKeys(' hello');
- expect(input.getAttribute('value')).toEqual('say hello');
- input.sendKeys(protractor.Key.ESCAPE);
- expect(input.getAttribute('value')).toEqual('say');
- other.click();
- expect(model.getText()).toEqual('say');
- });
- </file>
- </example>
-
- This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
- If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
-
- <example name="ngModelOptions-directive-debounce" module="optionsExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ debounce: 1000 }" />
- <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
- </form>
- <pre>user.name = <span ng-bind="user.name"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('optionsExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say' };
- }]);
- </file>
- </example>
-
- This one shows how to bind to getter/setters:
-
- <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ getterSetter: true }" />
- </form>
- <pre>user.name = <span ng-bind="user.name()"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('getterSetterExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- var _name = 'Brian';
- $scope.user = {
- name: function(newName) {
- return angular.isDefined(newName) ? (_name = newName) : _name;
- }
- };
- }]);
- </file>
- </example>
- */
-var ngModelOptionsDirective = function() {
- return {
- restrict: 'A',
- controller: ['$scope', '$attrs', function($scope, $attrs) {
- var that = this;
- this.$options = $scope.$eval($attrs.ngModelOptions);
- // Allow adding/overriding bound events
- if (this.$options.updateOn !== undefined) {
- this.$options.updateOnDefault = false;
- // extract "default" pseudo-event from list of events that can trigger a model update
- this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
- that.$options.updateOnDefault = true;
- return ' ';
- }));
- } else {
- this.$options.updateOnDefault = true;
- }
- }]
- };
-};
-
-// helper methods
-function addSetValidityMethod(context) {
- var ctrl = context.ctrl,
- $element = context.$element,
- classCache = {},
- set = context.set,
- unset = context.unset,
- parentForm = context.parentForm,
- $animate = context.$animate;
-
- classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
-
- ctrl.$setValidity = setValidity;
-
- function setValidity(validationErrorKey, state, options) {
- if (state === undefined) {
- createAndSet('$pending', validationErrorKey, options);
- } else {
- unsetAndCleanup('$pending', validationErrorKey, options);
- }
- if (!isBoolean(state)) {
- unset(ctrl.$error, validationErrorKey, options);
- unset(ctrl.$$success, validationErrorKey, options);
- } else {
- if (state) {
- unset(ctrl.$error, validationErrorKey, options);
- set(ctrl.$$success, validationErrorKey, options);
- } else {
- set(ctrl.$error, validationErrorKey, options);
- unset(ctrl.$$success, validationErrorKey, options);
- }
- }
- if (ctrl.$pending) {
- cachedToggleClass(PENDING_CLASS, true);
- ctrl.$valid = ctrl.$invalid = undefined;
- toggleValidationCss('', null);
- } else {
- cachedToggleClass(PENDING_CLASS, false);
- ctrl.$valid = isObjectEmpty(ctrl.$error);
- ctrl.$invalid = !ctrl.$valid;
- toggleValidationCss('', ctrl.$valid);
- }
-
- // re-read the state as the set/unset methods could have
- // combined state in ctrl.$error[validationError] (used for forms),
- // where setting/unsetting only increments/decrements the value,
- // and does not replace it.
- var combinedState;
- if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
- combinedState = undefined;
- } else if (ctrl.$error[validationErrorKey]) {
- combinedState = false;
- } else if (ctrl.$$success[validationErrorKey]) {
- combinedState = true;
- } else {
- combinedState = null;
- }
- toggleValidationCss(validationErrorKey, combinedState);
- parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
- }
-
- function createAndSet(name, value, options) {
- if (!ctrl[name]) {
- ctrl[name] = {};
- }
- set(ctrl[name], value, options);
- }
-
- function unsetAndCleanup(name, value, options) {
- if (ctrl[name]) {
- unset(ctrl[name], value, options);
- }
- if (isObjectEmpty(ctrl[name])) {
- ctrl[name] = undefined;
- }
- }
-
- function cachedToggleClass(className, switchValue) {
- if (switchValue && !classCache[className]) {
- $animate.addClass($element, className);
- classCache[className] = true;
- } else if (!switchValue && classCache[className]) {
- $animate.removeClass($element, className);
- classCache[className] = false;
- }
- }
-
- function toggleValidationCss(validationErrorKey, isValid) {
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
-
- cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
- cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
- }
-}
-
-function isObjectEmpty(obj) {
- if (obj) {
- for (var prop in obj) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * @ngdoc directive
* @name ngBind
* @restrict AC
*
@@ -31241,6 +29909,83 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
};
}];
+/**
+ * @ngdoc directive
+ * @name ngChange
+ *
+ * @description
+ * Evaluate the given expression when the user changes the input.
+ * The expression is evaluated immediately, unlike the JavaScript onchange event
+ * which only triggers at the end of a change (usually, when the user leaves the
+ * form element or presses the return key).
+ *
+ * The `ngChange` expression is only evaluated when a change in the input value causes
+ * a new value to be committed to the model.
+ *
+ * It will not be evaluated:
+ * * if the value returned from the `$parsers` transformation pipeline has not changed
+ * * if the input has continued to be invalid since the model will stay `null`
+ * * if the model is changed programmatically and not by a change to the input value
+ *
+ *
+ * Note, this directive requires `ngModel` to be present.
+ *
+ * @element input
+ * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
+ * in input value.
+ *
+ * @example
+ * <example name="ngChange-directive" module="changeExample">
+ * <file name="index.html">
+ * <script>
+ * angular.module('changeExample', [])
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.counter = 0;
+ * $scope.change = function() {
+ * $scope.counter++;
+ * };
+ * }]);
+ * </script>
+ * <div ng-controller="ExampleController">
+ * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
+ * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
+ * <label for="ng-change-example2">Confirmed</label><br />
+ * <tt>debug = {{confirmed}}</tt><br/>
+ * <tt>counter = {{counter}}</tt><br/>
+ * </div>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * var counter = element(by.binding('counter'));
+ * var debug = element(by.binding('confirmed'));
+ *
+ * it('should evaluate the expression if changing from view', function() {
+ * expect(counter.getText()).toContain('0');
+ *
+ * element(by.id('ng-change-example1')).click();
+ *
+ * expect(counter.getText()).toContain('1');
+ * expect(debug.getText()).toContain('true');
+ * });
+ *
+ * it('should not evaluate the expression if changing from model', function() {
+ * element(by.id('ng-change-example2')).click();
+
+ * expect(counter.getText()).toContain('0');
+ * expect(debug.getText()).toContain('true');
+ * });
+ * </file>
+ * </example>
+ */
+var ngChangeDirective = valueFn({
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ ctrl.$viewChangeListeners.push(function() {
+ scope.$eval(attr.ngChange);
+ });
+ }
+});
+
function classDirective(name, selector) {
name = 'ngClass' + name;
return ['$animate', function($animate) {
@@ -31382,8 +30127,9 @@ function classDirective(name, selector) {
* new classes are added.
*
* @animations
- * add - happens just before the class is applied to the element
- * remove - happens just before the class is removed from the element
+ * **add** - happens just before the class is applied to the elements
+ *
+ * **remove** - happens just before the class is removed from the element
*
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
@@ -31630,17 +30376,13 @@ var ngClassEvenDirective = classDirective('Even', 1);
* document; alternatively, the css rule above must be included in the external stylesheet of the
* application.
*
- * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
- * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
- * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
- *
* @element ANY
*
* @example
<example>
<file name="index.html">
<div id="template1" ng-cloak>{{ 'hello' }}</div>
- <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
+ <div id="template2" class="ng-cloak">{{ 'world' }}</div>
</file>
<file name="protractor.js" type="protractor">
it('should remove the template directive and css class', function() {
@@ -32725,7 +31467,7 @@ var ngIfDirective = ['$animate', function($animate) {
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
- url of the template: <tt>{{template.url}}</tt>
+ url of the template: <code>{{template.url}}</code>
<hr/>
<div class="slide-animate-container">
<div class="slide-animate" ng-include="template.url"></div>
@@ -32849,13 +31591,13 @@ var ngIfDirective = ['$animate', function($animate) {
* @name ngInclude#$includeContentError
* @eventType emit on the scope ngInclude was declared in
* @description
- * Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
+ * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
*
* @param {Object} angularEvent Synthetic event object.
* @param {String} src URL of content to load.
*/
-var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce',
- function($templateRequest, $anchorScroll, $animate, $sce) {
+var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
+ function($templateRequest, $anchorScroll, $animate) {
return {
restrict: 'ECA',
priority: 400,
@@ -32891,7 +31633,7 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce
}
};
- scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) {
+ scope.$watch(srcExp, function ngIncludeWatchAction(src) {
var afterAnimation = function() {
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
@@ -32989,7 +31731,7 @@ var ngIncludeFillContentDirective = ['$compile',
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* <pre class="prettyprint">
- * <div ng-init="test1 = (data | orderBy:'name')"></div>
+ * `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* </pre>
* </div>
*
@@ -33039,6 +31781,1472 @@ var ngInitDirective = ngDirective({
/**
* @ngdoc directive
+ * @name ngList
+ *
+ * @description
+ * Text input that converts between a delimited string and an array of strings. The default
+ * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
+ * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
+ *
+ * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
+ * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
+ * list item is respected. This implies that the user of the directive is responsible for
+ * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
+ * tab or newline character.
+ * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
+ * when joining the list items back together) and whitespace around each list item is stripped
+ * before it is added to the model.
+ *
+ * ### Example with Validation
+ *
+ * <example name="ngList-directive" module="listExample">
+ * <file name="app.js">
+ * angular.module('listExample', [])
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.names = ['morpheus', 'neo', 'trinity'];
+ * }]);
+ * </file>
+ * <file name="index.html">
+ * <form name="myForm" ng-controller="ExampleController">
+ * List: <input name="namesInput" ng-model="names" ng-list required>
+ * <span class="error" ng-show="myForm.namesInput.$error.required">
+ * Required!</span>
+ * <br>
+ * <tt>names = {{names}}</tt><br/>
+ * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
+ * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
+ * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ * </form>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * var listInput = element(by.model('names'));
+ * var names = element(by.exactBinding('names'));
+ * var valid = element(by.binding('myForm.namesInput.$valid'));
+ * var error = element(by.css('span.error'));
+ *
+ * it('should initialize to model', function() {
+ * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
+ * expect(valid.getText()).toContain('true');
+ * expect(error.getCssValue('display')).toBe('none');
+ * });
+ *
+ * it('should be invalid if empty', function() {
+ * listInput.clear();
+ * listInput.sendKeys('');
+ *
+ * expect(names.getText()).toContain('');
+ * expect(valid.getText()).toContain('false');
+ * expect(error.getCssValue('display')).not.toBe('none');
+ * });
+ * </file>
+ * </example>
+ *
+ * ### Example - splitting on whitespace
+ * <example name="ngList-directive-newlines">
+ * <file name="index.html">
+ * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
+ * <pre>{{ list | json }}</pre>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * it("should split the text by newlines", function() {
+ * var listInput = element(by.model('list'));
+ * var output = element(by.binding('list | json'));
+ * listInput.sendKeys('abc\ndef\nghi');
+ * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
+ * });
+ * </file>
+ * </example>
+ *
+ * @element input
+ * @param {string=} ngList optional delimiter that should be used to split the value.
+ */
+var ngListDirective = function() {
+ return {
+ restrict: 'A',
+ priority: 100,
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ // We want to control whitespace trimming so we use this convoluted approach
+ // to access the ngList attribute, which doesn't pre-trim the attribute
+ var ngList = element.attr(attr.$attr.ngList) || ', ';
+ var trimValues = attr.ngTrim !== 'false';
+ var separator = trimValues ? trim(ngList) : ngList;
+
+ var parse = function(viewValue) {
+ // If the viewValue is invalid (say required but empty) it will be `undefined`
+ if (isUndefined(viewValue)) return;
+
+ var list = [];
+
+ if (viewValue) {
+ forEach(viewValue.split(separator), function(value) {
+ if (value) list.push(trimValues ? trim(value) : value);
+ });
+ }
+
+ return list;
+ };
+
+ ctrl.$parsers.push(parse);
+ ctrl.$formatters.push(function(value) {
+ if (isArray(value)) {
+ return value.join(ngList);
+ }
+
+ return undefined;
+ });
+
+ // Override the standard $isEmpty because an empty array means the input is empty.
+ ctrl.$isEmpty = function(value) {
+ return !value || !value.length;
+ };
+ }
+ };
+};
+
+/* global VALID_CLASS: true,
+ INVALID_CLASS: true,
+ PRISTINE_CLASS: true,
+ DIRTY_CLASS: true,
+ UNTOUCHED_CLASS: true,
+ TOUCHED_CLASS: true,
+*/
+
+var VALID_CLASS = 'ng-valid',
+ INVALID_CLASS = 'ng-invalid',
+ PRISTINE_CLASS = 'ng-pristine',
+ DIRTY_CLASS = 'ng-dirty',
+ UNTOUCHED_CLASS = 'ng-untouched',
+ TOUCHED_CLASS = 'ng-touched',
+ PENDING_CLASS = 'ng-pending';
+
+var ngModelMinErr = minErr('ngModel');
+
+/**
+ * @ngdoc type
+ * @name ngModel.NgModelController
+ *
+ * @property {string} $viewValue Actual string value in the view.
+ * @property {*} $modelValue The value in the model that the control is bound to.
+ * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
+ the control reads value from the DOM. The functions are called in array order, each passing
+ its return value through to the next. The last return value is forwarded to the
+ {@link ngModel.NgModelController#$validators `$validators`} collection.
+
+Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
+`$viewValue`}.
+
+Returning `undefined` from a parser means a parse error occurred. In that case,
+no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
+will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
+is set to `true`. The parse error is stored in `ngModel.$error.parse`.
+
+ *
+ * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
+ the model value changes. The functions are called in reverse array order, each passing the value through to the
+ next. The last return value is used as the actual DOM value.
+ Used to format / convert values for display in the control.
+ * ```js
+ * function formatter(value) {
+ * if (value) {
+ * return value.toUpperCase();
+ * }
+ * }
+ * ngModel.$formatters.push(formatter);
+ * ```
+ *
+ * @property {Object.<string, function>} $validators A collection of validators that are applied
+ * whenever the model value changes. The key value within the object refers to the name of the
+ * validator while the function refers to the validation operation. The validation operation is
+ * provided with the model value as an argument and must return a true or false value depending
+ * on the response of that validation.
+ *
+ * ```js
+ * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
+ * var value = modelValue || viewValue;
+ * return /[0-9]+/.test(value) &&
+ * /[a-z]+/.test(value) &&
+ * /[A-Z]+/.test(value) &&
+ * /\W+/.test(value);
+ * };
+ * ```
+ *
+ * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
+ * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
+ * is expected to return a promise when it is run during the model validation process. Once the promise
+ * is delivered then the validation status will be set to true when fulfilled and false when rejected.
+ * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
+ * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
+ * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
+ * will only run once all synchronous validators have passed.
+ *
+ * Please note that if $http is used then it is important that the server returns a success HTTP response code
+ * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
+ *
+ * ```js
+ * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
+ * var value = modelValue || viewValue;
+ *
+ * // Lookup user by username
+ * return $http.get('/api/users/' + value).
+ * then(function resolved() {
+ * //username exists, this means validation fails
+ * return $q.reject('exists');
+ * }, function rejected() {
+ * //username does not exist, therefore this validation passes
+ * return true;
+ * });
+ * };
+ * ```
+ *
+ * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
+ * view value has changed. It is called with no arguments, and its return value is ignored.
+ * This can be used in place of additional $watches against the model value.
+ *
+ * @property {Object} $error An object hash with all failing validator ids as keys.
+ * @property {Object} $pending An object hash with all pending validator ids as keys.
+ *
+ * @property {boolean} $untouched True if control has not lost focus yet.
+ * @property {boolean} $touched True if control has lost focus.
+ * @property {boolean} $pristine True if user has not interacted with the control yet.
+ * @property {boolean} $dirty True if user has already interacted with the control.
+ * @property {boolean} $valid True if there is no error.
+ * @property {boolean} $invalid True if at least one error on the control.
+ * @property {string} $name The name attribute of the control.
+ *
+ * @description
+ *
+ * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
+ * The controller contains services for data-binding, validation, CSS updates, and value formatting
+ * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
+ * listening to DOM events.
+ * Such DOM related logic should be provided by other directives which make use of
+ * `NgModelController` for data-binding to control elements.
+ * Angular provides this DOM logic for most {@link input `input`} elements.
+ * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
+ * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
+ *
+ * @example
+ * ### Custom Control Example
+ * This example shows how to use `NgModelController` with a custom control to achieve
+ * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
+ * collaborate together to achieve the desired result.
+ *
+ * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
+ * contents be edited in place by the user.
+ *
+ * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
+ * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
+ * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
+ * that content using the `$sce` service.
+ *
+ * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
+ <file name="style.css">
+ [contenteditable] {
+ border: 1px solid black;
+ background-color: white;
+ min-height: 20px;
+ }
+
+ .ng-invalid {
+ border: 1px solid red;
+ }
+
+ </file>
+ <file name="script.js">
+ angular.module('customControl', ['ngSanitize']).
+ directive('contenteditable', ['$sce', function($sce) {
+ return {
+ restrict: 'A', // only activate on element attribute
+ require: '?ngModel', // get a hold of NgModelController
+ link: function(scope, element, attrs, ngModel) {
+ if (!ngModel) return; // do nothing if no ng-model
+
+ // Specify how UI should be updated
+ ngModel.$render = function() {
+ element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
+ };
+
+ // Listen for change events to enable binding
+ element.on('blur keyup change', function() {
+ scope.$evalAsync(read);
+ });
+ read(); // initialize
+
+ // Write data to the model
+ function read() {
+ var html = element.html();
+ // When we clear the content editable the browser leaves a <br> behind
+ // If strip-br attribute is provided then we strip this out
+ if ( attrs.stripBr && html == '<br>' ) {
+ html = '';
+ }
+ ngModel.$setViewValue(html);
+ }
+ }
+ };
+ }]);
+ </file>
+ <file name="index.html">
+ <form name="myForm">
+ <div contenteditable
+ name="myWidget" ng-model="userContent"
+ strip-br="true"
+ required>Change me!</div>
+ <span ng-show="myForm.myWidget.$error.required">Required!</span>
+ <hr>
+ <textarea ng-model="userContent"></textarea>
+ </form>
+ </file>
+ <file name="protractor.js" type="protractor">
+ it('should data-bind and become invalid', function() {
+ if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
+ // SafariDriver can't handle contenteditable
+ // and Firefox driver can't clear contenteditables very well
+ return;
+ }
+ var contentEditable = element(by.css('[contenteditable]'));
+ var content = 'Change me!';
+
+ expect(contentEditable.getText()).toEqual(content);
+
+ contentEditable.clear();
+ contentEditable.sendKeys(protractor.Key.BACK_SPACE);
+ expect(contentEditable.getText()).toEqual('');
+ expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
+ });
+ </file>
+ * </example>
+ *
+ *
+ */
+var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
+ function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
+ this.$viewValue = Number.NaN;
+ this.$modelValue = Number.NaN;
+ this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
+ this.$validators = {};
+ this.$asyncValidators = {};
+ this.$parsers = [];
+ this.$formatters = [];
+ this.$viewChangeListeners = [];
+ this.$untouched = true;
+ this.$touched = false;
+ this.$pristine = true;
+ this.$dirty = false;
+ this.$valid = true;
+ this.$invalid = false;
+ this.$error = {}; // keep invalid keys here
+ this.$$success = {}; // keep valid keys here
+ this.$pending = undefined; // keep pending keys here
+ this.$name = $interpolate($attr.name || '', false)($scope);
+
+
+ var parsedNgModel = $parse($attr.ngModel),
+ parsedNgModelAssign = parsedNgModel.assign,
+ ngModelGet = parsedNgModel,
+ ngModelSet = parsedNgModelAssign,
+ pendingDebounce = null,
+ parserValid,
+ ctrl = this;
+
+ this.$$setOptions = function(options) {
+ ctrl.$options = options;
+ if (options && options.getterSetter) {
+ var invokeModelGetter = $parse($attr.ngModel + '()'),
+ invokeModelSetter = $parse($attr.ngModel + '($$$p)');
+
+ ngModelGet = function($scope) {
+ var modelValue = parsedNgModel($scope);
+ if (isFunction(modelValue)) {
+ modelValue = invokeModelGetter($scope);
+ }
+ return modelValue;
+ };
+ ngModelSet = function($scope, newValue) {
+ if (isFunction(parsedNgModel($scope))) {
+ invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
+ } else {
+ parsedNgModelAssign($scope, ctrl.$modelValue);
+ }
+ };
+ } else if (!parsedNgModel.assign) {
+ throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
+ $attr.ngModel, startingTag($element));
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$render
+ *
+ * @description
+ * Called when the view needs to be updated. It is expected that the user of the ng-model
+ * directive will implement this method.
+ *
+ * The `$render()` method is invoked in the following situations:
+ *
+ * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
+ * committed value then `$render()` is called to update the input control.
+ * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
+ * the `$viewValue` are different to last time.
+ *
+ * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
+ * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
+ * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
+ * invoked if you only change a property on the objects.
+ */
+ this.$render = noop;
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$isEmpty
+ *
+ * @description
+ * This is called when we need to determine if the value of an input is empty.
+ *
+ * For instance, the required directive does this to work out if the input has data or not.
+ *
+ * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
+ *
+ * You can override this for input directives whose concept of being empty is different to the
+ * default. The `checkboxInputType` directive does this because in its case a value of `false`
+ * implies empty.
+ *
+ * @param {*} value The value of the input to check for emptiness.
+ * @returns {boolean} True if `value` is "empty".
+ */
+ this.$isEmpty = function(value) {
+ return isUndefined(value) || value === '' || value === null || value !== value;
+ };
+
+ var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
+ currentValidationRunId = 0;
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setValidity
+ *
+ * @description
+ * Change the validity state, and notify the form.
+ *
+ * This method can be called within $parsers/$formatters or a custom validation implementation.
+ * However, in most cases it should be sufficient to use the `ngModel.$validators` and
+ * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
+ *
+ * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
+ * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
+ * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
+ * The `validationErrorKey` should be in camelCase and will get converted into dash-case
+ * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
+ * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
+ * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
+ * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
+ * Skipped is used by Angular when validators do not run because of parse errors and
+ * when `$asyncValidators` do not run because any of the `$validators` failed.
+ */
+ addSetValidityMethod({
+ ctrl: this,
+ $element: $element,
+ set: function(object, property) {
+ object[property] = true;
+ },
+ unset: function(object, property) {
+ delete object[property];
+ },
+ parentForm: parentForm,
+ $animate: $animate
+ });
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setPristine
+ *
+ * @description
+ * Sets the control to its pristine state.
+ *
+ * This method can be called to remove the `ng-dirty` class and set the control to its pristine
+ * state (`ng-pristine` class). A model is considered to be pristine when the control
+ * has not been changed from when first compiled.
+ */
+ this.$setPristine = function() {
+ ctrl.$dirty = false;
+ ctrl.$pristine = true;
+ $animate.removeClass($element, DIRTY_CLASS);
+ $animate.addClass($element, PRISTINE_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setDirty
+ *
+ * @description
+ * Sets the control to its dirty state.
+ *
+ * This method can be called to remove the `ng-pristine` class and set the control to its dirty
+ * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
+ * from when first compiled.
+ */
+ this.$setDirty = function() {
+ ctrl.$dirty = true;
+ ctrl.$pristine = false;
+ $animate.removeClass($element, PRISTINE_CLASS);
+ $animate.addClass($element, DIRTY_CLASS);
+ parentForm.$setDirty();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setUntouched
+ *
+ * @description
+ * Sets the control to its untouched state.
+ *
+ * This method can be called to remove the `ng-touched` class and set the control to its
+ * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
+ * by default, however this function can be used to restore that state if the model has
+ * already been touched by the user.
+ */
+ this.$setUntouched = function() {
+ ctrl.$touched = false;
+ ctrl.$untouched = true;
+ $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setTouched
+ *
+ * @description
+ * Sets the control to its touched state.
+ *
+ * This method can be called to remove the `ng-untouched` class and set the control to its
+ * touched state (`ng-touched` class). A model is considered to be touched when the user has
+ * first focused the control element and then shifted focus away from the control (blur event).
+ */
+ this.$setTouched = function() {
+ ctrl.$touched = true;
+ ctrl.$untouched = false;
+ $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$rollbackViewValue
+ *
+ * @description
+ * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
+ * which may be caused by a pending debounced event or because the input is waiting for a some
+ * future event.
+ *
+ * If you have an input that uses `ng-model-options` to set up debounced events or events such
+ * as blur you can have a situation where there is a period when the `$viewValue`
+ * is out of synch with the ngModel's `$modelValue`.
+ *
+ * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
+ * programmatically before these debounced/future events have resolved/occurred, because Angular's
+ * dirty checking mechanism is not able to tell whether the model has actually changed or not.
+ *
+ * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
+ * input which may have such events pending. This is important in order to make sure that the
+ * input field will be updated with the new model value and any pending operations are cancelled.
+ *
+ * <example name="ng-model-cancel-update" module="cancel-update-example">
+ * <file name="app.js">
+ * angular.module('cancel-update-example', [])
+ *
+ * .controller('CancelUpdateController', ['$scope', function($scope) {
+ * $scope.resetWithCancel = function(e) {
+ * if (e.keyCode == 27) {
+ * $scope.myForm.myInput1.$rollbackViewValue();
+ * $scope.myValue = '';
+ * }
+ * };
+ * $scope.resetWithoutCancel = function(e) {
+ * if (e.keyCode == 27) {
+ * $scope.myValue = '';
+ * }
+ * };
+ * }]);
+ * </file>
+ * <file name="index.html">
+ * <div ng-controller="CancelUpdateController">
+ * <p>Try typing something in each input. See that the model only updates when you
+ * blur off the input.
+ * </p>
+ * <p>Now see what happens if you start typing then press the Escape key</p>
+ *
+ * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
+ * <p>With $rollbackViewValue()</p>
+ * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/>
+ * myValue: "{{ myValue }}"
+ *
+ * <p>Without $rollbackViewValue()</p>
+ * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/>
+ * myValue: "{{ myValue }}"
+ * </form>
+ * </div>
+ * </file>
+ * </example>
+ */
+ this.$rollbackViewValue = function() {
+ $timeout.cancel(pendingDebounce);
+ ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
+ ctrl.$render();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$validate
+ *
+ * @description
+ * Runs each of the registered validators (first synchronous validators and then
+ * asynchronous validators).
+ * If the validity changes to invalid, the model will be set to `undefined`,
+ * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
+ * If the validity changes to valid, it will set the model to the last available valid
+ * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
+ */
+ this.$validate = function() {
+ // ignore $validate before model is initialized
+ if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
+ return;
+ }
+
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ // Note: we use the $$rawModelValue as $modelValue might have been
+ // set to undefined during a view -> model update that found validation
+ // errors. We can't parse the view here, since that could change
+ // the model although neither viewValue nor the model on the scope changed
+ var modelValue = ctrl.$$rawModelValue;
+
+ var prevValid = ctrl.$valid;
+ var prevModelValue = ctrl.$modelValue;
+
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+
+ ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
+ // If there was no change in validity, don't update the model
+ // This prevents changing an invalid modelValue to undefined
+ if (!allowInvalid && prevValid !== allValid) {
+ // Note: Don't check ctrl.$valid here, as we could have
+ // external validators (e.g. calculated on the server),
+ // that just call $setValidity and need the model value
+ // to calculate their validity.
+ ctrl.$modelValue = allValid ? modelValue : undefined;
+
+ if (ctrl.$modelValue !== prevModelValue) {
+ ctrl.$$writeModelToScope();
+ }
+ }
+ });
+
+ };
+
+ this.$$runValidators = function(modelValue, viewValue, doneCallback) {
+ currentValidationRunId++;
+ var localValidationRunId = currentValidationRunId;
+
+ // check parser error
+ if (!processParseErrors()) {
+ validationDone(false);
+ return;
+ }
+ if (!processSyncValidators()) {
+ validationDone(false);
+ return;
+ }
+ processAsyncValidators();
+
+ function processParseErrors() {
+ var errorKey = ctrl.$$parserName || 'parse';
+ if (parserValid === undefined) {
+ setValidity(errorKey, null);
+ } else {
+ if (!parserValid) {
+ forEach(ctrl.$validators, function(v, name) {
+ setValidity(name, null);
+ });
+ forEach(ctrl.$asyncValidators, function(v, name) {
+ setValidity(name, null);
+ });
+ }
+ // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
+ setValidity(errorKey, parserValid);
+ return parserValid;
+ }
+ return true;
+ }
+
+ function processSyncValidators() {
+ var syncValidatorsValid = true;
+ forEach(ctrl.$validators, function(validator, name) {
+ var result = validator(modelValue, viewValue);
+ syncValidatorsValid = syncValidatorsValid && result;
+ setValidity(name, result);
+ });
+ if (!syncValidatorsValid) {
+ forEach(ctrl.$asyncValidators, function(v, name) {
+ setValidity(name, null);
+ });
+ return false;
+ }
+ return true;
+ }
+
+ function processAsyncValidators() {
+ var validatorPromises = [];
+ var allValid = true;
+ forEach(ctrl.$asyncValidators, function(validator, name) {
+ var promise = validator(modelValue, viewValue);
+ if (!isPromiseLike(promise)) {
+ throw ngModelMinErr("$asyncValidators",
+ "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
+ }
+ setValidity(name, undefined);
+ validatorPromises.push(promise.then(function() {
+ setValidity(name, true);
+ }, function(error) {
+ allValid = false;
+ setValidity(name, false);
+ }));
+ });
+ if (!validatorPromises.length) {
+ validationDone(true);
+ } else {
+ $q.all(validatorPromises).then(function() {
+ validationDone(allValid);
+ }, noop);
+ }
+ }
+
+ function setValidity(name, isValid) {
+ if (localValidationRunId === currentValidationRunId) {
+ ctrl.$setValidity(name, isValid);
+ }
+ }
+
+ function validationDone(allValid) {
+ if (localValidationRunId === currentValidationRunId) {
+
+ doneCallback(allValid);
+ }
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$commitViewValue
+ *
+ * @description
+ * Commit a pending update 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.
+ */
+ this.$commitViewValue = function() {
+ var viewValue = ctrl.$viewValue;
+
+ $timeout.cancel(pendingDebounce);
+
+ // If the view value has not changed then we should just exit, except in the case where there is
+ // a native validator on the element. In this case the validation state may have changed even though
+ // the viewValue has stayed empty.
+ if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
+ return;
+ }
+ ctrl.$$lastCommittedViewValue = viewValue;
+
+ // change to dirty
+ if (ctrl.$pristine) {
+ this.$setDirty();
+ }
+ this.$$parseAndValidate();
+ };
+
+ this.$$parseAndValidate = function() {
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ var modelValue = viewValue;
+ parserValid = isUndefined(modelValue) ? undefined : true;
+
+ if (parserValid) {
+ for (var i = 0; i < ctrl.$parsers.length; i++) {
+ modelValue = ctrl.$parsers[i](modelValue);
+ if (isUndefined(modelValue)) {
+ parserValid = false;
+ break;
+ }
+ }
+ }
+ if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
+ // ctrl.$modelValue has not been touched yet...
+ ctrl.$modelValue = ngModelGet($scope);
+ }
+ var prevModelValue = ctrl.$modelValue;
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+ ctrl.$$rawModelValue = modelValue;
+
+ if (allowInvalid) {
+ ctrl.$modelValue = modelValue;
+ writeToModelIfNeeded();
+ }
+
+ // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
+ // This can happen if e.g. $setViewValue is called from inside a parser
+ ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
+ if (!allowInvalid) {
+ // Note: Don't check ctrl.$valid here, as we could have
+ // external validators (e.g. calculated on the server),
+ // that just call $setValidity and need the model value
+ // to calculate their validity.
+ ctrl.$modelValue = allValid ? modelValue : undefined;
+ writeToModelIfNeeded();
+ }
+ });
+
+ function writeToModelIfNeeded() {
+ if (ctrl.$modelValue !== prevModelValue) {
+ ctrl.$$writeModelToScope();
+ }
+ }
+ };
+
+ this.$$writeModelToScope = function() {
+ ngModelSet($scope, ctrl.$modelValue);
+ forEach(ctrl.$viewChangeListeners, function(listener) {
+ try {
+ listener();
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setViewValue
+ *
+ * @description
+ * Update the view value.
+ *
+ * This method should be called when an input directive want to change the view value; typically,
+ * this is done from within a DOM event handler.
+ *
+ * For example {@link ng.directive:input input} calls it when the value of the input changes and
+ * {@link ng.directive:select select} calls it when an option is selected.
+ *
+ * If the new `value` is an object (rather than a string or a number), we should make a copy of the
+ * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
+ * watch of objects, it only looks for a change of identity. If you only change the property of
+ * the object then ngModel will not realise that the object has changed and will not invoke the
+ * `$parsers` and `$validators` pipelines.
+ *
+ * For this reason, you should not change properties of the copy once it has been passed to
+ * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
+ *
+ * When this method is called, the new `value` will be staged for committing through the `$parsers`
+ * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
+ * value sent directly for processing, finally to be applied to `$modelValue` and then the
+ * **expression** specified in the `ng-model` attribute.
+ *
+ * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
+ *
+ * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
+ * and the `default` trigger is not listed, all those actions will remain pending until one of the
+ * `updateOn` events is triggered on the DOM element.
+ * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
+ * directive is used with a custom debounce for this particular event.
+ *
+ * Note that calling this function does not trigger a `$digest`.
+ *
+ * @param {string} value Value from the view.
+ * @param {string} trigger Event that triggered the update.
+ */
+ this.$setViewValue = function(value, trigger) {
+ ctrl.$viewValue = value;
+ if (!ctrl.$options || ctrl.$options.updateOnDefault) {
+ ctrl.$$debounceViewValueCommit(trigger);
+ }
+ };
+
+ this.$$debounceViewValueCommit = function(trigger) {
+ var debounceDelay = 0,
+ options = ctrl.$options,
+ debounce;
+
+ if (options && isDefined(options.debounce)) {
+ debounce = options.debounce;
+ if (isNumber(debounce)) {
+ debounceDelay = debounce;
+ } else if (isNumber(debounce[trigger])) {
+ debounceDelay = debounce[trigger];
+ } else if (isNumber(debounce['default'])) {
+ debounceDelay = debounce['default'];
+ }
+ }
+
+ $timeout.cancel(pendingDebounce);
+ if (debounceDelay) {
+ pendingDebounce = $timeout(function() {
+ ctrl.$commitViewValue();
+ }, debounceDelay);
+ } else if ($rootScope.$$phase) {
+ ctrl.$commitViewValue();
+ } else {
+ $scope.$apply(function() {
+ ctrl.$commitViewValue();
+ });
+ }
+ };
+
+ // model -> value
+ // Note: we cannot use a normal scope.$watch as we want to detect the following:
+ // 1. scope value is 'a'
+ // 2. user enters 'b'
+ // 3. ng-change kicks in and reverts scope value to 'a'
+ // -> scope value did not change since the last digest as
+ // ng-change executes in apply phase
+ // 4. view should be changed back to 'a'
+ $scope.$watch(function ngModelWatch() {
+ var modelValue = ngModelGet($scope);
+
+ // if scope model value and ngModel value are out of sync
+ // TODO(perf): why not move this to the action fn?
+ if (modelValue !== ctrl.$modelValue &&
+ // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
+ (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
+ ) {
+ ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
+ parserValid = undefined;
+
+ var formatters = ctrl.$formatters,
+ idx = formatters.length;
+
+ var viewValue = modelValue;
+ while (idx--) {
+ viewValue = formatters[idx](viewValue);
+ }
+ if (ctrl.$viewValue !== viewValue) {
+ ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
+ ctrl.$render();
+
+ ctrl.$$runValidators(modelValue, viewValue, noop);
+ }
+ }
+
+ return modelValue;
+ });
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name ngModel
+ *
+ * @element input
+ * @priority 1
+ *
+ * @description
+ * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
+ * property on the scope using {@link ngModel.NgModelController NgModelController},
+ * which is created and exposed by this directive.
+ *
+ * `ngModel` is responsible for:
+ *
+ * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
+ * require.
+ * - Providing validation behavior (i.e. required, number, email, url).
+ * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
+ * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
+ * - Registering the control with its parent {@link ng.directive:form form}.
+ *
+ * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
+ * current scope. If the property doesn't already exist on this scope, it will be created
+ * implicitly and added to the scope.
+ *
+ * For best practices on using `ngModel`, see:
+ *
+ * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
+ *
+ * For basic examples, how to use `ngModel`, see:
+ *
+ * - {@link ng.directive:input input}
+ * - {@link input[text] text}
+ * - {@link input[checkbox] checkbox}
+ * - {@link input[radio] radio}
+ * - {@link input[number] number}
+ * - {@link input[email] email}
+ * - {@link input[url] url}
+ * - {@link input[date] date}
+ * - {@link input[datetime-local] datetime-local}
+ * - {@link input[time] time}
+ * - {@link input[month] month}
+ * - {@link input[week] week}
+ * - {@link ng.directive:select select}
+ * - {@link ng.directive:textarea textarea}
+ *
+ * # CSS classes
+ * The following CSS classes are added and removed on the associated input/select/textarea element
+ * depending on the validity of the model.
+ *
+ * - `ng-valid`: the model is valid
+ * - `ng-invalid`: the model is invalid
+ * - `ng-valid-[key]`: for each valid key added by `$setValidity`
+ * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
+ * - `ng-pristine`: the control hasn't been interacted with yet
+ * - `ng-dirty`: the control has been interacted with
+ * - `ng-touched`: the control has been blurred
+ * - `ng-untouched`: the control hasn't been blurred
+ * - `ng-pending`: any `$asyncValidators` are unfulfilled
+ *
+ * Keep in mind that ngAnimate can detect each of these classes when added and removed.
+ *
+ * ## Animation Hooks
+ *
+ * Animations within models are triggered when any of the associated CSS classes are added and removed
+ * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
+ * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
+ * The animations that are triggered within ngModel are similar to how they work in ngClass and
+ * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
+ *
+ * The following example shows a simple way to utilize CSS transitions to style an input element
+ * that has been rendered as invalid after it has been validated:
+ *
+ * <pre>
+ * //be sure to include ngAnimate as a module to hook into more
+ * //advanced animations
+ * .my-input {
+ * transition:0.5s linear all;
+ * background: white;
+ * }
+ * .my-input.ng-invalid {
+ * background: red;
+ * color:white;
+ * }
+ * </pre>
+ *
+ * @example
+ * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
+ <file name="index.html">
+ <script>
+ angular.module('inputExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.val = '1';
+ }]);
+ </script>
+ <style>
+ .my-input {
+ -webkit-transition:all linear 0.5s;
+ transition:all linear 0.5s;
+ background: transparent;
+ }
+ .my-input.ng-invalid {
+ color:white;
+ background: red;
+ }
+ </style>
+ Update input to see transitions when valid/invalid.
+ Integer is a valid value.
+ <form name="testForm" ng-controller="ExampleController">
+ <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" />
+ </form>
+ </file>
+ * </example>
+ *
+ * ## Binding to a getter/setter
+ *
+ * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
+ * function that returns a representation of the model when called with zero arguments, and sets
+ * the internal state of a model when called with an argument. It's sometimes useful to use this
+ * for models that have an internal representation that's different than what the model exposes
+ * to the view.
+ *
+ * <div class="alert alert-success">
+ * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
+ * frequently than other parts of your code.
+ * </div>
+ *
+ * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
+ * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
+ * a `<form>`, which will enable this behavior for all `<input>`s within it. See
+ * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
+ *
+ * The following example shows how to use `ngModel` with a getter/setter:
+ *
+ * @example
+ * <example name="ngModel-getter-setter" module="getterSetterExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ getterSetter: true }" />
+ </form>
+ <pre>user.name = <span ng-bind="user.name()"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('getterSetterExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ var _name = 'Brian';
+ $scope.user = {
+ name: function(newName) {
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
+ }
+ };
+ }]);
+ </file>
+ * </example>
+ */
+var ngModelDirective = ['$rootScope', function($rootScope) {
+ return {
+ restrict: 'A',
+ require: ['ngModel', '^?form', '^?ngModelOptions'],
+ controller: NgModelController,
+ // Prelink needs to run before any input directive
+ // so that we can set the NgModelOptions in NgModelController
+ // before anyone else uses it.
+ priority: 1,
+ compile: function ngModelCompile(element) {
+ // Setup initial state of the control
+ element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
+
+ return {
+ pre: function ngModelPreLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0],
+ formCtrl = ctrls[1] || nullFormCtrl;
+
+ modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
+
+ // notify others, especially parent forms
+ formCtrl.$addControl(modelCtrl);
+
+ attr.$observe('name', function(newValue) {
+ if (modelCtrl.$name !== newValue) {
+ formCtrl.$$renameControl(modelCtrl, newValue);
+ }
+ });
+
+ scope.$on('$destroy', function() {
+ formCtrl.$removeControl(modelCtrl);
+ });
+ },
+ post: function ngModelPostLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0];
+ if (modelCtrl.$options && modelCtrl.$options.updateOn) {
+ element.on(modelCtrl.$options.updateOn, function(ev) {
+ modelCtrl.$$debounceViewValueCommit(ev && ev.type);
+ });
+ }
+
+ element.on('blur', function(ev) {
+ if (modelCtrl.$touched) return;
+
+ if ($rootScope.$$phase) {
+ scope.$evalAsync(modelCtrl.$setTouched);
+ } else {
+ scope.$apply(modelCtrl.$setTouched);
+ }
+ });
+ }
+ };
+ }
+ };
+}];
+
+var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
+
+/**
+ * @ngdoc directive
+ * @name ngModelOptions
+ *
+ * @description
+ * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
+ * events that will trigger a model update and/or a debouncing delay so that the actual update only
+ * takes place when a timer expires; this timer will be reset after another change takes place.
+ *
+ * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
+ * be different than the value in the actual model. This means that if you update the model you
+ * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
+ * order to make sure it is synchronized with the model and that any debounced action is canceled.
+ *
+ * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
+ * method is by making sure the input is placed inside a form that has a `name` attribute. This is
+ * important because `form` controllers are published to the related scope under the name in their
+ * `name` attribute.
+ *
+ * Any pending changes will take place immediately when an enclosing form is submitted via the
+ * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
+ * to have access to the updated model.
+ *
+ * `ngModelOptions` has an effect on the element it's declared on and its descendants.
+ *
+ * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
+ * - `updateOn`: string specifying which event should the input be bound to. You can set several
+ * events using an space delimited list. There is a special event called `default` that
+ * matches the default events belonging of the control.
+ * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
+ * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
+ * custom value for each event. For example:
+ * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
+ * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
+ * not validate correctly instead of the default behavior of setting the model to undefined.
+ * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
+ `ngModel` as getters/setters.
+ * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
+ * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
+ * otherwise the default timezone of the browser will be used.
+ *
+ * @example
+
+ The following example shows how to override immediate updates. Changes on the inputs within the
+ form will update the model only when the control loses focus (blur event). If `escape` key is
+ pressed while the input field is focused, the value is reset to the value in the current model.
+
+ <example name="ngModelOptions-directive-blur" module="optionsExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ updateOn: 'blur' }"
+ ng-keyup="cancel($event)" /><br />
+
+ Other data:
+ <input type="text" ng-model="user.data" /><br />
+ </form>
+ <pre>user.name = <span ng-bind="user.name"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('optionsExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.user = { name: 'say', data: '' };
+
+ $scope.cancel = function(e) {
+ if (e.keyCode == 27) {
+ $scope.userForm.userName.$rollbackViewValue();
+ }
+ };
+ }]);
+ </file>
+ <file name="protractor.js" type="protractor">
+ var model = element(by.binding('user.name'));
+ var input = element(by.model('user.name'));
+ var other = element(by.model('user.data'));
+
+ it('should allow custom events', function() {
+ input.sendKeys(' hello');
+ input.click();
+ expect(model.getText()).toEqual('say');
+ other.click();
+ expect(model.getText()).toEqual('say hello');
+ });
+
+ it('should $rollbackViewValue when model changes', function() {
+ input.sendKeys(' hello');
+ expect(input.getAttribute('value')).toEqual('say hello');
+ input.sendKeys(protractor.Key.ESCAPE);
+ expect(input.getAttribute('value')).toEqual('say');
+ other.click();
+ expect(model.getText()).toEqual('say');
+ });
+ </file>
+ </example>
+
+ This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
+ If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
+
+ <example name="ngModelOptions-directive-debounce" module="optionsExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ debounce: 1000 }" />
+ <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
+ </form>
+ <pre>user.name = <span ng-bind="user.name"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('optionsExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.user = { name: 'say' };
+ }]);
+ </file>
+ </example>
+
+ This one shows how to bind to getter/setters:
+
+ <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ getterSetter: true }" />
+ </form>
+ <pre>user.name = <span ng-bind="user.name()"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('getterSetterExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ var _name = 'Brian';
+ $scope.user = {
+ name: function(newName) {
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
+ }
+ };
+ }]);
+ </file>
+ </example>
+ */
+var ngModelOptionsDirective = function() {
+ return {
+ restrict: 'A',
+ controller: ['$scope', '$attrs', function($scope, $attrs) {
+ var that = this;
+ this.$options = $scope.$eval($attrs.ngModelOptions);
+ // Allow adding/overriding bound events
+ if (this.$options.updateOn !== undefined) {
+ this.$options.updateOnDefault = false;
+ // extract "default" pseudo-event from list of events that can trigger a model update
+ this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
+ that.$options.updateOnDefault = true;
+ return ' ';
+ }));
+ } else {
+ this.$options.updateOnDefault = true;
+ }
+ }]
+ };
+};
+
+
+
+// helper methods
+function addSetValidityMethod(context) {
+ var ctrl = context.ctrl,
+ $element = context.$element,
+ classCache = {},
+ set = context.set,
+ unset = context.unset,
+ parentForm = context.parentForm,
+ $animate = context.$animate;
+
+ classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
+
+ ctrl.$setValidity = setValidity;
+
+ function setValidity(validationErrorKey, state, controller) {
+ if (state === undefined) {
+ createAndSet('$pending', validationErrorKey, controller);
+ } else {
+ unsetAndCleanup('$pending', validationErrorKey, controller);
+ }
+ if (!isBoolean(state)) {
+ unset(ctrl.$error, validationErrorKey, controller);
+ unset(ctrl.$$success, validationErrorKey, controller);
+ } else {
+ if (state) {
+ unset(ctrl.$error, validationErrorKey, controller);
+ set(ctrl.$$success, validationErrorKey, controller);
+ } else {
+ set(ctrl.$error, validationErrorKey, controller);
+ unset(ctrl.$$success, validationErrorKey, controller);
+ }
+ }
+ if (ctrl.$pending) {
+ cachedToggleClass(PENDING_CLASS, true);
+ ctrl.$valid = ctrl.$invalid = undefined;
+ toggleValidationCss('', null);
+ } else {
+ cachedToggleClass(PENDING_CLASS, false);
+ ctrl.$valid = isObjectEmpty(ctrl.$error);
+ ctrl.$invalid = !ctrl.$valid;
+ toggleValidationCss('', ctrl.$valid);
+ }
+
+ // re-read the state as the set/unset methods could have
+ // combined state in ctrl.$error[validationError] (used for forms),
+ // where setting/unsetting only increments/decrements the value,
+ // and does not replace it.
+ var combinedState;
+ if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
+ combinedState = undefined;
+ } else if (ctrl.$error[validationErrorKey]) {
+ combinedState = false;
+ } else if (ctrl.$$success[validationErrorKey]) {
+ combinedState = true;
+ } else {
+ combinedState = null;
+ }
+
+ toggleValidationCss(validationErrorKey, combinedState);
+ parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
+ }
+
+ function createAndSet(name, value, controller) {
+ if (!ctrl[name]) {
+ ctrl[name] = {};
+ }
+ set(ctrl[name], value, controller);
+ }
+
+ function unsetAndCleanup(name, value, controller) {
+ if (ctrl[name]) {
+ unset(ctrl[name], value, controller);
+ }
+ if (isObjectEmpty(ctrl[name])) {
+ ctrl[name] = undefined;
+ }
+ }
+
+ function cachedToggleClass(className, switchValue) {
+ if (switchValue && !classCache[className]) {
+ $animate.addClass($element, className);
+ classCache[className] = true;
+ } else if (!switchValue && classCache[className]) {
+ $animate.removeClass($element, className);
+ classCache[className] = false;
+ }
+ }
+
+ function toggleValidationCss(validationErrorKey, isValid) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+
+ cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
+ cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
+ }
+}
+
+function isObjectEmpty(obj) {
+ if (obj) {
+ for (var prop in obj) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * @ngdoc directive
* @name ngNonBindable
* @restrict AC
* @priority 1000
@@ -33322,6 +33530,78 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
*
+ * # Iterating over object properties
+ *
+ * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
+ * syntax:
+ *
+ * ```js
+ * <div ng-repeat="(key, value) in myObj"> ... </div>
+ * ```
+ *
+ * You need to be aware that the JavaScript specification does not define what order
+ * it will return the keys for an object. In order to have a guaranteed deterministic order
+ * for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**.
+ *
+ * If this is not desired, the recommended workaround is to convert your object into an array
+ * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
+ * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
+ * or implement a `$watch` on the object yourself.
+ *
+ * In version 1.4 we will remove the sorting, since it seems that browsers generally follow the
+ * strategy of providing keys in the order in which they were defined, although there are exceptions
+ * when keys are deleted and reinstated.
+ *
+ *
+ * # Tracking and Duplicates
+ *
+ * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
+ *
+ * * When an item is added, a new instance of the template is added to the DOM.
+ * * When an item is removed, its template instance is removed from the DOM.
+ * * When items are reordered, their respective templates are reordered in the DOM.
+ *
+ * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
+ * there are duplicates, it is not possible to maintain a one-to-one mapping between collection
+ * items and DOM elements.
+ *
+ * If you do need to repeat duplicate items, you can substitute the default tracking behavior
+ * with your own using the `track by` expression.
+ *
+ * For example, you may track items by the index of each item in the collection, using the
+ * special scope property `$index`:
+ * ```html
+ * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
+ * {{n}}
+ * </div>
+ * ```
+ *
+ * You may use arbitrary expressions in `track by`, including references to custom functions
+ * on the scope:
+ * ```html
+ * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
+ * {{n}}
+ * </div>
+ * ```
+ *
+ * If you are working with objects that have an identifier property, you can track
+ * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
+ * will not have to rebuild the DOM elements for items it has already rendered, even if the
+ * JavaScript objects in the collection have been substituted for new ones:
+ * ```html
+ * <div ng-repeat="model in collection track by model.id">
+ * {{model.name}}
+ * </div>
+ * ```
+ *
+ * When no `track by` expression is provided, it is equivalent to tracking by the built-in
+ * `$id` function, which tracks items by their identity:
+ * ```html
+ * <div ng-repeat="obj in collection track by $id(obj)">
+ * {{obj.prop}}
+ * </div>
+ * ```
+ *
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
@@ -33389,12 +33669,12 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
*
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
- * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
- * which can be used to associate the objects in the collection with the DOM elements. If no tracking function
- * is specified the ng-repeat associates elements by identity in the collection. It is an error to have
- * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
- * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
- * before specifying a tracking expression.
+ * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
+ * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
+ * is specified, ng-repeat associates elements by identity. It is an error to have
+ * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
+ * mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
+ * applied before the tracking expression.
*
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
* will be associated by item identity in the array.
@@ -33566,7 +33846,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
- /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
+ /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
@@ -33778,10 +34058,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
- * class in CSS:
+ * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
+ * with extra animation classes that can be added.
*
* ```css
- * .ng-hide {
+ * .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
@@ -34170,7 +34451,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
*
* @scope
* @priority 1200
- * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
+ * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
* On child elements add:
*
* * `ngSwitchWhen`: the case statement to match against. If match then this
@@ -34187,7 +34468,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
<div ng-controller="ExampleController">
<select ng-model="selection" ng-options="item for item in items">
</select>
- <tt>selection={{selection}}</tt>
+ <code>selection={{selection}}</code>
<hr/>
<div class="animate-switch-container"
ng-switch on="selection">
@@ -34465,14 +34746,15 @@ var ngOptionsMinErr = minErr('ngOptions');
*
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
* elements for the `<select>` element using the array or object obtained by evaluating the
- * `ngOptions` comprehension_expression.
+ * `ngOptions` comprehension expression.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
- * similar result. However, the `ngOptions` provides some benefits such as reducing memory and
+ * similar result. However, `ngOptions` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance, as well as providing
- * more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
- * used when the `select` model needs to be bound to a non-string value. This is because an option
- * element can only be bound to string values at present.
+ * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
+ * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
+ * to a non-string value. This is because an option element can only be bound to string values at
+ * present.
*
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
@@ -34487,28 +34769,51 @@ var ngOptionsMinErr = minErr('ngOptions');
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
* </div>
*
- * ## `select as`
+ * ## `select` **`as`**
*
- * Using `select as` will bind the result of the `select as` expression to the model, but
+ * Using `select` **`as`** will bind the result of the `select` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
- * or property name (for object data sources) of the value within the collection. If a `track by` expression
+ * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
- * ### `select as` with `track by`
*
- * Using `select as` together with `track by` is not recommended. Reasoning:
+ * ### `select` **`as`** and **`track by`**
+ *
+ * <div class="alert alert-warning">
+ * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
+ * </div>
+ *
+ * Consider the following example:
+ *
+ * ```html
+ * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
+ * ```
+ *
+ * ```js
+ * $scope.values = [{
+ * id: 1,
+ * label: 'aLabel',
+ * subItem: { name: 'aSubItem' }
+ * }, {
+ * id: 2,
+ * label: 'bLabel',
+ * subItem: { name: 'bSubItem' }
+ * }];
+ *
+ * $scope.selected = { name: 'aSubItem' };
+ * ```
+ *
+ * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
+ * of the data source (to `item` in this example). To calculate whether an element is selected, we do the
+ * following:
+ *
+ * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
+ * 2. Apply **`track by`** to the already selected value in `ngModel`.
+ * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
+ * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
+ * a wrong object, the selected element can't be found, `<select>` is always reset to the "not
+ * selected" option.
*
- * - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
- * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
- * $scope.selected = {name: 'aSubItem'};
- * - track by is always applied to `value`, with the purpose of preserving the selection,
- * (to `item` in this case)
- * - to calculate whether an item is selected we do the following:
- * 1. apply `track by` to the values in the array, e.g.
- * In the example: [1,2]
- * 2. apply `track by` to the already selected value in `ngModel`:
- * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
- * value from `ngModel` is `{name: aSubItem}`.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -34744,7 +35049,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
selectElement.val(viewValue);
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
} else {
- if (isUndefined(viewValue) && emptyOption) {
+ if (viewValue == null && emptyOption) {
selectElement.val('');
} else {
selectCtrl.renderUnknownOption(viewValue);
@@ -35208,6 +35513,96 @@ var styleDirective = valueFn({
terminal: false
});
+var requiredDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+ attr.required = true; // force truthy in case we are on non input element
+
+ ctrl.$validators.required = function(modelValue, viewValue) {
+ return !attr.required || !ctrl.$isEmpty(viewValue);
+ };
+
+ attr.$observe('required', function() {
+ ctrl.$validate();
+ });
+ }
+ };
+};
+
+
+var patternDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var regexp, patternExp = attr.ngPattern || attr.pattern;
+ attr.$observe('pattern', function(regex) {
+ if (isString(regex) && regex.length > 0) {
+ regex = new RegExp('^' + regex + '$');
+ }
+
+ if (regex && !regex.test) {
+ throw minErr('ngPattern')('noregexp',
+ 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
+ regex, startingTag(elm));
+ }
+
+ regexp = regex || undefined;
+ ctrl.$validate();
+ });
+
+ ctrl.$validators.pattern = function(value) {
+ return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
+ };
+ }
+ };
+};
+
+
+var maxlengthDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var maxlength = -1;
+ attr.$observe('maxlength', function(value) {
+ var intVal = int(value);
+ maxlength = isNaN(intVal) ? -1 : intVal;
+ ctrl.$validate();
+ });
+ ctrl.$validators.maxlength = function(modelValue, viewValue) {
+ return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
+ };
+ }
+ };
+};
+
+var minlengthDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var minlength = 0;
+ attr.$observe('minlength', function(value) {
+ minlength = int(value) || 0;
+ ctrl.$validate();
+ });
+ ctrl.$validators.minlength = function(modelValue, viewValue) {
+ return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
+ };
+ }
+ };
+};
+
/**
* Setup file for the Scenario.
* Must be first in the compilation/bootstrap list.
@@ -35511,7 +35906,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
var element = windowJquery(this),
bindings;
if (bindings = element.data('$binding')) {
- for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
+ for (var expressions = [], binding, j=0, jj=bindings.length; j < jj; j++) {
binding = bindings[j];
if (binding.expressions) {
@@ -35587,8 +35982,7 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
if (window.WebKitTransitionEvent) {
evnt = new WebKitTransitionEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
- }
- else {
+ } else {
try {
evnt = new TransitionEvent(eventType, eventData);
}
@@ -35597,13 +35991,11 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
evnt.initTransitionEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
- }
- else if (/animationend/.test(eventType)) {
+ } else if (/animationend/.test(eventType)) {
if (window.WebKitAnimationEvent) {
evnt = new WebKitAnimationEvent(eventType, eventData);
evnt.initEvent(eventType, false, true);
- }
- else {
+ } else {
try {
evnt = new AnimationEvent(eventType, eventData);
}
@@ -35612,8 +36004,9 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
evnt.initAnimationEvent(eventType, null, null, null, eventData.elapsedTime || 0);
}
}
- }
- else {
+ } else if (/touch/.test(eventType) && supportsTouchEvents()) {
+ evnt = createTouchEvent(element, eventType, x, y);
+ } else {
evnt = document.createEvent('MouseEvents');
x = x || 0;
y = y || 0;
@@ -35648,6 +36041,37 @@ _jQuery.fn.bindings = function(windowJquery, bindExp) {
return finalProcessDefault;
};
+
+ function supportsTouchEvents() {
+ if ('_cached' in supportsTouchEvents) {
+ return supportsTouchEvents._cached;
+ }
+ if (!document.createTouch || !document.createTouchList) {
+ supportsTouchEvents._cached = false;
+ return false;
+ }
+ try {
+ document.createEvent('TouchEvent');
+ } catch (e) {
+ supportsTouchEvents._cached = false;
+ return false;
+ }
+ supportsTouchEvents._cached = true;
+ return true;
+ }
+
+ function createTouchEvent(element, eventType, x, y) {
+ var evnt = new Event(eventType);
+ x = x || 0;
+ y = y || 0;
+
+ var touch = document.createTouch(window, element, Date.now(), x, y, x, y);
+ var touches = document.createTouchList(touch);
+
+ evnt.touches = touches;
+
+ return evnt;
+ }
}());
/**
@@ -35718,19 +36142,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
try {
var $window = self.getWindow_();
- if ($window.angular) {
- // Disable animations
- $window.angular.resumeBootstrap([['$provide', function($provide) {
- return ['$animate', function($animate) {
- $animate.enabled(false);
- }];
- }]]);
+ if (!$window.angular) {
+ self.executeAction(loadFn);
+ return;
+ }
+
+ if (!$window.angular.resumeBootstrap) {
+ $window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
+ } else {
+ resumeDeferredBootstrap();
}
- self.executeAction(loadFn);
} catch (e) {
errorFn(e);
}
+
+ function resumeDeferredBootstrap() {
+ // Disable animations
+ var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
+ return ['$animate', function($animate) {
+ $animate.enabled(false);
+ }];
+ }]]);
+ self.rootElement = $injector.get('$rootElement')[0];
+ self.executeAction(loadFn);
+ }
}).attr('src', url);
// for IE compatibility set the name *after* setting the frame url
@@ -35755,7 +36191,14 @@ angular.scenario.Application.prototype.executeAction = function(action) {
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
- angularInit($window.document, function(element) {
+
+ if (!!this.rootElement) {
+ executeWithElement(this.rootElement);
+ } else {
+ angularInit($window.document, angular.bind(this, executeWithElement));
+ }
+
+ function executeWithElement(element) {
var $injector = $window.angular.element(element).injector();
var $element = _jQuery(element);
@@ -35768,7 +36211,7 @@ angular.scenario.Application.prototype.executeAction = function(action) {
action.call(self, $window, $element);
});
});
- });
+ }
};
/**
@@ -37387,5 +37830,5 @@ if (config.autotest) {
})(window, document);
-!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide:not(.ng-hide-animate) {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n</style>');
-!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";\n/* CSS Document */\n\n/** Structure */\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n font-size: 14px;\n}\n\n#system-error {\n font-size: 1.5em;\n text-align: center;\n}\n\n#json, #xml {\n display: none;\n}\n\n#header {\n position: fixed;\n width: 100%;\n}\n\n#specs {\n padding-top: 50px;\n}\n\n#header .angular {\n font-family: Courier New, monospace;\n font-weight: bold;\n}\n\n#header h1 {\n font-weight: normal;\n float: left;\n font-size: 30px;\n line-height: 30px;\n margin: 0;\n padding: 10px 10px;\n height: 30px;\n}\n\n#application h2,\n#specs h2 {\n margin: 0;\n padding: 0.5em;\n font-size: 1.1em;\n}\n\n#status-legend {\n margin-top: 10px;\n margin-right: 10px;\n}\n\n#header,\n#application,\n.test-info,\n.test-actions li {\n overflow: hidden;\n}\n\n#application {\n margin: 10px;\n}\n\n#application iframe {\n width: 100%;\n height: 758px;\n}\n\n#application .popout {\n float: right;\n}\n\n#application iframe {\n border: none;\n}\n\n.tests li,\n.test-actions li,\n.test-it li,\n.test-it ol,\n.status-display {\n list-style-type: none;\n}\n\n.tests,\n.test-it ol,\n.status-display {\n margin: 0;\n padding: 0;\n}\n\n.test-info {\n margin-left: 1em;\n margin-top: 0.5em;\n border-radius: 8px 0 0 8px;\n -webkit-border-radius: 8px 0 0 8px;\n -moz-border-radius: 8px 0 0 8px;\n cursor: pointer;\n}\n\n.test-info:hover .test-name {\n text-decoration: underline;\n}\n\n.test-info .closed:before {\n content: \'\\25b8\\00A0\';\n}\n\n.test-info .open:before {\n content: \'\\25be\\00A0\';\n font-weight: bold;\n}\n\n.test-it ol {\n margin-left: 2.5em;\n}\n\n.status-display,\n.status-display li {\n float: right;\n}\n\n.status-display li {\n padding: 5px 10px;\n}\n\n.timer-result,\n.test-title {\n display: inline-block;\n margin: 0;\n padding: 4px;\n}\n\n.test-actions .test-title,\n.test-actions .test-result {\n display: table-cell;\n padding-left: 0.5em;\n padding-right: 0.5em;\n}\n\n.test-actions {\n display: table;\n}\n\n.test-actions li {\n display: table-row;\n}\n\n.timer-result {\n width: 4em;\n padding: 0 10px;\n text-align: right;\n font-family: monospace;\n}\n\n.test-it pre,\n.test-actions pre {\n clear: left;\n color: black;\n margin-left: 6em;\n}\n\n.test-describe {\n padding-bottom: 0.5em;\n}\n\n.test-describe .test-describe {\n margin: 5px 5px 10px 2em;\n}\n\n.test-actions .status-pending .test-title:before {\n content: \'\\00bb\\00A0\';\n}\n\n.scrollpane {\n max-height: 20em;\n overflow: auto;\n}\n\n/** Colors */\n\n#header {\n background-color: #F2C200;\n}\n\n#specs h2 {\n border-top: 2px solid #BABAD1;\n}\n\n#specs h2,\n#application h2 {\n background-color: #efefef;\n}\n\n#application {\n border: 1px solid #BABAD1;\n}\n\n.test-describe .test-describe {\n border-left: 1px solid #BABAD1;\n border-right: 1px solid #BABAD1;\n border-bottom: 1px solid #BABAD1;\n}\n\n.status-display {\n border: 1px solid #777;\n}\n\n.status-display .status-pending,\n.status-pending .test-info {\n background-color: #F9EEBC;\n}\n\n.status-display .status-success,\n.status-success .test-info {\n background-color: #B1D7A1;\n}\n\n.status-display .status-failure,\n.status-failure .test-info {\n background-color: #FF8286;\n}\n\n.status-display .status-error,\n.status-error .test-info {\n background-color: black;\n color: white;\n}\n\n.test-actions .status-success .test-title {\n color: #30B30A;\n}\n\n.test-actions .status-failure .test-title {\n color: #DF0000;\n}\n\n.test-actions .status-error .test-title {\n color: black;\n}\n\n.test-actions .timer-result {\n color: #888;\n}\n</style>'); \ No newline at end of file
+!window.angular.$$csp() && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";\n\n[ng\\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak],\n.ng-cloak, .x-ng-cloak,\n.ng-hide:not(.ng-hide-animate) {\n display: none !important;\n}\n\nng\\:form {\n display: block;\n}\n</style>');
+!window.angular.$$csp() && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";\n/* CSS Document */\n\n/** Structure */\nbody {\n font-family: Arial, sans-serif;\n margin: 0;\n font-size: 14px;\n}\n\n#system-error {\n font-size: 1.5em;\n text-align: center;\n}\n\n#json, #xml {\n display: none;\n}\n\n#header {\n position: fixed;\n width: 100%;\n}\n\n#specs {\n padding-top: 50px;\n}\n\n#header .angular {\n font-family: Courier New, monospace;\n font-weight: bold;\n}\n\n#header h1 {\n font-weight: normal;\n float: left;\n font-size: 30px;\n line-height: 30px;\n margin: 0;\n padding: 10px 10px;\n height: 30px;\n}\n\n#application h2,\n#specs h2 {\n margin: 0;\n padding: 0.5em;\n font-size: 1.1em;\n}\n\n#status-legend {\n margin-top: 10px;\n margin-right: 10px;\n}\n\n#header,\n#application,\n.test-info,\n.test-actions li {\n overflow: hidden;\n}\n\n#application {\n margin: 10px;\n}\n\n#application iframe {\n width: 100%;\n height: 758px;\n}\n\n#application .popout {\n float: right;\n}\n\n#application iframe {\n border: none;\n}\n\n.tests li,\n.test-actions li,\n.test-it li,\n.test-it ol,\n.status-display {\n list-style-type: none;\n}\n\n.tests,\n.test-it ol,\n.status-display {\n margin: 0;\n padding: 0;\n}\n\n.test-info {\n margin-left: 1em;\n margin-top: 0.5em;\n border-radius: 8px 0 0 8px;\n -webkit-border-radius: 8px 0 0 8px;\n -moz-border-radius: 8px 0 0 8px;\n cursor: pointer;\n}\n\n.test-info:hover .test-name {\n text-decoration: underline;\n}\n\n.test-info .closed:before {\n content: \'\\25b8\\00A0\';\n}\n\n.test-info .open:before {\n content: \'\\25be\\00A0\';\n font-weight: bold;\n}\n\n.test-it ol {\n margin-left: 2.5em;\n}\n\n.status-display,\n.status-display li {\n float: right;\n}\n\n.status-display li {\n padding: 5px 10px;\n}\n\n.timer-result,\n.test-title {\n display: inline-block;\n margin: 0;\n padding: 4px;\n}\n\n.test-actions .test-title,\n.test-actions .test-result {\n display: table-cell;\n padding-left: 0.5em;\n padding-right: 0.5em;\n}\n\n.test-actions {\n display: table;\n}\n\n.test-actions li {\n display: table-row;\n}\n\n.timer-result {\n width: 4em;\n padding: 0 10px;\n text-align: right;\n font-family: monospace;\n}\n\n.test-it pre,\n.test-actions pre {\n clear: left;\n color: black;\n margin-left: 6em;\n}\n\n.test-describe {\n padding-bottom: 0.5em;\n}\n\n.test-describe .test-describe {\n margin: 5px 5px 10px 2em;\n}\n\n.test-actions .status-pending .test-title:before {\n content: \'\\00bb\\00A0\';\n}\n\n.scrollpane {\n max-height: 20em;\n overflow: auto;\n}\n\n/** Colors */\n\n#header {\n background-color: #F2C200;\n}\n\n#specs h2 {\n border-top: 2px solid #BABAD1;\n}\n\n#specs h2,\n#application h2 {\n background-color: #efefef;\n}\n\n#application {\n border: 1px solid #BABAD1;\n}\n\n.test-describe .test-describe {\n border-left: 1px solid #BABAD1;\n border-right: 1px solid #BABAD1;\n border-bottom: 1px solid #BABAD1;\n}\n\n.status-display {\n border: 1px solid #777;\n}\n\n.status-display .status-pending,\n.status-pending .test-info {\n background-color: #F9EEBC;\n}\n\n.status-display .status-success,\n.status-success .test-info {\n background-color: #B1D7A1;\n}\n\n.status-display .status-failure,\n.status-failure .test-info {\n background-color: #FF8286;\n}\n\n.status-display .status-error,\n.status-error .test-info {\n background-color: black;\n color: white;\n}\n\n.test-actions .status-success .test-title {\n color: #30B30A;\n}\n\n.test-actions .status-failure .test-title {\n color: #DF0000;\n}\n\n.test-actions .status-error .test-title {\n color: black;\n}\n\n.test-actions .timer-result {\n color: #888;\n}\n</style>'); \ No newline at end of file
diff --git a/xstatic/pkg/angular/data/angular-touch.js b/xstatic/pkg/angular/data/angular-touch.js
index d53a732..d9a43cc 100644
--- a/xstatic/pkg/angular/data/angular-touch.js
+++ b/xstatic/pkg/angular/data/angular-touch.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -27,6 +27,10 @@
/* global -ngTouch */
var ngTouch = angular.module('ngTouch', []);
+function nodeName_(element) {
+ return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName));
+}
+
/* global ngTouch: false */
/**
@@ -67,11 +71,9 @@ ngTouch.factory('$swipe', [function() {
};
function getCoordinates(event) {
- var touches = event.touches && event.touches.length ? event.touches : [event];
- var e = (event.changedTouches && event.changedTouches[0]) ||
- (event.originalEvent && event.originalEvent.changedTouches &&
- event.originalEvent.changedTouches[0]) ||
- touches[0].originalEvent || touches[0];
+ var originalEvent = event.originalEvent || event;
+ var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent];
+ var e = (originalEvent.changedTouches && originalEvent.changedTouches[0]) || touches[0];
return {
x: e.clientX,
@@ -193,7 +195,9 @@ ngTouch.factory('$swipe', [function() {
};
}]);
-/* global ngTouch: false */
+/* global ngTouch: false,
+ nodeName_: false
+*/
/**
* @ngdoc directive
@@ -335,7 +339,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
lastLabelClickCoordinates = null;
}
// remember label click coordinates to prevent click busting of trigger click event on input
- if (event.target.tagName.toLowerCase() === 'label') {
+ if (nodeName_(event.target) === 'label') {
lastLabelClickCoordinates = [x, y];
}
@@ -351,7 +355,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
event.preventDefault();
// Blur focused form elements
- event.target && event.target.blur();
+ event.target && event.target.blur && event.target.blur();
}
@@ -414,8 +418,10 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
startTime = Date.now();
- var touches = event.touches && event.touches.length ? event.touches : [event];
- var e = touches[0].originalEvent || touches[0];
+ // Use jQuery originalEvent
+ var originalEvent = event.originalEvent || event;
+ var touches = originalEvent.touches && originalEvent.touches.length ? originalEvent.touches : [originalEvent];
+ var e = touches[0];
touchStartX = e.clientX;
touchStartY = e.clientY;
});
@@ -431,9 +437,12 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement',
element.on('touchend', function(event) {
var diff = Date.now() - startTime;
- var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches :
- ((event.touches && event.touches.length) ? event.touches : [event]);
- var e = touches[0].originalEvent || touches[0];
+ // Use jQuery originalEvent
+ var originalEvent = event.originalEvent || event;
+ var touches = (originalEvent.changedTouches && originalEvent.changedTouches.length) ?
+ originalEvent.changedTouches :
+ ((originalEvent.touches && originalEvent.touches.length) ? originalEvent.touches : [originalEvent]);
+ var e = touches[0];
var x = e.clientX;
var y = e.clientY;
var dist = Math.sqrt(Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2));
diff --git a/xstatic/pkg/angular/data/angular.js b/xstatic/pkg/angular/data/angular.js
index f12180f..7ab67fb 100644
--- a/xstatic/pkg/angular/data/angular.js
+++ b/xstatic/pkg/angular/data/angular.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.7
+ * @license AngularJS v1.3.18
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
@@ -54,7 +54,7 @@ function minErr(module, ErrorConstructor) {
return match;
});
- message = message + '\nhttp://errors.angularjs.org/1.3.7/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.18/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
@@ -149,6 +149,7 @@ function minErr(module, ErrorConstructor) {
createMap: true,
NODE_TYPE_ELEMENT: true,
+ NODE_TYPE_ATTRIBUTE: true,
NODE_TYPE_TEXT: true,
NODE_TYPE_COMMENT: true,
NODE_TYPE_DOCUMENT: true,
@@ -260,7 +261,9 @@ function isArrayLike(obj) {
return false;
}
- var length = obj.length;
+ // Support: iOS 8.2 (not reproducible in simulator)
+ // "length" in obj used to prevent JIT error (gh-11508)
+ var length = "length" in Object(obj) && obj.length;
if (obj.nodeType === NODE_TYPE_ELEMENT && length) {
return true;
@@ -381,8 +384,7 @@ function nextUid() {
function setHashKey(obj, h) {
if (h) {
obj.$$hashKey = h;
- }
- else {
+ } else {
delete obj.$$hashKey;
}
}
@@ -465,6 +467,8 @@ noop.$inject = [];
return (transformationFn || angular.identity)(value);
};
```
+ * @param {*} value to be returned.
+ * @returns {*} the value passed in.
*/
function identity($) {return $;}
identity.$inject = [];
@@ -545,6 +549,12 @@ function isString(value) {return typeof value === 'string';}
* @description
* Determines if a reference is a `Number`.
*
+ * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`.
+ *
+ * If you wish to exclude these then you can use the native
+ * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite)
+ * method.
+ *
* @param {*} value Reference to check.
* @returns {boolean} True if `value` is a `Number`.
*/
@@ -689,7 +699,7 @@ function isElement(node) {
function makeMap(str) {
var obj = {}, items = str.split(","), i;
for (i = 0; i < items.length; i++)
- obj[ items[i] ] = true;
+ obj[items[i]] = true;
return obj;
}
@@ -719,7 +729,7 @@ function arrayRemove(array, value) {
* Creates a deep copy of `source`, which should be an object or an array.
*
* * If no destination is supplied, a copy of the object or array is created.
- * * If a destination is provided, all of its elements (for array) or properties (for objects)
+ * * If a destination is provided, all of its elements (for arrays) or properties (for objects)
* are deleted and then all elements/properties from the source are copied to it.
* * If `source` is not an object or array (inc. `null` and `undefined`), `source` is returned.
* * If `source` is identical to 'destination' an exception will be thrown.
@@ -913,10 +923,11 @@ function equals(o1, o2) {
} else if (isDate(o1)) {
if (!isDate(o2)) return false;
return equals(o1.getTime(), o2.getTime());
- } else if (isRegExp(o1) && isRegExp(o2)) {
- return o1.toString() == o2.toString();
+ } else if (isRegExp(o1)) {
+ return isRegExp(o2) ? o1.toString() == o2.toString() : false;
} else {
- if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false;
+ if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) ||
+ isArray(o2) || isDate(o2) || isRegExp(o2)) return false;
keySet = {};
for (key in o1) {
if (key.charAt(0) === '$' || isFunction(o1[key])) continue;
@@ -1034,8 +1045,8 @@ function toJsonReplacer(key, value) {
* stripped since angular uses this notation internally.
*
* @param {Object|Array|Date|string|number} obj Input to be serialized into JSON.
- * @param {boolean|number=} pretty If set to true, the JSON output will contain newlines and whitespace.
- * If set to an integer, the JSON output will contain that many spaces per indentation (the default is 2).
+ * @param {boolean|number} [pretty=2] If set to true, the JSON output will contain newlines and whitespace.
+ * If set to an integer, the JSON output will contain that many spaces per indentation.
* @returns {string|undefined} JSON-ified string representing `obj`.
*/
function toJson(obj, pretty) {
@@ -1057,7 +1068,7 @@ function toJson(obj, pretty) {
* Deserializes a JSON string.
*
* @param {string} json JSON string to deserialize.
- * @returns {Object|Array|string|number} Deserialized thingy.
+ * @returns {Object|Array|string|number} Deserialized JSON string.
*/
function fromJson(json) {
return isString(json)
@@ -1230,7 +1241,7 @@ function getNgAttribute(element, ngAttr) {
* {@link angular.bootstrap} instead. AngularJS applications cannot be nested within each other.
*
* You can specify an **AngularJS module** to be used as the root module for the application. This
- * module will be loaded into the {@link auto.$injector} when the application is bootstrapped and
+ * module will be loaded into the {@link auto.$injector} when the application is bootstrapped. It
* should contain the application code needed or have dependencies on other modules that will
* contain the code. See {@link angular.module} for more information.
*
@@ -1238,7 +1249,7 @@ function getNgAttribute(element, ngAttr) {
* document would not be compiled, the `AppController` would not be instantiated and the `{{ a+b }}`
* would not be resolved to `3`.
*
- * `ngApp` is the easiest, and most common, way to bootstrap an application.
+ * `ngApp` is the easiest, and most common way to bootstrap an application.
*
<example module="ngAppDemo">
<file name="index.html">
@@ -1400,7 +1411,7 @@ function angularInit(element, bootstrap) {
* @param {DOMElement} element DOM element which is the root of angular application.
* @param {Array<String|Function|Array>=} modules an array of modules to load into the application.
* Each item in the array should be the name of a predefined module or a (DI annotated)
- * function that will be invoked by the injector as a run block.
+ * function that will be invoked by the injector as a `config` block.
* See: {@link angular.module modules}
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
@@ -1470,8 +1481,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
- doBootstrap();
+ return doBootstrap();
};
+
+ if (isFunction(angular.resumeDeferredBootstrap)) {
+ angular.resumeDeferredBootstrap();
+ }
}
/**
@@ -1663,6 +1678,7 @@ function createMap() {
}
var NODE_TYPE_ELEMENT = 1;
+var NODE_TYPE_ATTRIBUTE = 2;
var NODE_TYPE_TEXT = 3;
var NODE_TYPE_COMMENT = 8;
var NODE_TYPE_DOCUMENT = 9;
@@ -1899,10 +1915,17 @@ function setupModuleLoader(window) {
* @ngdoc method
* @name angular.Module#filter
* @module ng
- * @param {string} name Filter name.
+ * @param {string} name Filter name - this must be a valid angular expression identifier
* @param {Function} filterFactory Factory function for creating new instance of filter.
* @description
* See {@link ng.$filterProvider#register $filterProvider.register()}.
+ *
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
*/
filter: invokeLater('$filterProvider', 'register'),
@@ -2116,11 +2139,11 @@ function toDebugString(obj) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.7', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.18', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
- dot: 7,
- codeName: 'leaky-obstruction'
+ dot: 18,
+ codeName: 'collective-penmanship'
};
@@ -2257,6 +2280,17 @@ function publishExternalAPI(angular) {
]);
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
/* global JQLitePrototype: true,
addEventListenerFn: true,
removeEventListenerFn: true,
@@ -2285,7 +2319,7 @@ function publishExternalAPI(angular) {
* Angular to manipulate the DOM in a cross-browser compatible way. **jqLite** implements only the most
* commonly needed functionality with the goal of having a very small footprint.</div>
*
- * To use jQuery, simply load it before `DOMContentLoaded` event fired.
+ * To use `jQuery`, simply ensure it is loaded before the `angular.js` file.
*
* <div class="alert">**Note:** all element references in Angular are always wrapped with jQuery or
* jqLite; they are never raw DOM references.</div>
@@ -2301,7 +2335,7 @@ function publishExternalAPI(angular) {
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
- * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`
+ * - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyle()`. As a setter, does not convert numbers to strings or append 'px'.
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
@@ -2844,6 +2878,10 @@ forEach({
},
attr: function(element, name, value) {
+ var nodeType = element.nodeType;
+ if (nodeType === NODE_TYPE_TEXT || nodeType === NODE_TYPE_ATTRIBUTE || nodeType === NODE_TYPE_COMMENT) {
+ return;
+ }
var lowercasedName = lowercase(name);
if (BOOLEAN_ATTR[lowercasedName]) {
if (isDefined(value)) {
@@ -3534,7 +3572,7 @@ function annotate(fn, strictDi, name) {
* Return an instance of the service.
*
* @param {string} name The name of the instance to retrieve.
- * @param {string} caller An optional string to provide the origin of the function call for error messages.
+ * @param {string=} caller An optional string to provide the origin of the function call for error messages.
* @return {*} The instance.
*/
@@ -3545,8 +3583,8 @@ function annotate(fn, strictDi, name) {
* @description
* Invoke the method and supply the method arguments from the `$injector`.
*
- * @param {!Function} fn The function to invoke. Function parameters are injected according to the
- * {@link guide/di $inject Annotation} rules.
+ * @param {Function|Array.<string|Function>} fn The injectable function to invoke. Function parameters are
+ * injected according to the {@link guide/di $inject Annotation} rules.
* @param {Object=} self The `this` for the invoked method.
* @param {Object=} locals Optional object. If preset then any argument names are read from this
* object first, before the `$injector` is consulted.
@@ -3813,8 +3851,8 @@ function annotate(fn, strictDi, name) {
* configure your service in a provider.
*
* @param {string} name The name of the instance.
- * @param {function()} $getFn The $getFn for the instance creation. Internally this is a short hand
- * for `$provide.provider(name, {$get: $getFn})`.
+ * @param {Function|Array.<string|Function>} $getFn The injectable $getFn for the instance creation.
+ * Internally this is a short hand for `$provide.provider(name, {$get: $getFn})`.
* @returns {Object} registered provider instance
*
* @example
@@ -3849,7 +3887,8 @@ function annotate(fn, strictDi, name) {
* as a type/class.
*
* @param {string} name The name of the instance.
- * @param {Function} constructor A class (constructor function) that will be instantiated.
+ * @param {Function|Array.<string|Function>} constructor An injectable class (constructor function)
+ * that will be instantiated.
* @returns {Object} registered provider instance
*
* @example
@@ -3948,7 +3987,7 @@ function annotate(fn, strictDi, name) {
* object which replaces or wraps and delegates to the original service.
*
* @param {string} name The name of the service to decorate.
- * @param {function()} decorator This function will be invoked when the service needs to be
+ * @param {Function|Array.<string|Function>} decorator This function will be invoked when the service needs to be
* instantiated and should return the decorated service instance. The function is called using
* the {@link auto.$injector#invoke injector.invoke} method and is therefore fully injectable.
* Local injection arguments:
@@ -4155,7 +4194,7 @@ function createInjector(modulesToLoad, strictDi) {
}
var args = [],
- $inject = annotate(fn, strictDi, serviceName),
+ $inject = createInjector.$$annotate(fn, strictDi, serviceName),
length, i,
key;
@@ -4184,7 +4223,7 @@ function createInjector(modulesToLoad, strictDi) {
// Check if Type is annotated and use just the given function at n-1 as parameter
// e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
// Object creation: http://jsperf.com/create-constructor/2
- var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype || null);
var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
@@ -4194,7 +4233,7 @@ function createInjector(modulesToLoad, strictDi) {
invoke: invoke,
instantiate: instantiate,
get: getService,
- annotate: annotate,
+ annotate: createInjector.$$annotate,
has: function(name) {
return providerCache.hasOwnProperty(name + providerSuffix) || cache.hasOwnProperty(name);
}
@@ -4680,6 +4719,7 @@ var $AnimateProvider = ['$provide', function($provide) {
* @return {Promise} the animation callback promise
*/
leave: function(element, options) {
+ applyStyles(element, options);
element.remove();
return asyncPromise();
},
@@ -4914,7 +4954,7 @@ function Browser(window, document, $log, $sniffer) {
function getHash(url) {
var index = url.indexOf('#');
- return index === -1 ? '' : url.substr(index + 1);
+ return index === -1 ? '' : url.substr(index);
}
/**
@@ -5041,7 +5081,7 @@ function Browser(window, document, $log, $sniffer) {
// Do the assignment again so that those two variables are referentially identical.
lastHistoryState = cachedState;
} else {
- if (!sameBase) {
+ if (!sameBase || reloadLocation) {
reloadLocation = url;
}
if (replace) {
@@ -5084,11 +5124,19 @@ function Browser(window, document, $log, $sniffer) {
fireUrlChange();
}
+ function getCurrentState() {
+ try {
+ return history.state;
+ } catch (e) {
+ // MSIE can reportedly throw when there is no state (UNCONFIRMED).
+ }
+ }
+
// This variable should be used *only* inside the cacheState function.
var lastCachedState = null;
function cacheState() {
// This should be the only place in $browser where `history.state` is read.
- cachedState = window.history.state;
+ cachedState = getCurrentState();
cachedState = isUndefined(cachedState) ? null : cachedState;
// Prevent callbacks fo fire twice if both hashchange & popstate were fired.
@@ -5685,7 +5733,7 @@ function $CacheFactoryProvider() {
* the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE,
* element with ng-app attribute), otherwise the template will be ignored.
*
- * Adding via the $templateCache service:
+ * Adding via the `$templateCache` service:
*
* ```js
* var myApp = angular.module('myApp', []);
@@ -5713,6 +5761,17 @@ function $TemplateCacheProvider() {
}];
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
/* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE!
*
* DOM-related variables:
@@ -5777,7 +5836,8 @@ function $TemplateCacheProvider() {
* templateNamespace: 'html',
* scope: false,
* controller: function($scope, $element, $attrs, $transclude, otherInjectables) { ... },
- * controllerAs: 'stringAlias',
+ * controllerAs: 'stringIdentifier',
+ * bindToController: false,
* require: 'siblingDirectiveName', // or // ['^parentDirectiveName', '?optionalDirectiveName', '?^optionalParent'],
* compile: function compile(tElement, tAttrs, transclude) {
* return {
@@ -5924,7 +5984,8 @@ function $TemplateCacheProvider() {
* Require another directive and inject its controller as the fourth argument to the linking function. The
* `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the
* injected argument will be an array in corresponding order. If no such directive can be
- * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with:
+ * found, or if the directive does not have a controller, then an error is raised (unless no link function
+ * is specified, in which case error checking is skipped). The name can be prefixed with:
*
* * (no prefix) - Locate the required controller on the current element. Throw an error if not found.
* * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found.
@@ -6095,9 +6156,15 @@ function $TemplateCacheProvider() {
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
* between all directive linking functions.
*
- * * `controller` - a controller instance - A controller instance if at least one directive on the
- * element defines a controller. The controller is shared among all the directives, which allows
- * the directives to use the controllers as a communication channel.
+ * * `controller` - the directive's required controller instance(s) - Instances are shared
+ * among all directives, which allows the directives to use the controllers as a communication
+ * channel. The exact value depends on the directive's `require` property:
+ * * `string`: the controller instance
+ * * `array`: array of controller instances
+ * * no controller(s) required: `undefined`
+ *
+ * If a required controller cannot be found, and it is optional, the instance is `null`,
+ * otherwise the {@link error:$compile:ctreq Missing Required Controller} error is thrown.
*
* * `transcludeFn` - A transclude linking function pre-bound to the correct transclusion scope.
* This is the same as the `$transclude`
@@ -6123,7 +6190,7 @@ function $TemplateCacheProvider() {
*
* ### Transclusion
*
- * Transclusion is the process of extracting a collection of DOM element from one part of the DOM and
+ * Transclusion is the process of extracting a collection of DOM elements from one part of the DOM and
* copying them to another part of the DOM, while maintaining their connection to the original AngularJS
* scope from where they were taken.
*
@@ -6451,6 +6518,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
return bindings;
}
+ function assertValidDirectiveName(name) {
+ var letter = name.charAt(0);
+ if (!letter || letter !== lowercase(letter)) {
+ throw $compileMinErr('baddir', "Directive name '{0}' is invalid. The first character must be a lowercase letter", name);
+ }
+ return name;
+ }
+
/**
* @ngdoc method
* @name $compileProvider#directive
@@ -6469,6 +6544,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
this.directive = function registerDirective(name, directiveFactory) {
assertNotHasOwnProperty(name, 'directive');
if (isString(name)) {
+ assertValidDirectiveName(name);
assertArg(directiveFactory, 'directiveFactory');
if (!hasDirectives.hasOwnProperty(name)) {
hasDirectives[name] = [];
@@ -7163,6 +7239,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
// use class as directive
className = node.className;
+ if (isObject(className)) {
+ // Maybe SVGAnimatedString
+ className = className.animVal;
+ }
if (isString(className) && className !== '') {
while (match = CLASS_DIRECTIVE_REGEXP.exec(className)) {
nName = directiveNormalize(match[2]);
@@ -7864,8 +7944,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
afterTemplateChildLinkFn,
beforeTemplateCompileNode = $compileNode[0],
origAsyncDirective = directives.shift(),
- // The fact that we have to copy and patch the directive seems wrong!
- derivedSyncDirective = extend({}, origAsyncDirective, {
+ derivedSyncDirective = inherit(origAsyncDirective, {
templateUrl: null, transclude: null, replace: null, $$originalDirective: origAsyncDirective
}),
templateUrl = (isFunction(origAsyncDirective.templateUrl))
@@ -7875,7 +7954,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
$compileNode.empty();
- $templateRequest($sce.getTrustedResourceUrl(templateUrl))
+ $templateRequest(templateUrl)
.then(function(content) {
var compileNode, tempTemplateAttrs, $template, childBoundTranscludeFn;
@@ -8318,6 +8397,8 @@ function removeComments(jqNodes) {
return jqNodes;
}
+var $controllerMinErr = minErr('$controller');
+
/**
* @ngdoc provider
* @name $controllerProvider
@@ -8405,7 +8486,12 @@ function $ControllerProvider() {
}
if (isString(expression)) {
- match = expression.match(CNTRL_REG),
+ match = expression.match(CNTRL_REG);
+ if (!match) {
+ throw $controllerMinErr('ctrlfmt',
+ "Badly formed controller string '{0}'. " +
+ "Must match `__name__ as __id__` or `__name__`.", expression);
+ }
constructor = match[1],
identifier = identifier || match[3];
expression = controllers.hasOwnProperty(constructor)
@@ -8429,7 +8515,7 @@ function $ControllerProvider() {
// Object creation: http://jsperf.com/create-constructor/2
var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
- instance = Object.create(controllerPrototype);
+ instance = Object.create(controllerPrototype || null);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
@@ -8915,7 +9001,7 @@ function $HttpProvider() {
* headers: {
* 'Content-Type': undefined
* },
- * data: { test: 'test' },
+ * data: { test: 'test' }
* }
*
* $http(req).success(function(){...}).error(function(){...});
@@ -8925,7 +9011,7 @@ function $HttpProvider() {
*
* Both requests and responses can be transformed using transformation functions: `transformRequest`
* and `transformResponse`. These properties can be a single function that returns
- * the transformed value (`{function(data, headersGetter, status)`) or an array of such transformation functions,
+ * the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
*
* ### Default Transformations
@@ -9350,6 +9436,8 @@ function $HttpProvider() {
}
promise.success = function(fn) {
+ assertArgFn(fn, 'fn');
+
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -9357,6 +9445,8 @@ function $HttpProvider() {
};
promise.error = function(fn) {
+ assertArgFn(fn, 'fn');
+
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
@@ -9758,7 +9848,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
xhr.onload = function requestLoaded() {
var statusText = xhr.statusText || '';
- // responseText is the old-school way of retrieving response (supported by IE8 & 9)
+ // responseText is the old-school way of retrieving response (supported by IE9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
var response = ('response' in xhr) ? xhr.response : xhr.responseText;
@@ -9837,7 +9927,7 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
};
function jsonpReq(url, callbackId, done) {
- // we can't use jQuery/jqLite here because jQuery does crazy shit with script elements, e.g.:
+ // we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
var script = rawDocument.createElement('script'), callback = null;
@@ -10464,7 +10554,15 @@ function $LocaleProvider() {
mediumDate: 'MMM d, y',
shortDate: 'M/d/yy',
mediumTime: 'h:mm:ss a',
- shortTime: 'h:mm a'
+ shortTime: 'h:mm a',
+ ERANAMES: [
+ "Before Christ",
+ "Anno Domini"
+ ],
+ ERAS: [
+ "BC",
+ "AD"
+ ]
},
pluralCat: function(num) {
@@ -10662,7 +10760,7 @@ function LocationHashbangUrl(appBase, hashPrefix) {
var withoutBaseUrl = beginsWith(appBase, url) || beginsWith(appBaseNoFile, url);
var withoutHashUrl;
- if (withoutBaseUrl.charAt(0) === '#') {
+ if (!isUndefined(withoutBaseUrl) && withoutBaseUrl.charAt(0) === '#') {
// The rest of the url starts with a hash so we have
// got either a hashbang path or a plain hash fragment
@@ -10676,7 +10774,15 @@ function LocationHashbangUrl(appBase, hashPrefix) {
// There was no hashbang path nor hash fragment:
// If we are in HTML5 mode we use what is left as the path;
// Otherwise we ignore what is left
- withoutHashUrl = this.$$html5 ? withoutBaseUrl : '';
+ if (this.$$html5) {
+ withoutHashUrl = withoutBaseUrl;
+ } else {
+ withoutHashUrl = '';
+ if (isUndefined(withoutBaseUrl)) {
+ appBase = url;
+ this.replace();
+ }
+ }
}
parseAppUrl(withoutHashUrl, this);
@@ -10786,7 +10892,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
hash = this.$$hash ? '#' + encodeUriSegment(this.$$hash) : '';
this.$$url = encodePath(this.$$path) + (search ? '?' + search : '') + hash;
- // include hashPrefix in $$absUrl when $$url is empty so IE8 & 9 do not reload page because of removal of '#'
+ // include hashPrefix in $$absUrl when $$url is empty so IE9 does not reload page because of removal of '#'
this.$$absUrl = appBase + hashPrefix + this.$$url;
};
@@ -10890,11 +10996,19 @@ var locationPrototype = {
*
* Return host of current url.
*
+ * Note: compared to the non-angular version `location.host` which returns `hostname:port`, this returns the `hostname` portion only.
+ *
*
* ```js
* // given url http://example.com/#/some/path?foo=bar&baz=xoxo
* var host = $location.host();
* // => "example.com"
+ *
+ * // given url http://user:password@example.com:8080/#/some/path?foo=bar&baz=xoxo
+ * host = $location.host();
+ * // => "example.com"
+ * host = location.host;
+ * // => "example.com:8080"
* ```
*
* @return {string} host of current url.
@@ -11314,7 +11428,7 @@ function $LocationProvider() {
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
// currently we open nice url link and redirect then
- if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.which == 2) return;
+ if (!html5Mode.rewriteLinks || event.ctrlKey || event.metaKey || event.shiftKey || event.which == 2 || event.button == 2) return;
var elm = jqLite(event.target);
@@ -11356,7 +11470,7 @@ function $LocationProvider() {
// rewrite hashbang url <> html5 url
- if ($location.absUrl() != initialUrl) {
+ if (trimEmptyHash($location.absUrl()) != trimEmptyHash(initialUrl)) {
$browser.url($location.absUrl(), true);
}
@@ -11472,6 +11586,7 @@ function $LocationProvider() {
<button ng-click="$log.warn(message)">warn</button>
<button ng-click="$log.info(message)">info</button>
<button ng-click="$log.error(message)">error</button>
+ <button ng-click="$log.debug(message)">debug</button>
</div>
</file>
</example>
@@ -11602,6 +11717,17 @@ function $LogProvider() {
}];
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $parseMinErr = minErr('$parse');
// Sandboxing Angular Expressions
@@ -12266,7 +12392,7 @@ Parser.prototype = {
}, {
assign: function(scope, value, locals) {
var o = object(scope, locals);
- if (!o) object.assign(scope, o = {});
+ if (!o) object.assign(scope, o = {}, locals);
return getter.assign(o, value);
}
});
@@ -12292,7 +12418,7 @@ Parser.prototype = {
var key = ensureSafeMemberName(indexFn(self, locals), expression);
// prevent overwriting of Function.constructor which would break ensureSafeObject check
var o = ensureSafeObject(obj(self, locals), expression);
- if (!o) obj.assign(self, o = {});
+ if (!o) obj.assign(self, o = {}, locals);
return o[key] = value;
}
});
@@ -12330,6 +12456,11 @@ Parser.prototype = {
? fn.apply(context, args)
: fn(args[0], args[1], args[2], args[3], args[4]);
+ if (args) {
+ // Free-up the memory (arguments of the last function call).
+ args.length = 0;
+ }
+
return ensureSafeObject(v, expressionText);
};
},
@@ -12402,18 +12533,19 @@ Parser.prototype = {
// Parser helper functions
//////////////////////////////////////////////////
-function setter(obj, path, setValue, fullExp) {
+function setter(obj, locals, path, setValue, fullExp) {
ensureSafeObject(obj, fullExp);
+ ensureSafeObject(locals, fullExp);
var element = path.split('.'), key;
for (var i = 0; element.length > 1; i++) {
key = ensureSafeMemberName(element.shift(), fullExp);
- var propertyObj = ensureSafeObject(obj[key], fullExp);
+ var propertyObj = (i === 0 && locals && locals[key]) || obj[key];
if (!propertyObj) {
propertyObj = {};
obj[key] = propertyObj;
}
- obj = propertyObj;
+ obj = ensureSafeObject(propertyObj, fullExp);
}
key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj[key], fullExp);
@@ -12540,8 +12672,8 @@ function getterFn(path, options, fullExp) {
}
fn.sharedGetter = true;
- fn.assign = function(self, value) {
- return setter(self, path, value, path);
+ fn.assign = function(self, value, locals) {
+ return setter(self, locals, path, value, path);
};
getterFnCache[path] = fn;
return fn;
@@ -13200,8 +13332,7 @@ function qFactory(nextTick, exceptionHandler) {
'qcycle',
"Expected promise to be resolved with value other than itself '{0}'",
val));
- }
- else {
+ } else {
this.$$resolve(val);
}
@@ -13432,7 +13563,7 @@ function $$RAFProvider() { //rAF
$window.webkitCancelRequestAnimationFrame;
var rafSupported = !!requestAnimationFrame;
- var raf = rafSupported
+ var rafFn = rafSupported
? function(fn) {
var id = requestAnimationFrame(fn);
return function() {
@@ -13446,9 +13577,47 @@ function $$RAFProvider() { //rAF
};
};
- raf.supported = rafSupported;
+ queueFn.supported = rafSupported;
+
+ var cancelLastRAF;
+ var taskCount = 0;
+ var taskQueue = [];
+ return queueFn;
+
+ function flush() {
+ for (var i = 0; i < taskQueue.length; i++) {
+ var task = taskQueue[i];
+ if (task) {
+ taskQueue[i] = null;
+ task();
+ }
+ }
+ taskCount = taskQueue.length = 0;
+ }
- return raf;
+ function queueFn(asyncFn) {
+ var index = taskQueue.length;
+
+ taskCount++;
+ taskQueue.push(asyncFn);
+
+ if (index === 0) {
+ cancelLastRAF = rafFn(flush);
+ }
+
+ return function cancelQueueFn() {
+ if (index >= 0) {
+ taskQueue[index] = null;
+ index = null;
+
+ if (--taskCount === 0 && cancelLastRAF) {
+ cancelLastRAF();
+ cancelLastRAF = null;
+ taskQueue.length = 0;
+ }
+ }
+ };
+ }
}];
}
@@ -13532,9 +13701,26 @@ function $RootScopeProvider() {
return TTL;
};
+ function createChildScopeClass(parent) {
+ function ChildScope() {
+ this.$$watchers = this.$$nextSibling =
+ this.$$childHead = this.$$childTail = null;
+ this.$$listeners = {};
+ this.$$listenerCount = {};
+ this.$id = nextUid();
+ this.$$ChildScope = null;
+ }
+ ChildScope.prototype = parent;
+ return ChildScope;
+ }
+
this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser',
function($injector, $exceptionHandler, $parse, $browser) {
+ function destroyChildScope($event) {
+ $event.currentScope.$$destroyed = true;
+ }
+
/**
* @ngdoc type
* @name $rootScope.Scope
@@ -13543,12 +13729,9 @@ function $RootScopeProvider() {
* A root scope can be retrieved using the {@link ng.$rootScope $rootScope} key from the
* {@link auto.$injector $injector}. Child scopes are created using the
* {@link ng.$rootScope.Scope#$new $new()} method. (Most scopes are created automatically when
- * compiled HTML template is executed.)
+ * compiled HTML template is executed.) See also the {@link guide/scope Scopes guide} for
+ * an in-depth introduction and usage examples.
*
- * Here is a simple scope snippet to show how you can interact with the scope.
- * ```html
- * <file src="./test/ng/rootScopeSpec.js" tag="docs1" />
- * ```
*
* # Inheritance
* A scope can inherit from a parent scope, as in this example:
@@ -13657,15 +13840,7 @@ function $RootScopeProvider() {
// Only create a child scope class if somebody asks for one,
// but cache it to allow the VM to optimize lookups.
if (!this.$$ChildScope) {
- this.$$ChildScope = function ChildScope() {
- this.$$watchers = this.$$nextSibling =
- this.$$childHead = this.$$childTail = null;
- this.$$listeners = {};
- this.$$listenerCount = {};
- this.$id = nextUid();
- this.$$ChildScope = null;
- };
- this.$$ChildScope.prototype = this;
+ this.$$ChildScope = createChildScopeClass(this);
}
child = new this.$$ChildScope();
}
@@ -13683,13 +13858,9 @@ function $RootScopeProvider() {
// prototypically. In all other cases, this property needs to be set
// when the parent scope is destroyed.
// The listener needs to be added after the parent is set
- if (isolate || parent != this) child.$on('$destroy', destroyChild);
+ if (isolate || parent != this) child.$on('$destroy', destroyChildScope);
return child;
-
- function destroyChild() {
- child.$$destroyed = true;
- }
},
/**
@@ -13721,9 +13892,9 @@ function $RootScopeProvider() {
*
*
* If you want to be notified whenever {@link ng.$rootScope.Scope#$digest $digest} is called,
- * you can register a `watchExpression` function with no `listener`. (Since `watchExpression`
- * can execute multiple times per {@link ng.$rootScope.Scope#$digest $digest} cycle when a
- * change is detected, be prepared for multiple calls to your listener.)
+ * you can register a `watchExpression` function with no `listener`. (Be prepared for
+ * multiple calls to your `watchExpression` because it will execute multiple times in a
+ * single {@link ng.$rootScope.Scope#$digest $digest} cycle if a change is detected.)
*
* After a watcher is registered with the scope, the `listener` fn is called asynchronously
* (via {@link ng.$rootScope.Scope#$evalAsync $evalAsync}) to initialize the
@@ -14497,7 +14668,7 @@ function $RootScopeProvider() {
* @kind function
*
* @description
- * Schedule the invokation of $apply to occur at a later time. The actual time difference
+ * Schedule the invocation of $apply to occur at a later time. The actual time difference
* varies across browsers, but is typically around ~10 milliseconds.
*
* This can be used to queue up multiple expressions which need to be evaluated in the same
@@ -14850,6 +15021,17 @@ function $$SanitizeUriProvider() {
};
}
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+ * Any commits to this file should be reviewed with security in mind. *
+ * Changes to this file can potentially create security vulnerabilities. *
+ * An approval from 2 Core members with history of modifying *
+ * this file is required. *
+ * *
+ * Does the change somehow allow for arbitrary javascript to be executed? *
+ * Or allows for someone to change the prototype of built-in objects? *
+ * Or gives undesired access to variables likes document or window? *
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
var $sceMinErr = minErr('$sce');
var SCE_CONTEXTS = {
@@ -15384,7 +15566,7 @@ function $SceDelegateProvider() {
* characters: '`:`', '`/`', '`.`', '`?`', '`&`' and ';'. It's a useful wildcard for use
* in a whitelist.
* - `**`: matches zero or more occurrences 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.
+ * 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.) Its usage at the very end of the path is ok. (e.g.
* http://foo.example.com/templates/**).
@@ -15392,11 +15574,11 @@ function $SceDelegateProvider() {
* - *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
@@ -15406,7 +15588,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
@@ -15992,12 +16174,14 @@ var $compileMinErr = minErr('$compile');
* @name $templateRequest
*
* @description
- * The `$templateRequest` service 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 then a `$compile` error will be thrown (the exception can be thwarted
- * by setting the 2nd parameter of the function to true).
- *
- * @param {string} tpl The HTTP request template URL
+ * 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} the HTTP Promise for the given.
@@ -16005,10 +16189,18 @@ var $compileMinErr = minErr('$compile');
* @property {number} totalPendingRequests total amount of pending template requests being downloaded.
*/
function $TemplateRequestProvider() {
- this.$get = ['$templateCache', '$http', '$q', function($templateCache, $http, $q) {
+ this.$get = ['$templateCache', '$http', '$q', '$sce', function($templateCache, $http, $q, $sce) {
function handleRequestFn(tpl, ignoreRequestError) {
- var self = handleRequestFn;
- self.totalPendingRequests++;
+ 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;
@@ -16026,13 +16218,14 @@ function $TemplateRequestProvider() {
};
return $http.get(tpl, httpOptions)
+ ['finally'](function() {
+ handleRequestFn.totalPendingRequests--;
+ })
.then(function(response) {
- self.totalPendingRequests--;
return response.data;
}, handleError);
function handleError(resp) {
- self.totalPendingRequests--;
if (!ignoreRequestError) {
throw $compileMinErr('tpload', 'Failed to load template: {0}', tpl);
}
@@ -16269,20 +16462,13 @@ var originUrl = urlResolve(window.location.href);
*
* 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
@@ -16412,6 +16598,13 @@ function $WindowProvider() {
* 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.
*
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
+ *
* ```js
* // Filter registration
* function MyModule($provide, $filterProvider) {
@@ -16493,6 +16686,13 @@ function $FilterProvider($provide) {
* @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.
+ *
+ * <div class="alert alert-warning">
+ * **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`).
+ * </div>
* @returns {Object} Registered filter instance, or if a map of filters was provided then a map
* of the registered filter instances.
*/
@@ -16554,19 +16754,26 @@ 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. 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`
+ * 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".
*
+ * 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)`: 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.
@@ -16579,10 +16786,10 @@ function $FilterProvider($provide) {
*
* - `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.
+ * 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.
@@ -16659,14 +16866,16 @@ function filterFilter() {
return function(array, expression, comparator) {
if (!isArray(array)) return array;
+ var expressionType = (expression !== null) ? typeof expression : 'null';
var predicateFn;
var matchAgainstAnyProp;
- switch (typeof expression) {
+ switch (expressionType) {
case 'function':
predicateFn = expression;
break;
case 'boolean':
+ case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
@@ -16685,12 +16894,21 @@ function filterFilter() {
// 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(actual) || isObject(expected)) {
// Prevent an object to be considered equal to a string like `'[object'`
return false;
@@ -16703,19 +16921,22 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) {
}
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) {
- var actualType = typeof actual;
- var expectedType = typeof expected;
+function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) {
+ var actualType = (actual !== null) ? typeof actual : 'null';
+ var expectedType = (expected !== null) ? typeof expected : 'null';
if ((expectedType === 'string') && (expected.charAt(0) === '!')) {
return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp);
- } else if (actualType === 'array') {
+ } 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) {
@@ -16728,21 +16949,21 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp) {
var key;
if (matchAgainstAnyProp) {
for (key in actual) {
- if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator)) {
+ if ((key.charAt(0) !== '$') && deepCompare(actual[key], expected, comparator, true)) {
return true;
}
}
- return false;
+ return dontMatchWholeObject ? false : deepCompare(actual, expected, comparator, false);
} else if (expectedType === 'object') {
for (key in expected) {
var expectedVal = expected[key];
- if (isFunction(expectedVal)) {
+ if (isFunction(expectedVal) || isUndefined(expectedVal)) {
continue;
}
- var keyIsDollar = key === '$';
- var actualVal = keyIsDollar ? actual : actual[key];
- if (!deepCompare(actualVal, expectedVal, comparator, keyIsDollar)) {
+ var matchAnyProperty = key === '$';
+ var actualVal = matchAnyProperty ? actual : actual[key];
+ if (!deepCompare(actualVal, expectedVal, comparator, matchAnyProperty, matchAnyProperty)) {
return false;
}
}
@@ -16838,6 +17059,8 @@ function currencyFilter($locale) {
* @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.
@@ -17050,6 +17273,14 @@ 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),
@@ -17076,10 +17307,14 @@ var DATE_FORMATS = {
a: ampmGetter,
Z: timeZoneGetter,
ww: weekGetter(2),
- w: weekGetter(1)
+ w: weekGetter(1),
+ G: eraGetter,
+ GG: eraGetter,
+ GGG: eraGetter,
+ GGGG: longEraGetter
};
-var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/,
+var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,
NUMBER_STRING = /^\-?\d+$/;
/**
@@ -17111,11 +17346,13 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d
* * `'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)
+ * * `'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}:
@@ -17406,37 +17643,12 @@ function limitToFilter() {
limit = int(limit);
}
- if (isString(input)) {
- //NaN check on limit
- if (limit) {
- return limit >= 0 ? input.slice(0, limit) : input.slice(limit, input.length);
- } else {
- return "";
- }
- }
-
- 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;
+ //NaN check on limit
+ if (limit) {
+ return limit > 0 ? input.slice(0, limit) : input.slice(limit);
} else {
- i = input.length + limit;
- n = input.length;
+ return isString(input) ? "" : [];
}
-
- for (; i < n; i++) {
- out.push(input[i]);
- }
-
- return out;
};
}
@@ -17457,7 +17669,7 @@ function limitToFilter() {
* Can be one of:
*
* - `function`: Getter function. The result of this function will be sorted using the
- * `<`, `=`, `>` operator.
+ * `<`, `===`, `>` 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
@@ -17474,6 +17686,43 @@ function limitToFilter() {
* @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`.
+ <example module="orderByExample">
+ <file name="index.html">
+ <script>
+ angular.module('orderByExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $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}];
+ }]);
+ </script>
+ <div ng-controller="ExampleController">
+ <table class="friend">
+ <tr>
+ <th>Name</th>
+ <th>Phone Number</th>
+ <th>Age</th>
+ </tr>
+ <tr ng-repeat="friend in friends | orderBy:'-age'">
+ <td>{{friend.name}}</td>
+ <td>{{friend.phone}}</td>
+ <td>{{friend.age}}</td>
+ </tr>
+ </table>
+ </div>
+ </file>
+ </example>
+ *
+ * The predicate and reverse parameters can be controlled dynamically through scope properties,
+ * as shown in the next example.
* @example
<example module="orderByExample">
<file name="index.html">
@@ -17570,9 +17819,7 @@ function orderByFilter($parse) {
}
if (predicate === '') {
// Effectively no predicate was passed so we compare identity
- return reverseComparator(function(a, b) {
- return compare(a, b);
- }, descending);
+ return reverseComparator(compare, descending);
}
get = $parse(predicate);
if (get.constant) {
@@ -17614,14 +17861,14 @@ function orderByFilter($parse) {
function objectToString(value) {
if (value === null) return 'null';
- if (typeof value.toString === 'function') {
- value = value.toString();
- if (isPrimitive(value)) return value;
- }
if (typeof value.valueOf === 'function') {
value = value.valueOf();
if (isPrimitive(value)) return value;
}
+ if (typeof value.toString === 'function') {
+ value = value.toString();
+ if (isPrimitive(value)) return value;
+ }
return '';
}
@@ -17674,6 +17921,9 @@ var htmlAnchorDirective = valueFn({
compile: function(element, attr) {
if (!attr.href && !attr.xlinkHref && !attr.name) {
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';
@@ -17847,20 +18097,24 @@ var htmlAnchorDirective = valueFn({
*
* @description
*
- * We shouldn't do this, because it 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.
+ *
+ * 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
- * <div ng-init="scope = { isDisabled: false }">
- * <button disabled="{{scope.isDisabled}}">Disabled</button>
+ * <!-- See below for an example of ng-disabled being used correctly -->
+ * <div ng-init="isDisabled = false">
+ * <button disabled="{{isDisabled}}">Disabled</button>
* </div>
* ```
*
- * 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.)
+ * 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
<example>
@@ -17879,7 +18133,7 @@ var htmlAnchorDirective = valueFn({
*
* @element INPUT
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
- * then special attribute "disabled" will be set on the element
+ * then the `disabled` attribute will be set on the element
*/
@@ -18274,6 +18528,9 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
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);
};
@@ -18291,23 +18548,23 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
addSetValidityMethod({
ctrl: this,
$element: element,
- set: function(object, property, control) {
+ set: function(object, property, controller) {
var list = object[property];
if (!list) {
- object[property] = [control];
+ object[property] = [controller];
} else {
- var index = list.indexOf(control);
+ var index = list.indexOf(controller);
if (index === -1) {
- list.push(control);
+ list.push(controller);
}
}
},
- unset: function(object, property, control) {
+ unset: function(object, property, controller) {
var list = object[property];
if (!list) {
return;
}
- arrayRemove(list, control);
+ arrayRemove(list, controller);
if (list.length === 0) {
delete object[property];
}
@@ -18424,7 +18681,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
*
* # Alias: {@link ng.directive:ngForm `ngForm`}
*
- * In Angular forms can be nested. This means that the outer form is valid when all of the child
+ * In Angular, forms can be nested. This means that the outer form is valid when all of the child
* forms are valid as well. However, browsers do not allow nesting of `<form>` elements, so
* Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to
* `<form>` but can be nested. This allows you to have nested forms, which is very useful when
@@ -18523,11 +18780,11 @@ function FormController(element, attrs, $scope, $animate, $interpolate) {
<form name="myForm" ng-controller="FormController" class="my-form">
userType: <input name="input" ng-model="userType" required>
<span class="error" ng-show="myForm.input.$error.required">Required!</span><br>
- <tt>userType = {{userType}}</tt><br>
- <tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br>
- <tt>myForm.input.$error = {{myForm.input.$error}}</tt><br>
- <tt>myForm.$valid = {{myForm.$valid}}</tt><br>
- <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br>
+ <code>userType = {{userType}}</code><br>
+ <code>myForm.input.$valid = {{myForm.input.$valid}}</code><br>
+ <code>myForm.input.$error = {{myForm.input.$error}}</code><br>
+ <code>myForm.$valid = {{myForm.$valid}}</code><br>
+ <code>myForm.$error.required = {{!!myForm.$error.required}}</code><br>
</form>
</file>
<file name="protractor.js" type="protractor">
@@ -18562,10 +18819,12 @@ var formDirectiveFactory = function(isNgForm) {
name: 'form',
restrict: isNgForm ? 'EAC' : 'E',
controller: FormController,
- compile: function ngFormCompile(formElement) {
+ compile: function ngFormCompile(formElement, attr) {
// Setup initial state of the control
formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS);
+ var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false);
+
return {
pre: function ngFormPreLink(scope, formElement, attr, controller) {
// if `action` attr is not present on the form, prevent the default action (submission)
@@ -18596,23 +18855,21 @@ var formDirectiveFactory = function(isNgForm) {
});
}
- var parentFormCtrl = controller.$$parentForm,
- alias = controller.$name;
-
- if (alias) {
- setter(scope, alias, controller, alias);
- attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) {
- if (alias === newValue) return;
- setter(scope, alias, undefined, alias);
- alias = newValue;
- setter(scope, alias, controller, alias);
- parentFormCtrl.$$renameControl(controller, alias);
+ var parentFormCtrl = controller.$$parentForm;
+
+ if (nameAttr) {
+ setter(scope, null, controller.$name, controller, controller.$name);
+ attr.$observe(nameAttr, function(newValue) {
+ if (controller.$name === newValue) return;
+ setter(scope, null, controller.$name, undefined, controller.$name);
+ parentFormCtrl.$$renameControl(controller, newValue);
+ setter(scope, null, controller.$name, controller, controller.$name);
});
}
formElement.on('$destroy', function() {
parentFormCtrl.$removeControl(controller);
- if (alias) {
- setter(scope, alias, undefined, alias);
+ if (nameAttr) {
+ setter(scope, null, attr[nameAttr], undefined, controller.$name);
}
extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards
});
@@ -18628,12 +18885,13 @@ var formDirectiveFactory = function(isNgForm) {
var formDirective = formDirectiveFactory();
var ngFormDirective = formDirectiveFactory(true);
-/* global VALID_CLASS: true,
- INVALID_CLASS: true,
- PRISTINE_CLASS: true,
- DIRTY_CLASS: true,
- UNTOUCHED_CLASS: true,
- TOUCHED_CLASS: true,
+/* global VALID_CLASS: false,
+ INVALID_CLASS: false,
+ PRISTINE_CLASS: false,
+ DIRTY_CLASS: false,
+ UNTOUCHED_CLASS: false,
+ TOUCHED_CLASS: false,
+ ngModelMinErr: false,
*/
// Regex code is obtained from SO: https://stackoverflow.com/questions/3143070/javascript-regex-iso-datetime#answer-3143231
@@ -18646,9 +18904,6 @@ var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{
var WEEK_REGEXP = /^(\d{4})-W(\d\d)$/;
var MONTH_REGEXP = /^(\d{4})-(\d\d)$/;
var TIME_REGEXP = /^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/;
-var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
-
-var $ngModelMinErr = new minErr('ngModel');
var inputType = {
@@ -18691,19 +18946,21 @@ var inputType = {
<script>
angular.module('textInputExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'guest';
- $scope.word = /^\s*\w*\s*$/;
+ $scope.example = {
+ text: 'guest',
+ word: /^\s*\w*\s*$/
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Single word: <input type="text" name="input" ng-model="text"
- ng-pattern="word" required ng-trim="false">
+ Single word: <input type="text" name="input" ng-model="example.text"
+ ng-pattern="example.word" required ng-trim="false">
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.pattern">
Single word only!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{example.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -18711,9 +18968,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('example.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('example.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('guest');
@@ -18775,18 +19032,20 @@ var inputType = {
<script>
angular.module('dateInputExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 9, 22);
+ $scope.example = {
+ value: new Date(2013, 9, 22)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date in 2013:
- <input type="date" id="exampleInput" name="input" ng-model="value"
+ <input type="date" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-dd" min="2013-01-01" max="2013-12-31" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.date">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-MM-dd"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM-dd"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -18794,9 +19053,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM-dd"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM-dd"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -18866,18 +19125,20 @@ var inputType = {
<script>
angular.module('dateExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2010, 11, 28, 14, 57);
+ $scope.example = {
+ value: new Date(2010, 11, 28, 14, 57)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
- <input type="datetime-local" id="exampleInput" name="input" ng-model="value"
+ <input type="datetime-local" id="exampleInput" name="input" ng-model="example.value"
placeholder="yyyy-MM-ddTHH:mm:ss" min="2001-01-01T00:00:00" max="2013-12-31T00:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.datetimelocal">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM-ddTHH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -18885,9 +19146,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM-ddTHH:mm:ss"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM-ddTHH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -18958,18 +19219,20 @@ var inputType = {
<script>
angular.module('timeExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(1970, 0, 1, 14, 57, 0);
+ $scope.example = {
+ value: new Date(1970, 0, 1, 14, 57, 0)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a between 8am and 5pm:
- <input type="time" id="exampleInput" name="input" ng-model="value"
+ <input type="time" id="exampleInput" name="input" ng-model="example.value"
placeholder="HH:mm:ss" min="08:00:00" max="17:00:00" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.time">
Not a valid date!</span>
- <tt>value = {{value | date: "HH:mm:ss"}}</tt><br/>
+ <tt>value = {{example.value | date: "HH:mm:ss"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -18977,9 +19240,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "HH:mm:ss"'));
+ var value = element(by.binding('example.value | date: "HH:mm:ss"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -19049,18 +19312,20 @@ var inputType = {
<script>
angular.module('weekExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 0, 3);
+ $scope.example = {
+ value: new Date(2013, 0, 3)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
Pick a date between in 2013:
- <input id="exampleInput" type="week" name="input" ng-model="value"
+ <input id="exampleInput" type="week" name="input" ng-model="example.value"
placeholder="YYYY-W##" min="2012-W32" max="2013-W52" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.week">
Not a valid date!</span>
- <tt>value = {{value | date: "yyyy-Www"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-Www"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -19068,9 +19333,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-Www"'));
+ var value = element(by.binding('example.value | date: "yyyy-Www"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -19140,18 +19405,20 @@ var inputType = {
<script>
angular.module('monthExample', [])
.controller('DateController', ['$scope', function($scope) {
- $scope.value = new Date(2013, 9, 1);
+ $scope.example = {
+ value: new Date(2013, 9, 1)
+ };
}]);
</script>
<form name="myForm" ng-controller="DateController as dateCtrl">
- Pick a month int 2013:
- <input id="exampleInput" type="month" name="input" ng-model="value"
+ Pick a month in 2013:
+ <input id="exampleInput" type="month" name="input" ng-model="example.value"
placeholder="yyyy-MM" min="2013-01" max="2013-12" required />
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.month">
Not a valid month!</span>
- <tt>value = {{value | date: "yyyy-MM"}}</tt><br/>
+ <tt>value = {{example.value | date: "yyyy-MM"}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -19159,9 +19426,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value | date: "yyyy-MM"'));
+ var value = element(by.binding('example.value | date: "yyyy-MM"'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
// currently protractor/webdriver does not support
// sending keys to all known HTML5 input controls
@@ -19205,7 +19472,11 @@ var inputType = {
* Text input with number validation and transformation. Sets the `number` validation
* error if not a valid number.
*
- * The model must always be a number, otherwise Angular will throw an error.
+ * <div class="alert alert-warning">
+ * The model must always be of type `number` otherwise Angular will throw an error.
+ * Be aware that a string containing a number is not enough. See the {@link ngModel:numfmt}
+ * error docs for more information and an example of how to convert your model if necessary.
+ * </div>
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -19237,17 +19508,19 @@ var inputType = {
<script>
angular.module('numberExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.value = 12;
+ $scope.example = {
+ value: 12
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Number: <input type="number" name="input" ng-model="value"
+ Number: <input type="number" name="input" ng-model="example.value"
min="0" max="99" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.number">
Not valid number!</span>
- <tt>value = {{value}}</tt><br/>
+ <tt>value = {{example.value}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -19255,9 +19528,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var value = element(by.binding('value'));
+ var value = element(by.binding('example.value'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('value'));
+ var input = element(by.model('example.value'));
it('should initialize to model', function() {
expect(value.getText()).toContain('12');
@@ -19325,16 +19598,18 @@ var inputType = {
<script>
angular.module('urlExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'http://google.com';
+ $scope.url = {
+ text: 'http://google.com'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- URL: <input type="url" name="input" ng-model="text" required>
+ URL: <input type="url" name="input" ng-model="url.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.url">
Not valid url!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{url.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -19343,9 +19618,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('url.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('url.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('http://google.com');
@@ -19414,16 +19689,18 @@ var inputType = {
<script>
angular.module('emailExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.text = 'me@example.com';
+ $scope.email = {
+ text: 'me@example.com'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Email: <input type="email" name="input" ng-model="text" required>
+ Email: <input type="email" name="input" ng-model="email.text" required>
<span class="error" ng-show="myForm.input.$error.required">
Required!</span>
<span class="error" ng-show="myForm.input.$error.email">
Not valid email!</span>
- <tt>text = {{text}}</tt><br/>
+ <tt>text = {{email.text}}</tt><br/>
<tt>myForm.input.$valid = {{myForm.input.$valid}}</tt><br/>
<tt>myForm.input.$error = {{myForm.input.$error}}</tt><br/>
<tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
@@ -19432,9 +19709,9 @@ var inputType = {
</form>
</file>
<file name="protractor.js" type="protractor">
- var text = element(by.binding('text'));
+ var text = element(by.binding('email.text'));
var valid = element(by.binding('myForm.input.$valid'));
- var input = element(by.model('text'));
+ var input = element(by.model('email.text'));
it('should initialize to model', function() {
expect(text.getText()).toContain('me@example.com');
@@ -19481,7 +19758,9 @@ var inputType = {
<script>
angular.module('radioExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.color = 'blue';
+ $scope.color = {
+ name: 'blue'
+ };
$scope.specialValue = {
"id": "12345",
"value": "green"
@@ -19489,20 +19768,20 @@ var inputType = {
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- <input type="radio" ng-model="color" value="red"> Red <br/>
- <input type="radio" ng-model="color" ng-value="specialValue"> Green <br/>
- <input type="radio" ng-model="color" value="blue"> Blue <br/>
- <tt>color = {{color | json}}</tt><br/>
+ <input type="radio" ng-model="color.name" value="red"> Red <br/>
+ <input type="radio" ng-model="color.name" ng-value="specialValue"> Green <br/>
+ <input type="radio" ng-model="color.name" value="blue"> Blue <br/>
+ <tt>color = {{color.name | json}}</tt><br/>
</form>
Note that `ng-value="specialValue"` sets radio item's value to be the value of `$scope.specialValue`.
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
- var color = element(by.binding('color'));
+ var color = element(by.binding('color.name'));
expect(color.getText()).toContain('blue');
- element.all(by.model('color')).get(0).click();
+ element.all(by.model('color.name')).get(0).click();
expect(color.getText()).toContain('red');
});
@@ -19532,28 +19811,30 @@ var inputType = {
<script>
angular.module('checkboxExample', [])
.controller('ExampleController', ['$scope', function($scope) {
- $scope.value1 = true;
- $scope.value2 = 'YES'
+ $scope.checkboxModel = {
+ value1 : true,
+ value2 : 'YES'
+ };
}]);
</script>
<form name="myForm" ng-controller="ExampleController">
- Value1: <input type="checkbox" ng-model="value1"> <br/>
- Value2: <input type="checkbox" ng-model="value2"
+ Value1: <input type="checkbox" ng-model="checkboxModel.value1"> <br/>
+ Value2: <input type="checkbox" ng-model="checkboxModel.value2"
ng-true-value="'YES'" ng-false-value="'NO'"> <br/>
- <tt>value1 = {{value1}}</tt><br/>
- <tt>value2 = {{value2}}</tt><br/>
+ <tt>value1 = {{checkboxModel.value1}}</tt><br/>
+ <tt>value2 = {{checkboxModel.value2}}</tt><br/>
</form>
</file>
<file name="protractor.js" type="protractor">
it('should change state', function() {
- var value1 = element(by.binding('value1'));
- var value2 = element(by.binding('value2'));
+ var value1 = element(by.binding('checkboxModel.value1'));
+ var value2 = element(by.binding('checkboxModel.value2'));
expect(value1.getText()).toContain('true');
expect(value2.getText()).toContain('YES');
- element(by.model('value1')).click();
- element(by.model('value2')).click();
+ element(by.model('checkboxModel.value1')).click();
+ element(by.model('checkboxModel.value2')).click();
expect(value1.getText()).toContain('false');
expect(value2.getText()).toContain('NO');
@@ -19774,7 +20055,7 @@ function createDateInputType(type, regexp, parseDate, format) {
ctrl.$formatters.push(function(value) {
if (value && !isDate(value)) {
- throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
+ throw ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
}
if (isValidDate(value)) {
previousDate = value;
@@ -19851,14 +20132,14 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
ctrl.$formatters.push(function(value) {
if (!ctrl.$isEmpty(value)) {
if (!isNumber(value)) {
- throw $ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
+ throw ngModelMinErr('numfmt', 'Expected `{0}` to be a number', value);
}
value = value.toString();
}
return value;
});
- if (attr.min || attr.ngMin) {
+ if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal;
@@ -19874,7 +20155,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
- if (attr.max || attr.ngMax) {
+ if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal;
@@ -19944,7 +20225,7 @@ function parseConstantExpr($parse, context, name, expression, fallback) {
if (isDefined(expression)) {
parseFn = $parse(expression);
if (!parseFn.constant) {
- throw minErr('ngModel')('constexpr', 'Expected constant expression for `{0}`, but saw ' +
+ throw ngModelMinErr('constexpr', 'Expected constant expression for `{0}`, but saw ' +
'`{1}`.', name, expression);
}
return parseFn(context);
@@ -20150,1344 +20431,6 @@ var inputDirective = ['$browser', '$sniffer', '$filter', '$parse',
};
}];
-var VALID_CLASS = 'ng-valid',
- INVALID_CLASS = 'ng-invalid',
- PRISTINE_CLASS = 'ng-pristine',
- DIRTY_CLASS = 'ng-dirty',
- UNTOUCHED_CLASS = 'ng-untouched',
- TOUCHED_CLASS = 'ng-touched',
- PENDING_CLASS = 'ng-pending';
-
-/**
- * @ngdoc type
- * @name ngModel.NgModelController
- *
- * @property {string} $viewValue Actual string value in the view.
- * @property {*} $modelValue The value in the model that the control is bound to.
- * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
- the control reads value from the DOM. The functions are called in array order, each passing
- its return value through to the next. The last return value is forwarded to the
- {@link ngModel.NgModelController#$validators `$validators`} collection.
-
-Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
-`$viewValue`}.
-
-Returning `undefined` from a parser means a parse error occurred. In that case,
-no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
-will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
-is set to `true`. The parse error is stored in `ngModel.$error.parse`.
-
- *
- * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
- the model value changes. The functions are called in reverse array order, each passing the value through to the
- next. The last return value is used as the actual DOM value.
- Used to format / convert values for display in the control.
- * ```js
- * function formatter(value) {
- * if (value) {
- * return value.toUpperCase();
- * }
- * }
- * ngModel.$formatters.push(formatter);
- * ```
- *
- * @property {Object.<string, function>} $validators A collection of validators that are applied
- * whenever the model value changes. The key value within the object refers to the name of the
- * validator while the function refers to the validation operation. The validation operation is
- * provided with the model value as an argument and must return a true or false value depending
- * on the response of that validation.
- *
- * ```js
- * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
- * var value = modelValue || viewValue;
- * return /[0-9]+/.test(value) &&
- * /[a-z]+/.test(value) &&
- * /[A-Z]+/.test(value) &&
- * /\W+/.test(value);
- * };
- * ```
- *
- * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
- * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
- * is expected to return a promise when it is run during the model validation process. Once the promise
- * is delivered then the validation status will be set to true when fulfilled and false when rejected.
- * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
- * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
- * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
- * will only run once all synchronous validators have passed.
- *
- * Please note that if $http is used then it is important that the server returns a success HTTP response code
- * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
- *
- * ```js
- * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
- * var value = modelValue || viewValue;
- *
- * // Lookup user by username
- * return $http.get('/api/users/' + value).
- * then(function resolved() {
- * //username exists, this means validation fails
- * return $q.reject('exists');
- * }, function rejected() {
- * //username does not exist, therefore this validation passes
- * return true;
- * });
- * };
- * ```
- *
- * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
- * view value has changed. It is called with no arguments, and its return value is ignored.
- * This can be used in place of additional $watches against the model value.
- *
- * @property {Object} $error An object hash with all failing validator ids as keys.
- * @property {Object} $pending An object hash with all pending validator ids as keys.
- *
- * @property {boolean} $untouched True if control has not lost focus yet.
- * @property {boolean} $touched True if control has lost focus.
- * @property {boolean} $pristine True if user has not interacted with the control yet.
- * @property {boolean} $dirty True if user has already interacted with the control.
- * @property {boolean} $valid True if there is no error.
- * @property {boolean} $invalid True if at least one error on the control.
- * @property {string} $name The name attribute of the control.
- *
- * @description
- *
- * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
- * The controller contains services for data-binding, validation, CSS updates, and value formatting
- * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
- * listening to DOM events.
- * Such DOM related logic should be provided by other directives which make use of
- * `NgModelController` for data-binding to control elements.
- * Angular provides this DOM logic for most {@link input `input`} elements.
- * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
- * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
- *
- * @example
- * ### Custom Control Example
- * This example shows how to use `NgModelController` with a custom control to achieve
- * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
- * collaborate together to achieve the desired result.
- *
- * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element
- * contents be edited in place by the user. This will not work on older browsers.
- *
- * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
- * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
- * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
- * that content using the `$sce` service.
- *
- * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
- <file name="style.css">
- [contenteditable] {
- border: 1px solid black;
- background-color: white;
- min-height: 20px;
- }
-
- .ng-invalid {
- border: 1px solid red;
- }
-
- </file>
- <file name="script.js">
- angular.module('customControl', ['ngSanitize']).
- directive('contenteditable', ['$sce', function($sce) {
- return {
- restrict: 'A', // only activate on element attribute
- require: '?ngModel', // get a hold of NgModelController
- link: function(scope, element, attrs, ngModel) {
- if (!ngModel) return; // do nothing if no ng-model
-
- // Specify how UI should be updated
- ngModel.$render = function() {
- element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
- };
-
- // Listen for change events to enable binding
- element.on('blur keyup change', function() {
- scope.$evalAsync(read);
- });
- read(); // initialize
-
- // Write data to the model
- function read() {
- var html = element.html();
- // When we clear the content editable the browser leaves a <br> behind
- // If strip-br attribute is provided then we strip this out
- if ( attrs.stripBr && html == '<br>' ) {
- html = '';
- }
- ngModel.$setViewValue(html);
- }
- }
- };
- }]);
- </file>
- <file name="index.html">
- <form name="myForm">
- <div contenteditable
- name="myWidget" ng-model="userContent"
- strip-br="true"
- required>Change me!</div>
- <span ng-show="myForm.myWidget.$error.required">Required!</span>
- <hr>
- <textarea ng-model="userContent"></textarea>
- </form>
- </file>
- <file name="protractor.js" type="protractor">
- it('should data-bind and become invalid', function() {
- if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
- // SafariDriver can't handle contenteditable
- // and Firefox driver can't clear contenteditables very well
- return;
- }
- var contentEditable = element(by.css('[contenteditable]'));
- var content = 'Change me!';
-
- expect(contentEditable.getText()).toEqual(content);
-
- contentEditable.clear();
- contentEditable.sendKeys(protractor.Key.BACK_SPACE);
- expect(contentEditable.getText()).toEqual('');
- expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
- });
- </file>
- * </example>
- *
- *
- */
-var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
- function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
- this.$viewValue = Number.NaN;
- this.$modelValue = Number.NaN;
- this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
- this.$validators = {};
- this.$asyncValidators = {};
- this.$parsers = [];
- this.$formatters = [];
- this.$viewChangeListeners = [];
- this.$untouched = true;
- this.$touched = false;
- this.$pristine = true;
- this.$dirty = false;
- this.$valid = true;
- this.$invalid = false;
- this.$error = {}; // keep invalid keys here
- this.$$success = {}; // keep valid keys here
- this.$pending = undefined; // keep pending keys here
- this.$name = $interpolate($attr.name || '', false)($scope);
-
-
- var parsedNgModel = $parse($attr.ngModel),
- parsedNgModelAssign = parsedNgModel.assign,
- ngModelGet = parsedNgModel,
- ngModelSet = parsedNgModelAssign,
- pendingDebounce = null,
- ctrl = this;
-
- this.$$setOptions = function(options) {
- ctrl.$options = options;
- if (options && options.getterSetter) {
- var invokeModelGetter = $parse($attr.ngModel + '()'),
- invokeModelSetter = $parse($attr.ngModel + '($$$p)');
-
- ngModelGet = function($scope) {
- var modelValue = parsedNgModel($scope);
- if (isFunction(modelValue)) {
- modelValue = invokeModelGetter($scope);
- }
- return modelValue;
- };
- ngModelSet = function($scope, newValue) {
- if (isFunction(parsedNgModel($scope))) {
- invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
- } else {
- parsedNgModelAssign($scope, ctrl.$modelValue);
- }
- };
- } else if (!parsedNgModel.assign) {
- throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
- $attr.ngModel, startingTag($element));
- }
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$render
- *
- * @description
- * Called when the view needs to be updated. It is expected that the user of the ng-model
- * directive will implement this method.
- *
- * The `$render()` method is invoked in the following situations:
- *
- * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
- * committed value then `$render()` is called to update the input control.
- * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
- * the `$viewValue` are different to last time.
- *
- * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
- * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
- * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
- * invoked if you only change a property on the objects.
- */
- this.$render = noop;
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$isEmpty
- *
- * @description
- * This is called when we need to determine if the value of an input is empty.
- *
- * For instance, the required directive does this to work out if the input has data or not.
- *
- * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
- *
- * You can override this for input directives whose concept of being empty is different to the
- * default. The `checkboxInputType` directive does this because in its case a value of `false`
- * implies empty.
- *
- * @param {*} value The value of the input to check for emptiness.
- * @returns {boolean} True if `value` is "empty".
- */
- this.$isEmpty = function(value) {
- return isUndefined(value) || value === '' || value === null || value !== value;
- };
-
- var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
- currentValidationRunId = 0;
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setValidity
- *
- * @description
- * Change the validity state, and notify the form.
- *
- * This method can be called within $parsers/$formatters or a custom validation implementation.
- * However, in most cases it should be sufficient to use the `ngModel.$validators` and
- * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
- *
- * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
- * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
- * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
- * The `validationErrorKey` should be in camelCase and will get converted into dash-case
- * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
- * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
- * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
- * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
- * Skipped is used by Angular when validators do not run because of parse errors and
- * when `$asyncValidators` do not run because any of the `$validators` failed.
- */
- addSetValidityMethod({
- ctrl: this,
- $element: $element,
- set: function(object, property) {
- object[property] = true;
- },
- unset: function(object, property) {
- delete object[property];
- },
- parentForm: parentForm,
- $animate: $animate
- });
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setPristine
- *
- * @description
- * Sets the control to its pristine state.
- *
- * This method can be called to remove the `ng-dirty` class and set the control to its pristine
- * state (`ng-pristine` class). A model is considered to be pristine when the control
- * has not been changed from when first compiled.
- */
- this.$setPristine = function() {
- ctrl.$dirty = false;
- ctrl.$pristine = true;
- $animate.removeClass($element, DIRTY_CLASS);
- $animate.addClass($element, PRISTINE_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setDirty
- *
- * @description
- * Sets the control to its dirty state.
- *
- * This method can be called to remove the `ng-pristine` class and set the control to its dirty
- * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
- * from when first compiled.
- */
- this.$setDirty = function() {
- ctrl.$dirty = true;
- ctrl.$pristine = false;
- $animate.removeClass($element, PRISTINE_CLASS);
- $animate.addClass($element, DIRTY_CLASS);
- parentForm.$setDirty();
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setUntouched
- *
- * @description
- * Sets the control to its untouched state.
- *
- * This method can be called to remove the `ng-touched` class and set the control to its
- * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
- * by default, however this function can be used to restore that state if the model has
- * already been touched by the user.
- */
- this.$setUntouched = function() {
- ctrl.$touched = false;
- ctrl.$untouched = true;
- $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setTouched
- *
- * @description
- * Sets the control to its touched state.
- *
- * This method can be called to remove the `ng-untouched` class and set the control to its
- * touched state (`ng-touched` class). A model is considered to be touched when the user has
- * first focused the control element and then shifted focus away from the control (blur event).
- */
- this.$setTouched = function() {
- ctrl.$touched = true;
- ctrl.$untouched = false;
- $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$rollbackViewValue
- *
- * @description
- * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
- * which may be caused by a pending debounced event or because the input is waiting for a some
- * future event.
- *
- * If you have an input that uses `ng-model-options` to set up debounced events or events such
- * as blur you can have a situation where there is a period when the `$viewValue`
- * is out of synch with the ngModel's `$modelValue`.
- *
- * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
- * programmatically before these debounced/future events have resolved/occurred, because Angular's
- * dirty checking mechanism is not able to tell whether the model has actually changed or not.
- *
- * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
- * input which may have such events pending. This is important in order to make sure that the
- * input field will be updated with the new model value and any pending operations are cancelled.
- *
- * <example name="ng-model-cancel-update" module="cancel-update-example">
- * <file name="app.js">
- * angular.module('cancel-update-example', [])
- *
- * .controller('CancelUpdateController', ['$scope', function($scope) {
- * $scope.resetWithCancel = function(e) {
- * if (e.keyCode == 27) {
- * $scope.myForm.myInput1.$rollbackViewValue();
- * $scope.myValue = '';
- * }
- * };
- * $scope.resetWithoutCancel = function(e) {
- * if (e.keyCode == 27) {
- * $scope.myValue = '';
- * }
- * };
- * }]);
- * </file>
- * <file name="index.html">
- * <div ng-controller="CancelUpdateController">
- * <p>Try typing something in each input. See that the model only updates when you
- * blur off the input.
- * </p>
- * <p>Now see what happens if you start typing then press the Escape key</p>
- *
- * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
- * <p>With $rollbackViewValue()</p>
- * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/>
- * myValue: "{{ myValue }}"
- *
- * <p>Without $rollbackViewValue()</p>
- * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/>
- * myValue: "{{ myValue }}"
- * </form>
- * </div>
- * </file>
- * </example>
- */
- this.$rollbackViewValue = function() {
- $timeout.cancel(pendingDebounce);
- ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
- ctrl.$render();
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$validate
- *
- * @description
- * Runs each of the registered validators (first synchronous validators and then
- * asynchronous validators).
- * If the validity changes to invalid, the model will be set to `undefined`,
- * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
- * If the validity changes to valid, it will set the model to the last available valid
- * modelValue, i.e. either the last parsed value or the last value set from the scope.
- */
- this.$validate = function() {
- // ignore $validate before model is initialized
- if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
- return;
- }
-
- var viewValue = ctrl.$$lastCommittedViewValue;
- // Note: we use the $$rawModelValue as $modelValue might have been
- // set to undefined during a view -> model update that found validation
- // errors. We can't parse the view here, since that could change
- // the model although neither viewValue nor the model on the scope changed
- var modelValue = ctrl.$$rawModelValue;
-
- // Check if the there's a parse error, so we don't unset it accidentially
- var parserName = ctrl.$$parserName || 'parse';
- var parserValid = ctrl.$error[parserName] ? false : undefined;
-
- var prevValid = ctrl.$valid;
- var prevModelValue = ctrl.$modelValue;
-
- var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
-
- ctrl.$$runValidators(parserValid, modelValue, viewValue, function(allValid) {
- // If there was no change in validity, don't update the model
- // This prevents changing an invalid modelValue to undefined
- if (!allowInvalid && prevValid !== allValid) {
- // Note: Don't check ctrl.$valid here, as we could have
- // external validators (e.g. calculated on the server),
- // that just call $setValidity and need the model value
- // to calculate their validity.
- ctrl.$modelValue = allValid ? modelValue : undefined;
-
- if (ctrl.$modelValue !== prevModelValue) {
- ctrl.$$writeModelToScope();
- }
- }
- });
-
- };
-
- this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
- currentValidationRunId++;
- var localValidationRunId = currentValidationRunId;
-
- // check parser error
- if (!processParseErrors(parseValid)) {
- validationDone(false);
- return;
- }
- if (!processSyncValidators()) {
- validationDone(false);
- return;
- }
- processAsyncValidators();
-
- function processParseErrors(parseValid) {
- var errorKey = ctrl.$$parserName || 'parse';
- if (parseValid === undefined) {
- setValidity(errorKey, null);
- } else {
- setValidity(errorKey, parseValid);
- if (!parseValid) {
- forEach(ctrl.$validators, function(v, name) {
- setValidity(name, null);
- });
- forEach(ctrl.$asyncValidators, function(v, name) {
- setValidity(name, null);
- });
- return false;
- }
- }
- return true;
- }
-
- function processSyncValidators() {
- var syncValidatorsValid = true;
- forEach(ctrl.$validators, function(validator, name) {
- var result = validator(modelValue, viewValue);
- syncValidatorsValid = syncValidatorsValid && result;
- setValidity(name, result);
- });
- if (!syncValidatorsValid) {
- forEach(ctrl.$asyncValidators, function(v, name) {
- setValidity(name, null);
- });
- return false;
- }
- return true;
- }
-
- function processAsyncValidators() {
- var validatorPromises = [];
- var allValid = true;
- forEach(ctrl.$asyncValidators, function(validator, name) {
- var promise = validator(modelValue, viewValue);
- if (!isPromiseLike(promise)) {
- throw $ngModelMinErr("$asyncValidators",
- "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
- }
- setValidity(name, undefined);
- validatorPromises.push(promise.then(function() {
- setValidity(name, true);
- }, function(error) {
- allValid = false;
- setValidity(name, false);
- }));
- });
- if (!validatorPromises.length) {
- validationDone(true);
- } else {
- $q.all(validatorPromises).then(function() {
- validationDone(allValid);
- }, noop);
- }
- }
-
- function setValidity(name, isValid) {
- if (localValidationRunId === currentValidationRunId) {
- ctrl.$setValidity(name, isValid);
- }
- }
-
- function validationDone(allValid) {
- if (localValidationRunId === currentValidationRunId) {
-
- doneCallback(allValid);
- }
- }
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$commitViewValue
- *
- * @description
- * Commit a pending update 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.
- */
- this.$commitViewValue = function() {
- var viewValue = ctrl.$viewValue;
-
- $timeout.cancel(pendingDebounce);
-
- // If the view value has not changed then we should just exit, except in the case where there is
- // a native validator on the element. In this case the validation state may have changed even though
- // the viewValue has stayed empty.
- if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
- return;
- }
- ctrl.$$lastCommittedViewValue = viewValue;
-
- // change to dirty
- if (ctrl.$pristine) {
- this.$setDirty();
- }
- this.$$parseAndValidate();
- };
-
- this.$$parseAndValidate = function() {
- var viewValue = ctrl.$$lastCommittedViewValue;
- var modelValue = viewValue;
- var parserValid = isUndefined(modelValue) ? undefined : true;
-
- if (parserValid) {
- for (var i = 0; i < ctrl.$parsers.length; i++) {
- modelValue = ctrl.$parsers[i](modelValue);
- if (isUndefined(modelValue)) {
- parserValid = false;
- break;
- }
- }
- }
- if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
- // ctrl.$modelValue has not been touched yet...
- ctrl.$modelValue = ngModelGet($scope);
- }
- var prevModelValue = ctrl.$modelValue;
- var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
- ctrl.$$rawModelValue = modelValue;
-
- if (allowInvalid) {
- ctrl.$modelValue = modelValue;
- writeToModelIfNeeded();
- }
-
- // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
- // This can happen if e.g. $setViewValue is called from inside a parser
- ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
- if (!allowInvalid) {
- // Note: Don't check ctrl.$valid here, as we could have
- // external validators (e.g. calculated on the server),
- // that just call $setValidity and need the model value
- // to calculate their validity.
- ctrl.$modelValue = allValid ? modelValue : undefined;
- writeToModelIfNeeded();
- }
- });
-
- function writeToModelIfNeeded() {
- if (ctrl.$modelValue !== prevModelValue) {
- ctrl.$$writeModelToScope();
- }
- }
- };
-
- this.$$writeModelToScope = function() {
- ngModelSet($scope, ctrl.$modelValue);
- forEach(ctrl.$viewChangeListeners, function(listener) {
- try {
- listener();
- } catch (e) {
- $exceptionHandler(e);
- }
- });
- };
-
- /**
- * @ngdoc method
- * @name ngModel.NgModelController#$setViewValue
- *
- * @description
- * Update the view value.
- *
- * This method should be called when an input directive want to change the view value; typically,
- * this is done from within a DOM event handler.
- *
- * For example {@link ng.directive:input input} calls it when the value of the input changes and
- * {@link ng.directive:select select} calls it when an option is selected.
- *
- * If the new `value` is an object (rather than a string or a number), we should make a copy of the
- * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
- * watch of objects, it only looks for a change of identity. If you only change the property of
- * the object then ngModel will not realise that the object has changed and will not invoke the
- * `$parsers` and `$validators` pipelines.
- *
- * For this reason, you should not change properties of the copy once it has been passed to
- * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
- *
- * When this method is called, the new `value` will be staged for committing through the `$parsers`
- * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
- * value sent directly for processing, finally to be applied to `$modelValue` and then the
- * **expression** specified in the `ng-model` attribute.
- *
- * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
- *
- * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
- * and the `default` trigger is not listed, all those actions will remain pending until one of the
- * `updateOn` events is triggered on the DOM element.
- * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
- * directive is used with a custom debounce for this particular event.
- *
- * Note that calling this function does not trigger a `$digest`.
- *
- * @param {string} value Value from the view.
- * @param {string} trigger Event that triggered the update.
- */
- this.$setViewValue = function(value, trigger) {
- ctrl.$viewValue = value;
- if (!ctrl.$options || ctrl.$options.updateOnDefault) {
- ctrl.$$debounceViewValueCommit(trigger);
- }
- };
-
- this.$$debounceViewValueCommit = function(trigger) {
- var debounceDelay = 0,
- options = ctrl.$options,
- debounce;
-
- if (options && isDefined(options.debounce)) {
- debounce = options.debounce;
- if (isNumber(debounce)) {
- debounceDelay = debounce;
- } else if (isNumber(debounce[trigger])) {
- debounceDelay = debounce[trigger];
- } else if (isNumber(debounce['default'])) {
- debounceDelay = debounce['default'];
- }
- }
-
- $timeout.cancel(pendingDebounce);
- if (debounceDelay) {
- pendingDebounce = $timeout(function() {
- ctrl.$commitViewValue();
- }, debounceDelay);
- } else if ($rootScope.$$phase) {
- ctrl.$commitViewValue();
- } else {
- $scope.$apply(function() {
- ctrl.$commitViewValue();
- });
- }
- };
-
- // model -> value
- // Note: we cannot use a normal scope.$watch as we want to detect the following:
- // 1. scope value is 'a'
- // 2. user enters 'b'
- // 3. ng-change kicks in and reverts scope value to 'a'
- // -> scope value did not change since the last digest as
- // ng-change executes in apply phase
- // 4. view should be changed back to 'a'
- $scope.$watch(function ngModelWatch() {
- var modelValue = ngModelGet($scope);
-
- // if scope model value and ngModel value are out of sync
- // TODO(perf): why not move this to the action fn?
- if (modelValue !== ctrl.$modelValue) {
- ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
-
- var formatters = ctrl.$formatters,
- idx = formatters.length;
-
- var viewValue = modelValue;
- while (idx--) {
- viewValue = formatters[idx](viewValue);
- }
- if (ctrl.$viewValue !== viewValue) {
- ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
- ctrl.$render();
-
- ctrl.$$runValidators(undefined, modelValue, viewValue, noop);
- }
- }
-
- return modelValue;
- });
-}];
-
-
-/**
- * @ngdoc directive
- * @name ngModel
- *
- * @element input
- * @priority 1
- *
- * @description
- * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
- * property on the scope using {@link ngModel.NgModelController NgModelController},
- * which is created and exposed by this directive.
- *
- * `ngModel` is responsible for:
- *
- * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
- * require.
- * - Providing validation behavior (i.e. required, number, email, url).
- * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
- * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
- * - Registering the control with its parent {@link ng.directive:form form}.
- *
- * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
- * current scope. If the property doesn't already exist on this scope, it will be created
- * implicitly and added to the scope.
- *
- * For best practices on using `ngModel`, see:
- *
- * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
- *
- * For basic examples, how to use `ngModel`, see:
- *
- * - {@link ng.directive:input input}
- * - {@link input[text] text}
- * - {@link input[checkbox] checkbox}
- * - {@link input[radio] radio}
- * - {@link input[number] number}
- * - {@link input[email] email}
- * - {@link input[url] url}
- * - {@link input[date] date}
- * - {@link input[datetime-local] datetime-local}
- * - {@link input[time] time}
- * - {@link input[month] month}
- * - {@link input[week] week}
- * - {@link ng.directive:select select}
- * - {@link ng.directive:textarea textarea}
- *
- * # CSS classes
- * The following CSS classes are added and removed on the associated input/select/textarea element
- * depending on the validity of the model.
- *
- * - `ng-valid`: the model is valid
- * - `ng-invalid`: the model is invalid
- * - `ng-valid-[key]`: for each valid key added by `$setValidity`
- * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
- * - `ng-pristine`: the control hasn't been interacted with yet
- * - `ng-dirty`: the control has been interacted with
- * - `ng-touched`: the control has been blurred
- * - `ng-untouched`: the control hasn't been blurred
- * - `ng-pending`: any `$asyncValidators` are unfulfilled
- *
- * Keep in mind that ngAnimate can detect each of these classes when added and removed.
- *
- * ## Animation Hooks
- *
- * Animations within models are triggered when any of the associated CSS classes are added and removed
- * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
- * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
- * The animations that are triggered within ngModel are similar to how they work in ngClass and
- * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
- *
- * The following example shows a simple way to utilize CSS transitions to style an input element
- * that has been rendered as invalid after it has been validated:
- *
- * <pre>
- * //be sure to include ngAnimate as a module to hook into more
- * //advanced animations
- * .my-input {
- * transition:0.5s linear all;
- * background: white;
- * }
- * .my-input.ng-invalid {
- * background: red;
- * color:white;
- * }
- * </pre>
- *
- * @example
- * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
- <file name="index.html">
- <script>
- angular.module('inputExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.val = '1';
- }]);
- </script>
- <style>
- .my-input {
- -webkit-transition:all linear 0.5s;
- transition:all linear 0.5s;
- background: transparent;
- }
- .my-input.ng-invalid {
- color:white;
- background: red;
- }
- </style>
- Update input to see transitions when valid/invalid.
- Integer is a valid value.
- <form name="testForm" ng-controller="ExampleController">
- <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" />
- </form>
- </file>
- * </example>
- *
- * ## Binding to a getter/setter
- *
- * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
- * function that returns a representation of the model when called with zero arguments, and sets
- * the internal state of a model when called with an argument. It's sometimes useful to use this
- * for models that have an internal representation that's different than what the model exposes
- * to the view.
- *
- * <div class="alert alert-success">
- * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
- * frequently than other parts of your code.
- * </div>
- *
- * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
- * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
- * a `<form>`, which will enable this behavior for all `<input>`s within it. See
- * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
- *
- * The following example shows how to use `ngModel` with a getter/setter:
- *
- * @example
- * <example name="ngModel-getter-setter" module="getterSetterExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ getterSetter: true }" />
- </form>
- <pre>user.name = <span ng-bind="user.name()"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('getterSetterExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- var _name = 'Brian';
- $scope.user = {
- name: function(newName) {
- if (angular.isDefined(newName)) {
- _name = newName;
- }
- return _name;
- }
- };
- }]);
- </file>
- * </example>
- */
-var ngModelDirective = ['$rootScope', function($rootScope) {
- return {
- restrict: 'A',
- require: ['ngModel', '^?form', '^?ngModelOptions'],
- controller: NgModelController,
- // Prelink needs to run before any input directive
- // so that we can set the NgModelOptions in NgModelController
- // before anyone else uses it.
- priority: 1,
- compile: function ngModelCompile(element) {
- // Setup initial state of the control
- element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
-
- return {
- pre: function ngModelPreLink(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0],
- formCtrl = ctrls[1] || nullFormCtrl;
-
- modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
-
- // notify others, especially parent forms
- formCtrl.$addControl(modelCtrl);
-
- attr.$observe('name', function(newValue) {
- if (modelCtrl.$name !== newValue) {
- formCtrl.$$renameControl(modelCtrl, newValue);
- }
- });
-
- scope.$on('$destroy', function() {
- formCtrl.$removeControl(modelCtrl);
- });
- },
- post: function ngModelPostLink(scope, element, attr, ctrls) {
- var modelCtrl = ctrls[0];
- if (modelCtrl.$options && modelCtrl.$options.updateOn) {
- element.on(modelCtrl.$options.updateOn, function(ev) {
- modelCtrl.$$debounceViewValueCommit(ev && ev.type);
- });
- }
-
- element.on('blur', function(ev) {
- if (modelCtrl.$touched) return;
-
- if ($rootScope.$$phase) {
- scope.$evalAsync(modelCtrl.$setTouched);
- } else {
- scope.$apply(modelCtrl.$setTouched);
- }
- });
- }
- };
- }
- };
-}];
-
-
-/**
- * @ngdoc directive
- * @name ngChange
- *
- * @description
- * Evaluate the given expression when the user changes the input.
- * The expression is evaluated immediately, unlike the JavaScript onchange event
- * which only triggers at the end of a change (usually, when the user leaves the
- * form element or presses the return key).
- *
- * The `ngChange` expression is only evaluated when a change in the input value causes
- * a new value to be committed to the model.
- *
- * It will not be evaluated:
- * * if the value returned from the `$parsers` transformation pipeline has not changed
- * * if the input has continued to be invalid since the model will stay `null`
- * * if the model is changed programmatically and not by a change to the input value
- *
- *
- * Note, this directive requires `ngModel` to be present.
- *
- * @element input
- * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
- * in input value.
- *
- * @example
- * <example name="ngChange-directive" module="changeExample">
- * <file name="index.html">
- * <script>
- * angular.module('changeExample', [])
- * .controller('ExampleController', ['$scope', function($scope) {
- * $scope.counter = 0;
- * $scope.change = function() {
- * $scope.counter++;
- * };
- * }]);
- * </script>
- * <div ng-controller="ExampleController">
- * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
- * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
- * <label for="ng-change-example2">Confirmed</label><br />
- * <tt>debug = {{confirmed}}</tt><br/>
- * <tt>counter = {{counter}}</tt><br/>
- * </div>
- * </file>
- * <file name="protractor.js" type="protractor">
- * var counter = element(by.binding('counter'));
- * var debug = element(by.binding('confirmed'));
- *
- * it('should evaluate the expression if changing from view', function() {
- * expect(counter.getText()).toContain('0');
- *
- * element(by.id('ng-change-example1')).click();
- *
- * expect(counter.getText()).toContain('1');
- * expect(debug.getText()).toContain('true');
- * });
- *
- * it('should not evaluate the expression if changing from model', function() {
- * element(by.id('ng-change-example2')).click();
-
- * expect(counter.getText()).toContain('0');
- * expect(debug.getText()).toContain('true');
- * });
- * </file>
- * </example>
- */
-var ngChangeDirective = valueFn({
- restrict: 'A',
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- ctrl.$viewChangeListeners.push(function() {
- scope.$eval(attr.ngChange);
- });
- }
-});
-
-
-var requiredDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
- attr.required = true; // force truthy in case we are on non input element
-
- ctrl.$validators.required = function(modelValue, viewValue) {
- return !attr.required || !ctrl.$isEmpty(viewValue);
- };
-
- attr.$observe('required', function() {
- ctrl.$validate();
- });
- }
- };
-};
-
-
-var patternDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var regexp, patternExp = attr.ngPattern || attr.pattern;
- attr.$observe('pattern', function(regex) {
- if (isString(regex) && regex.length > 0) {
- regex = new RegExp('^' + regex + '$');
- }
-
- if (regex && !regex.test) {
- throw minErr('ngPattern')('noregexp',
- 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
- regex, startingTag(elm));
- }
-
- regexp = regex || undefined;
- ctrl.$validate();
- });
-
- ctrl.$validators.pattern = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
- };
- }
- };
-};
-
-
-var maxlengthDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var maxlength = -1;
- attr.$observe('maxlength', function(value) {
- var intVal = int(value);
- maxlength = isNaN(intVal) ? -1 : intVal;
- ctrl.$validate();
- });
- ctrl.$validators.maxlength = function(modelValue, viewValue) {
- return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
- };
- }
- };
-};
-
-var minlengthDirective = function() {
- return {
- restrict: 'A',
- require: '?ngModel',
- link: function(scope, elm, attr, ctrl) {
- if (!ctrl) return;
-
- var minlength = 0;
- attr.$observe('minlength', function(value) {
- minlength = int(value) || 0;
- ctrl.$validate();
- });
- ctrl.$validators.minlength = function(modelValue, viewValue) {
- return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
- };
- }
- };
-};
-
-
-/**
- * @ngdoc directive
- * @name ngList
- *
- * @description
- * Text input that converts between a delimited string and an array of strings. The default
- * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
- * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
- *
- * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
- * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
- * list item is respected. This implies that the user of the directive is responsible for
- * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
- * tab or newline character.
- * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
- * when joining the list items back together) and whitespace around each list item is stripped
- * before it is added to the model.
- *
- * ### Example with Validation
- *
- * <example name="ngList-directive" module="listExample">
- * <file name="app.js">
- * angular.module('listExample', [])
- * .controller('ExampleController', ['$scope', function($scope) {
- * $scope.names = ['morpheus', 'neo', 'trinity'];
- * }]);
- * </file>
- * <file name="index.html">
- * <form name="myForm" ng-controller="ExampleController">
- * List: <input name="namesInput" ng-model="names" ng-list required>
- * <span class="error" ng-show="myForm.namesInput.$error.required">
- * Required!</span>
- * <br>
- * <tt>names = {{names}}</tt><br/>
- * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
- * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
- * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
- * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
- * </form>
- * </file>
- * <file name="protractor.js" type="protractor">
- * var listInput = element(by.model('names'));
- * var names = element(by.exactBinding('names'));
- * var valid = element(by.binding('myForm.namesInput.$valid'));
- * var error = element(by.css('span.error'));
- *
- * it('should initialize to model', function() {
- * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
- * expect(valid.getText()).toContain('true');
- * expect(error.getCssValue('display')).toBe('none');
- * });
- *
- * it('should be invalid if empty', function() {
- * listInput.clear();
- * listInput.sendKeys('');
- *
- * expect(names.getText()).toContain('');
- * expect(valid.getText()).toContain('false');
- * expect(error.getCssValue('display')).not.toBe('none');
- * });
- * </file>
- * </example>
- *
- * ### Example - splitting on whitespace
- * <example name="ngList-directive-newlines">
- * <file name="index.html">
- * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
- * <pre>{{ list | json }}</pre>
- * </file>
- * <file name="protractor.js" type="protractor">
- * it("should split the text by newlines", function() {
- * var listInput = element(by.model('list'));
- * var output = element(by.binding('list | json'));
- * listInput.sendKeys('abc\ndef\nghi');
- * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
- * });
- * </file>
- * </example>
- *
- * @element input
- * @param {string=} ngList optional delimiter that should be used to split the value.
- */
-var ngListDirective = function() {
- return {
- restrict: 'A',
- priority: 100,
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- // We want to control whitespace trimming so we use this convoluted approach
- // to access the ngList attribute, which doesn't pre-trim the attribute
- var ngList = element.attr(attr.$attr.ngList) || ', ';
- var trimValues = attr.ngTrim !== 'false';
- var separator = trimValues ? trim(ngList) : ngList;
-
- var parse = function(viewValue) {
- // If the viewValue is invalid (say required but empty) it will be `undefined`
- if (isUndefined(viewValue)) return;
-
- var list = [];
-
- if (viewValue) {
- forEach(viewValue.split(separator), function(value) {
- if (value) list.push(trimValues ? trim(value) : value);
- });
- }
-
- return list;
- };
-
- ctrl.$parsers.push(parse);
- ctrl.$formatters.push(function(value) {
- if (isArray(value)) {
- return value.join(ngList);
- }
-
- return undefined;
- });
-
- // Override the standard $isEmpty because an empty array means the input is empty.
- ctrl.$isEmpty = function(value) {
- return !value || !value.length;
- };
- }
- };
-};
var CONSTANT_VALUE_REGEXP = /^(true|false|\d+)$/;
@@ -21570,281 +20513,6 @@ var ngValueDirective = function() {
/**
* @ngdoc directive
- * @name ngModelOptions
- *
- * @description
- * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
- * events that will trigger a model update and/or a debouncing delay so that the actual update only
- * takes place when a timer expires; this timer will be reset after another change takes place.
- *
- * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
- * be different than the value in the actual model. This means that if you update the model you
- * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
- * order to make sure it is synchronized with the model and that any debounced action is canceled.
- *
- * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
- * method is by making sure the input is placed inside a form that has a `name` attribute. This is
- * important because `form` controllers are published to the related scope under the name in their
- * `name` attribute.
- *
- * Any pending changes will take place immediately when an enclosing form is submitted via the
- * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
- * to have access to the updated model.
- *
- * `ngModelOptions` has an effect on the element it's declared on and its descendants.
- *
- * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
- * - `updateOn`: string specifying which event should the input be bound to. You can set several
- * events using an space delimited list. There is a special event called `default` that
- * matches the default events belonging of the control.
- * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
- * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
- * custom value for each event. For example:
- * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
- * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
- * not validate correctly instead of the default behavior of setting the model to undefined.
- * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
- `ngModel` as getters/setters.
- * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
- * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
- * otherwise the default timezone of the browser will be used.
- *
- * @example
-
- The following example shows how to override immediate updates. Changes on the inputs within the
- form will update the model only when the control loses focus (blur event). If `escape` key is
- pressed while the input field is focused, the value is reset to the value in the current model.
-
- <example name="ngModelOptions-directive-blur" module="optionsExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ updateOn: 'blur' }"
- ng-keyup="cancel($event)" /><br />
-
- Other data:
- <input type="text" ng-model="user.data" /><br />
- </form>
- <pre>user.name = <span ng-bind="user.name"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('optionsExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say', data: '' };
-
- $scope.cancel = function(e) {
- if (e.keyCode == 27) {
- $scope.userForm.userName.$rollbackViewValue();
- }
- };
- }]);
- </file>
- <file name="protractor.js" type="protractor">
- var model = element(by.binding('user.name'));
- var input = element(by.model('user.name'));
- var other = element(by.model('user.data'));
-
- it('should allow custom events', function() {
- input.sendKeys(' hello');
- input.click();
- expect(model.getText()).toEqual('say');
- other.click();
- expect(model.getText()).toEqual('say hello');
- });
-
- it('should $rollbackViewValue when model changes', function() {
- input.sendKeys(' hello');
- expect(input.getAttribute('value')).toEqual('say hello');
- input.sendKeys(protractor.Key.ESCAPE);
- expect(input.getAttribute('value')).toEqual('say');
- other.click();
- expect(model.getText()).toEqual('say');
- });
- </file>
- </example>
-
- This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
- If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
-
- <example name="ngModelOptions-directive-debounce" module="optionsExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ debounce: 1000 }" />
- <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
- </form>
- <pre>user.name = <span ng-bind="user.name"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('optionsExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- $scope.user = { name: 'say' };
- }]);
- </file>
- </example>
-
- This one shows how to bind to getter/setters:
-
- <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
- <file name="index.html">
- <div ng-controller="ExampleController">
- <form name="userForm">
- Name:
- <input type="text" name="userName"
- ng-model="user.name"
- ng-model-options="{ getterSetter: true }" />
- </form>
- <pre>user.name = <span ng-bind="user.name()"></span></pre>
- </div>
- </file>
- <file name="app.js">
- angular.module('getterSetterExample', [])
- .controller('ExampleController', ['$scope', function($scope) {
- var _name = 'Brian';
- $scope.user = {
- name: function(newName) {
- return angular.isDefined(newName) ? (_name = newName) : _name;
- }
- };
- }]);
- </file>
- </example>
- */
-var ngModelOptionsDirective = function() {
- return {
- restrict: 'A',
- controller: ['$scope', '$attrs', function($scope, $attrs) {
- var that = this;
- this.$options = $scope.$eval($attrs.ngModelOptions);
- // Allow adding/overriding bound events
- if (this.$options.updateOn !== undefined) {
- this.$options.updateOnDefault = false;
- // extract "default" pseudo-event from list of events that can trigger a model update
- this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
- that.$options.updateOnDefault = true;
- return ' ';
- }));
- } else {
- this.$options.updateOnDefault = true;
- }
- }]
- };
-};
-
-// helper methods
-function addSetValidityMethod(context) {
- var ctrl = context.ctrl,
- $element = context.$element,
- classCache = {},
- set = context.set,
- unset = context.unset,
- parentForm = context.parentForm,
- $animate = context.$animate;
-
- classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
-
- ctrl.$setValidity = setValidity;
-
- function setValidity(validationErrorKey, state, options) {
- if (state === undefined) {
- createAndSet('$pending', validationErrorKey, options);
- } else {
- unsetAndCleanup('$pending', validationErrorKey, options);
- }
- if (!isBoolean(state)) {
- unset(ctrl.$error, validationErrorKey, options);
- unset(ctrl.$$success, validationErrorKey, options);
- } else {
- if (state) {
- unset(ctrl.$error, validationErrorKey, options);
- set(ctrl.$$success, validationErrorKey, options);
- } else {
- set(ctrl.$error, validationErrorKey, options);
- unset(ctrl.$$success, validationErrorKey, options);
- }
- }
- if (ctrl.$pending) {
- cachedToggleClass(PENDING_CLASS, true);
- ctrl.$valid = ctrl.$invalid = undefined;
- toggleValidationCss('', null);
- } else {
- cachedToggleClass(PENDING_CLASS, false);
- ctrl.$valid = isObjectEmpty(ctrl.$error);
- ctrl.$invalid = !ctrl.$valid;
- toggleValidationCss('', ctrl.$valid);
- }
-
- // re-read the state as the set/unset methods could have
- // combined state in ctrl.$error[validationError] (used for forms),
- // where setting/unsetting only increments/decrements the value,
- // and does not replace it.
- var combinedState;
- if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
- combinedState = undefined;
- } else if (ctrl.$error[validationErrorKey]) {
- combinedState = false;
- } else if (ctrl.$$success[validationErrorKey]) {
- combinedState = true;
- } else {
- combinedState = null;
- }
- toggleValidationCss(validationErrorKey, combinedState);
- parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
- }
-
- function createAndSet(name, value, options) {
- if (!ctrl[name]) {
- ctrl[name] = {};
- }
- set(ctrl[name], value, options);
- }
-
- function unsetAndCleanup(name, value, options) {
- if (ctrl[name]) {
- unset(ctrl[name], value, options);
- }
- if (isObjectEmpty(ctrl[name])) {
- ctrl[name] = undefined;
- }
- }
-
- function cachedToggleClass(className, switchValue) {
- if (switchValue && !classCache[className]) {
- $animate.addClass($element, className);
- classCache[className] = true;
- } else if (!switchValue && classCache[className]) {
- $animate.removeClass($element, className);
- classCache[className] = false;
- }
- }
-
- function toggleValidationCss(validationErrorKey, isValid) {
- validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
-
- cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
- cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
- }
-}
-
-function isObjectEmpty(obj) {
- if (obj) {
- for (var prop in obj) {
- return false;
- }
- }
- return true;
-}
-
-/**
- * @ngdoc directive
* @name ngBind
* @restrict AC
*
@@ -22049,6 +20717,83 @@ var ngBindHtmlDirective = ['$sce', '$parse', '$compile', function($sce, $parse,
};
}];
+/**
+ * @ngdoc directive
+ * @name ngChange
+ *
+ * @description
+ * Evaluate the given expression when the user changes the input.
+ * The expression is evaluated immediately, unlike the JavaScript onchange event
+ * which only triggers at the end of a change (usually, when the user leaves the
+ * form element or presses the return key).
+ *
+ * The `ngChange` expression is only evaluated when a change in the input value causes
+ * a new value to be committed to the model.
+ *
+ * It will not be evaluated:
+ * * if the value returned from the `$parsers` transformation pipeline has not changed
+ * * if the input has continued to be invalid since the model will stay `null`
+ * * if the model is changed programmatically and not by a change to the input value
+ *
+ *
+ * Note, this directive requires `ngModel` to be present.
+ *
+ * @element input
+ * @param {expression} ngChange {@link guide/expression Expression} to evaluate upon change
+ * in input value.
+ *
+ * @example
+ * <example name="ngChange-directive" module="changeExample">
+ * <file name="index.html">
+ * <script>
+ * angular.module('changeExample', [])
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.counter = 0;
+ * $scope.change = function() {
+ * $scope.counter++;
+ * };
+ * }]);
+ * </script>
+ * <div ng-controller="ExampleController">
+ * <input type="checkbox" ng-model="confirmed" ng-change="change()" id="ng-change-example1" />
+ * <input type="checkbox" ng-model="confirmed" id="ng-change-example2" />
+ * <label for="ng-change-example2">Confirmed</label><br />
+ * <tt>debug = {{confirmed}}</tt><br/>
+ * <tt>counter = {{counter}}</tt><br/>
+ * </div>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * var counter = element(by.binding('counter'));
+ * var debug = element(by.binding('confirmed'));
+ *
+ * it('should evaluate the expression if changing from view', function() {
+ * expect(counter.getText()).toContain('0');
+ *
+ * element(by.id('ng-change-example1')).click();
+ *
+ * expect(counter.getText()).toContain('1');
+ * expect(debug.getText()).toContain('true');
+ * });
+ *
+ * it('should not evaluate the expression if changing from model', function() {
+ * element(by.id('ng-change-example2')).click();
+
+ * expect(counter.getText()).toContain('0');
+ * expect(debug.getText()).toContain('true');
+ * });
+ * </file>
+ * </example>
+ */
+var ngChangeDirective = valueFn({
+ restrict: 'A',
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ ctrl.$viewChangeListeners.push(function() {
+ scope.$eval(attr.ngChange);
+ });
+ }
+});
+
function classDirective(name, selector) {
name = 'ngClass' + name;
return ['$animate', function($animate) {
@@ -22190,8 +20935,9 @@ function classDirective(name, selector) {
* new classes are added.
*
* @animations
- * add - happens just before the class is applied to the element
- * remove - happens just before the class is removed from the element
+ * **add** - happens just before the class is applied to the elements
+ *
+ * **remove** - happens just before the class is removed from the element
*
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
@@ -22438,17 +21184,13 @@ var ngClassEvenDirective = classDirective('Even', 1);
* document; alternatively, the css rule above must be included in the external stylesheet of the
* application.
*
- * Legacy browsers, like IE7, do not provide attribute selector support (added in CSS 2.1) so they
- * cannot match the `[ng\:cloak]` selector. To work around this limitation, you must add the css
- * class `ng-cloak` in addition to the `ngCloak` directive as shown in the example below.
- *
* @element ANY
*
* @example
<example>
<file name="index.html">
<div id="template1" ng-cloak>{{ 'hello' }}</div>
- <div id="template2" ng-cloak class="ng-cloak">{{ 'hello IE7' }}</div>
+ <div id="template2" class="ng-cloak">{{ 'world' }}</div>
</file>
<file name="protractor.js" type="protractor">
it('should remove the template directive and css class', function() {
@@ -23533,7 +22275,7 @@ var ngIfDirective = ['$animate', function($animate) {
<select ng-model="template" ng-options="t.name for t in templates">
<option value="">(blank)</option>
</select>
- url of the template: <tt>{{template.url}}</tt>
+ url of the template: <code>{{template.url}}</code>
<hr/>
<div class="slide-animate-container">
<div class="slide-animate" ng-include="template.url"></div>
@@ -23657,13 +22399,13 @@ var ngIfDirective = ['$animate', function($animate) {
* @name ngInclude#$includeContentError
* @eventType emit on the scope ngInclude was declared in
* @description
- * Emitted when a template HTTP request yields an erronous response (status < 200 || status > 299)
+ * Emitted when a template HTTP request yields an erroneous response (status < 200 || status > 299)
*
* @param {Object} angularEvent Synthetic event object.
* @param {String} src URL of content to load.
*/
-var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce',
- function($templateRequest, $anchorScroll, $animate, $sce) {
+var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
+ function($templateRequest, $anchorScroll, $animate) {
return {
restrict: 'ECA',
priority: 400,
@@ -23699,7 +22441,7 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate', '$sce
}
};
- scope.$watch($sce.parseAsResourceUrl(srcExp), function ngIncludeWatchAction(src) {
+ scope.$watch(srcExp, function ngIncludeWatchAction(src) {
var afterAnimation = function() {
if (isDefined(autoScrollExp) && (!autoScrollExp || scope.$eval(autoScrollExp))) {
$anchorScroll();
@@ -23797,7 +22539,7 @@ var ngIncludeFillContentDirective = ['$compile',
* **Note**: If you have assignment in `ngInit` along with {@link ng.$filter `$filter`}, make
* sure you have parenthesis for correct precedence:
* <pre class="prettyprint">
- * <div ng-init="test1 = (data | orderBy:'name')"></div>
+ * `<div ng-init="test1 = (data | orderBy:'name')"></div>`
* </pre>
* </div>
*
@@ -23847,6 +22589,1472 @@ var ngInitDirective = ngDirective({
/**
* @ngdoc directive
+ * @name ngList
+ *
+ * @description
+ * Text input that converts between a delimited string and an array of strings. The default
+ * delimiter is a comma followed by a space - equivalent to `ng-list=", "`. You can specify a custom
+ * delimiter as the value of the `ngList` attribute - for example, `ng-list=" | "`.
+ *
+ * The behaviour of the directive is affected by the use of the `ngTrim` attribute.
+ * * If `ngTrim` is set to `"false"` then whitespace around both the separator and each
+ * list item is respected. This implies that the user of the directive is responsible for
+ * dealing with whitespace but also allows you to use whitespace as a delimiter, such as a
+ * tab or newline character.
+ * * Otherwise whitespace around the delimiter is ignored when splitting (although it is respected
+ * when joining the list items back together) and whitespace around each list item is stripped
+ * before it is added to the model.
+ *
+ * ### Example with Validation
+ *
+ * <example name="ngList-directive" module="listExample">
+ * <file name="app.js">
+ * angular.module('listExample', [])
+ * .controller('ExampleController', ['$scope', function($scope) {
+ * $scope.names = ['morpheus', 'neo', 'trinity'];
+ * }]);
+ * </file>
+ * <file name="index.html">
+ * <form name="myForm" ng-controller="ExampleController">
+ * List: <input name="namesInput" ng-model="names" ng-list required>
+ * <span class="error" ng-show="myForm.namesInput.$error.required">
+ * Required!</span>
+ * <br>
+ * <tt>names = {{names}}</tt><br/>
+ * <tt>myForm.namesInput.$valid = {{myForm.namesInput.$valid}}</tt><br/>
+ * <tt>myForm.namesInput.$error = {{myForm.namesInput.$error}}</tt><br/>
+ * <tt>myForm.$valid = {{myForm.$valid}}</tt><br/>
+ * <tt>myForm.$error.required = {{!!myForm.$error.required}}</tt><br/>
+ * </form>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * var listInput = element(by.model('names'));
+ * var names = element(by.exactBinding('names'));
+ * var valid = element(by.binding('myForm.namesInput.$valid'));
+ * var error = element(by.css('span.error'));
+ *
+ * it('should initialize to model', function() {
+ * expect(names.getText()).toContain('["morpheus","neo","trinity"]');
+ * expect(valid.getText()).toContain('true');
+ * expect(error.getCssValue('display')).toBe('none');
+ * });
+ *
+ * it('should be invalid if empty', function() {
+ * listInput.clear();
+ * listInput.sendKeys('');
+ *
+ * expect(names.getText()).toContain('');
+ * expect(valid.getText()).toContain('false');
+ * expect(error.getCssValue('display')).not.toBe('none');
+ * });
+ * </file>
+ * </example>
+ *
+ * ### Example - splitting on whitespace
+ * <example name="ngList-directive-newlines">
+ * <file name="index.html">
+ * <textarea ng-model="list" ng-list="&#10;" ng-trim="false"></textarea>
+ * <pre>{{ list | json }}</pre>
+ * </file>
+ * <file name="protractor.js" type="protractor">
+ * it("should split the text by newlines", function() {
+ * var listInput = element(by.model('list'));
+ * var output = element(by.binding('list | json'));
+ * listInput.sendKeys('abc\ndef\nghi');
+ * expect(output.getText()).toContain('[\n "abc",\n "def",\n "ghi"\n]');
+ * });
+ * </file>
+ * </example>
+ *
+ * @element input
+ * @param {string=} ngList optional delimiter that should be used to split the value.
+ */
+var ngListDirective = function() {
+ return {
+ restrict: 'A',
+ priority: 100,
+ require: 'ngModel',
+ link: function(scope, element, attr, ctrl) {
+ // We want to control whitespace trimming so we use this convoluted approach
+ // to access the ngList attribute, which doesn't pre-trim the attribute
+ var ngList = element.attr(attr.$attr.ngList) || ', ';
+ var trimValues = attr.ngTrim !== 'false';
+ var separator = trimValues ? trim(ngList) : ngList;
+
+ var parse = function(viewValue) {
+ // If the viewValue is invalid (say required but empty) it will be `undefined`
+ if (isUndefined(viewValue)) return;
+
+ var list = [];
+
+ if (viewValue) {
+ forEach(viewValue.split(separator), function(value) {
+ if (value) list.push(trimValues ? trim(value) : value);
+ });
+ }
+
+ return list;
+ };
+
+ ctrl.$parsers.push(parse);
+ ctrl.$formatters.push(function(value) {
+ if (isArray(value)) {
+ return value.join(ngList);
+ }
+
+ return undefined;
+ });
+
+ // Override the standard $isEmpty because an empty array means the input is empty.
+ ctrl.$isEmpty = function(value) {
+ return !value || !value.length;
+ };
+ }
+ };
+};
+
+/* global VALID_CLASS: true,
+ INVALID_CLASS: true,
+ PRISTINE_CLASS: true,
+ DIRTY_CLASS: true,
+ UNTOUCHED_CLASS: true,
+ TOUCHED_CLASS: true,
+*/
+
+var VALID_CLASS = 'ng-valid',
+ INVALID_CLASS = 'ng-invalid',
+ PRISTINE_CLASS = 'ng-pristine',
+ DIRTY_CLASS = 'ng-dirty',
+ UNTOUCHED_CLASS = 'ng-untouched',
+ TOUCHED_CLASS = 'ng-touched',
+ PENDING_CLASS = 'ng-pending';
+
+var ngModelMinErr = minErr('ngModel');
+
+/**
+ * @ngdoc type
+ * @name ngModel.NgModelController
+ *
+ * @property {string} $viewValue Actual string value in the view.
+ * @property {*} $modelValue The value in the model that the control is bound to.
+ * @property {Array.<Function>} $parsers Array of functions to execute, as a pipeline, whenever
+ the control reads value from the DOM. The functions are called in array order, each passing
+ its return value through to the next. The last return value is forwarded to the
+ {@link ngModel.NgModelController#$validators `$validators`} collection.
+
+Parsers are used to sanitize / convert the {@link ngModel.NgModelController#$viewValue
+`$viewValue`}.
+
+Returning `undefined` from a parser means a parse error occurred. In that case,
+no {@link ngModel.NgModelController#$validators `$validators`} will run and the `ngModel`
+will be set to `undefined` unless {@link ngModelOptions `ngModelOptions.allowInvalid`}
+is set to `true`. The parse error is stored in `ngModel.$error.parse`.
+
+ *
+ * @property {Array.<Function>} $formatters Array of functions to execute, as a pipeline, whenever
+ the model value changes. The functions are called in reverse array order, each passing the value through to the
+ next. The last return value is used as the actual DOM value.
+ Used to format / convert values for display in the control.
+ * ```js
+ * function formatter(value) {
+ * if (value) {
+ * return value.toUpperCase();
+ * }
+ * }
+ * ngModel.$formatters.push(formatter);
+ * ```
+ *
+ * @property {Object.<string, function>} $validators A collection of validators that are applied
+ * whenever the model value changes. The key value within the object refers to the name of the
+ * validator while the function refers to the validation operation. The validation operation is
+ * provided with the model value as an argument and must return a true or false value depending
+ * on the response of that validation.
+ *
+ * ```js
+ * ngModel.$validators.validCharacters = function(modelValue, viewValue) {
+ * var value = modelValue || viewValue;
+ * return /[0-9]+/.test(value) &&
+ * /[a-z]+/.test(value) &&
+ * /[A-Z]+/.test(value) &&
+ * /\W+/.test(value);
+ * };
+ * ```
+ *
+ * @property {Object.<string, function>} $asyncValidators A collection of validations that are expected to
+ * perform an asynchronous validation (e.g. a HTTP request). The validation function that is provided
+ * is expected to return a promise when it is run during the model validation process. Once the promise
+ * is delivered then the validation status will be set to true when fulfilled and false when rejected.
+ * When the asynchronous validators are triggered, each of the validators will run in parallel and the model
+ * value will only be updated once all validators have been fulfilled. As long as an asynchronous validator
+ * is unfulfilled, its key will be added to the controllers `$pending` property. Also, all asynchronous validators
+ * will only run once all synchronous validators have passed.
+ *
+ * Please note that if $http is used then it is important that the server returns a success HTTP response code
+ * in order to fulfill the validation and a status level of `4xx` in order to reject the validation.
+ *
+ * ```js
+ * ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
+ * var value = modelValue || viewValue;
+ *
+ * // Lookup user by username
+ * return $http.get('/api/users/' + value).
+ * then(function resolved() {
+ * //username exists, this means validation fails
+ * return $q.reject('exists');
+ * }, function rejected() {
+ * //username does not exist, therefore this validation passes
+ * return true;
+ * });
+ * };
+ * ```
+ *
+ * @property {Array.<Function>} $viewChangeListeners Array of functions to execute whenever the
+ * view value has changed. It is called with no arguments, and its return value is ignored.
+ * This can be used in place of additional $watches against the model value.
+ *
+ * @property {Object} $error An object hash with all failing validator ids as keys.
+ * @property {Object} $pending An object hash with all pending validator ids as keys.
+ *
+ * @property {boolean} $untouched True if control has not lost focus yet.
+ * @property {boolean} $touched True if control has lost focus.
+ * @property {boolean} $pristine True if user has not interacted with the control yet.
+ * @property {boolean} $dirty True if user has already interacted with the control.
+ * @property {boolean} $valid True if there is no error.
+ * @property {boolean} $invalid True if at least one error on the control.
+ * @property {string} $name The name attribute of the control.
+ *
+ * @description
+ *
+ * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
+ * The controller contains services for data-binding, validation, CSS updates, and value formatting
+ * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
+ * listening to DOM events.
+ * Such DOM related logic should be provided by other directives which make use of
+ * `NgModelController` for data-binding to control elements.
+ * Angular provides this DOM logic for most {@link input `input`} elements.
+ * At the end of this page you can find a {@link ngModel.NgModelController#custom-control-example
+ * custom control example} that uses `ngModelController` to bind to `contenteditable` elements.
+ *
+ * @example
+ * ### Custom Control Example
+ * This example shows how to use `NgModelController` with a custom control to achieve
+ * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
+ * collaborate together to achieve the desired result.
+ *
+ * `contenteditable` is an HTML5 attribute, which tells the browser to let the element
+ * contents be edited in place by the user.
+ *
+ * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize}
+ * module to automatically remove "bad" content like inline event listener (e.g. `<span onclick="...">`).
+ * However, as we are using `$sce` the model can still decide to provide unsafe content if it marks
+ * that content using the `$sce` service.
+ *
+ * <example name="NgModelController" module="customControl" deps="angular-sanitize.js">
+ <file name="style.css">
+ [contenteditable] {
+ border: 1px solid black;
+ background-color: white;
+ min-height: 20px;
+ }
+
+ .ng-invalid {
+ border: 1px solid red;
+ }
+
+ </file>
+ <file name="script.js">
+ angular.module('customControl', ['ngSanitize']).
+ directive('contenteditable', ['$sce', function($sce) {
+ return {
+ restrict: 'A', // only activate on element attribute
+ require: '?ngModel', // get a hold of NgModelController
+ link: function(scope, element, attrs, ngModel) {
+ if (!ngModel) return; // do nothing if no ng-model
+
+ // Specify how UI should be updated
+ ngModel.$render = function() {
+ element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
+ };
+
+ // Listen for change events to enable binding
+ element.on('blur keyup change', function() {
+ scope.$evalAsync(read);
+ });
+ read(); // initialize
+
+ // Write data to the model
+ function read() {
+ var html = element.html();
+ // When we clear the content editable the browser leaves a <br> behind
+ // If strip-br attribute is provided then we strip this out
+ if ( attrs.stripBr && html == '<br>' ) {
+ html = '';
+ }
+ ngModel.$setViewValue(html);
+ }
+ }
+ };
+ }]);
+ </file>
+ <file name="index.html">
+ <form name="myForm">
+ <div contenteditable
+ name="myWidget" ng-model="userContent"
+ strip-br="true"
+ required>Change me!</div>
+ <span ng-show="myForm.myWidget.$error.required">Required!</span>
+ <hr>
+ <textarea ng-model="userContent"></textarea>
+ </form>
+ </file>
+ <file name="protractor.js" type="protractor">
+ it('should data-bind and become invalid', function() {
+ if (browser.params.browser == 'safari' || browser.params.browser == 'firefox') {
+ // SafariDriver can't handle contenteditable
+ // and Firefox driver can't clear contenteditables very well
+ return;
+ }
+ var contentEditable = element(by.css('[contenteditable]'));
+ var content = 'Change me!';
+
+ expect(contentEditable.getText()).toEqual(content);
+
+ contentEditable.clear();
+ contentEditable.sendKeys(protractor.Key.BACK_SPACE);
+ expect(contentEditable.getText()).toEqual('');
+ expect(contentEditable.getAttribute('class')).toMatch(/ng-invalid-required/);
+ });
+ </file>
+ * </example>
+ *
+ *
+ */
+var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$parse', '$animate', '$timeout', '$rootScope', '$q', '$interpolate',
+ function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
+ this.$viewValue = Number.NaN;
+ this.$modelValue = Number.NaN;
+ this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
+ this.$validators = {};
+ this.$asyncValidators = {};
+ this.$parsers = [];
+ this.$formatters = [];
+ this.$viewChangeListeners = [];
+ this.$untouched = true;
+ this.$touched = false;
+ this.$pristine = true;
+ this.$dirty = false;
+ this.$valid = true;
+ this.$invalid = false;
+ this.$error = {}; // keep invalid keys here
+ this.$$success = {}; // keep valid keys here
+ this.$pending = undefined; // keep pending keys here
+ this.$name = $interpolate($attr.name || '', false)($scope);
+
+
+ var parsedNgModel = $parse($attr.ngModel),
+ parsedNgModelAssign = parsedNgModel.assign,
+ ngModelGet = parsedNgModel,
+ ngModelSet = parsedNgModelAssign,
+ pendingDebounce = null,
+ parserValid,
+ ctrl = this;
+
+ this.$$setOptions = function(options) {
+ ctrl.$options = options;
+ if (options && options.getterSetter) {
+ var invokeModelGetter = $parse($attr.ngModel + '()'),
+ invokeModelSetter = $parse($attr.ngModel + '($$$p)');
+
+ ngModelGet = function($scope) {
+ var modelValue = parsedNgModel($scope);
+ if (isFunction(modelValue)) {
+ modelValue = invokeModelGetter($scope);
+ }
+ return modelValue;
+ };
+ ngModelSet = function($scope, newValue) {
+ if (isFunction(parsedNgModel($scope))) {
+ invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
+ } else {
+ parsedNgModelAssign($scope, ctrl.$modelValue);
+ }
+ };
+ } else if (!parsedNgModel.assign) {
+ throw ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
+ $attr.ngModel, startingTag($element));
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$render
+ *
+ * @description
+ * Called when the view needs to be updated. It is expected that the user of the ng-model
+ * directive will implement this method.
+ *
+ * The `$render()` method is invoked in the following situations:
+ *
+ * * `$rollbackViewValue()` is called. If we are rolling back the view value to the last
+ * committed value then `$render()` is called to update the input control.
+ * * The value referenced by `ng-model` is changed programmatically and both the `$modelValue` and
+ * the `$viewValue` are different to last time.
+ *
+ * Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
+ * `$modelValue` and `$viewValue` are actually different to their previous value. If `$modelValue`
+ * or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
+ * invoked if you only change a property on the objects.
+ */
+ this.$render = noop;
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$isEmpty
+ *
+ * @description
+ * This is called when we need to determine if the value of an input is empty.
+ *
+ * For instance, the required directive does this to work out if the input has data or not.
+ *
+ * The default `$isEmpty` function checks whether the value is `undefined`, `''`, `null` or `NaN`.
+ *
+ * You can override this for input directives whose concept of being empty is different to the
+ * default. The `checkboxInputType` directive does this because in its case a value of `false`
+ * implies empty.
+ *
+ * @param {*} value The value of the input to check for emptiness.
+ * @returns {boolean} True if `value` is "empty".
+ */
+ this.$isEmpty = function(value) {
+ return isUndefined(value) || value === '' || value === null || value !== value;
+ };
+
+ var parentForm = $element.inheritedData('$formController') || nullFormCtrl,
+ currentValidationRunId = 0;
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setValidity
+ *
+ * @description
+ * Change the validity state, and notify the form.
+ *
+ * This method can be called within $parsers/$formatters or a custom validation implementation.
+ * However, in most cases it should be sufficient to use the `ngModel.$validators` and
+ * `ngModel.$asyncValidators` collections which will call `$setValidity` automatically.
+ *
+ * @param {string} validationErrorKey Name of the validator. The `validationErrorKey` will be assigned
+ * to either `$error[validationErrorKey]` or `$pending[validationErrorKey]`
+ * (for unfulfilled `$asyncValidators`), so that it is available for data-binding.
+ * The `validationErrorKey` should be in camelCase and will get converted into dash-case
+ * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
+ * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
+ * @param {boolean} isValid Whether the current state is valid (true), invalid (false), pending (undefined),
+ * or skipped (null). Pending is used for unfulfilled `$asyncValidators`.
+ * Skipped is used by Angular when validators do not run because of parse errors and
+ * when `$asyncValidators` do not run because any of the `$validators` failed.
+ */
+ addSetValidityMethod({
+ ctrl: this,
+ $element: $element,
+ set: function(object, property) {
+ object[property] = true;
+ },
+ unset: function(object, property) {
+ delete object[property];
+ },
+ parentForm: parentForm,
+ $animate: $animate
+ });
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setPristine
+ *
+ * @description
+ * Sets the control to its pristine state.
+ *
+ * This method can be called to remove the `ng-dirty` class and set the control to its pristine
+ * state (`ng-pristine` class). A model is considered to be pristine when the control
+ * has not been changed from when first compiled.
+ */
+ this.$setPristine = function() {
+ ctrl.$dirty = false;
+ ctrl.$pristine = true;
+ $animate.removeClass($element, DIRTY_CLASS);
+ $animate.addClass($element, PRISTINE_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setDirty
+ *
+ * @description
+ * Sets the control to its dirty state.
+ *
+ * This method can be called to remove the `ng-pristine` class and set the control to its dirty
+ * state (`ng-dirty` class). A model is considered to be dirty when the control has been changed
+ * from when first compiled.
+ */
+ this.$setDirty = function() {
+ ctrl.$dirty = true;
+ ctrl.$pristine = false;
+ $animate.removeClass($element, PRISTINE_CLASS);
+ $animate.addClass($element, DIRTY_CLASS);
+ parentForm.$setDirty();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setUntouched
+ *
+ * @description
+ * Sets the control to its untouched state.
+ *
+ * This method can be called to remove the `ng-touched` class and set the control to its
+ * untouched state (`ng-untouched` class). Upon compilation, a model is set as untouched
+ * by default, however this function can be used to restore that state if the model has
+ * already been touched by the user.
+ */
+ this.$setUntouched = function() {
+ ctrl.$touched = false;
+ ctrl.$untouched = true;
+ $animate.setClass($element, UNTOUCHED_CLASS, TOUCHED_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setTouched
+ *
+ * @description
+ * Sets the control to its touched state.
+ *
+ * This method can be called to remove the `ng-untouched` class and set the control to its
+ * touched state (`ng-touched` class). A model is considered to be touched when the user has
+ * first focused the control element and then shifted focus away from the control (blur event).
+ */
+ this.$setTouched = function() {
+ ctrl.$touched = true;
+ ctrl.$untouched = false;
+ $animate.setClass($element, TOUCHED_CLASS, UNTOUCHED_CLASS);
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$rollbackViewValue
+ *
+ * @description
+ * Cancel an update and reset the input element's value to prevent an update to the `$modelValue`,
+ * which may be caused by a pending debounced event or because the input is waiting for a some
+ * future event.
+ *
+ * If you have an input that uses `ng-model-options` to set up debounced events or events such
+ * as blur you can have a situation where there is a period when the `$viewValue`
+ * is out of synch with the ngModel's `$modelValue`.
+ *
+ * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
+ * programmatically before these debounced/future events have resolved/occurred, because Angular's
+ * dirty checking mechanism is not able to tell whether the model has actually changed or not.
+ *
+ * The `$rollbackViewValue()` method should be called before programmatically changing the model of an
+ * input which may have such events pending. This is important in order to make sure that the
+ * input field will be updated with the new model value and any pending operations are cancelled.
+ *
+ * <example name="ng-model-cancel-update" module="cancel-update-example">
+ * <file name="app.js">
+ * angular.module('cancel-update-example', [])
+ *
+ * .controller('CancelUpdateController', ['$scope', function($scope) {
+ * $scope.resetWithCancel = function(e) {
+ * if (e.keyCode == 27) {
+ * $scope.myForm.myInput1.$rollbackViewValue();
+ * $scope.myValue = '';
+ * }
+ * };
+ * $scope.resetWithoutCancel = function(e) {
+ * if (e.keyCode == 27) {
+ * $scope.myValue = '';
+ * }
+ * };
+ * }]);
+ * </file>
+ * <file name="index.html">
+ * <div ng-controller="CancelUpdateController">
+ * <p>Try typing something in each input. See that the model only updates when you
+ * blur off the input.
+ * </p>
+ * <p>Now see what happens if you start typing then press the Escape key</p>
+ *
+ * <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
+ * <p>With $rollbackViewValue()</p>
+ * <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/>
+ * myValue: "{{ myValue }}"
+ *
+ * <p>Without $rollbackViewValue()</p>
+ * <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/>
+ * myValue: "{{ myValue }}"
+ * </form>
+ * </div>
+ * </file>
+ * </example>
+ */
+ this.$rollbackViewValue = function() {
+ $timeout.cancel(pendingDebounce);
+ ctrl.$viewValue = ctrl.$$lastCommittedViewValue;
+ ctrl.$render();
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$validate
+ *
+ * @description
+ * Runs each of the registered validators (first synchronous validators and then
+ * asynchronous validators).
+ * If the validity changes to invalid, the model will be set to `undefined`,
+ * unless {@link ngModelOptions `ngModelOptions.allowInvalid`} is `true`.
+ * If the validity changes to valid, it will set the model to the last available valid
+ * `$modelValue`, i.e. either the last parsed value or the last value set from the scope.
+ */
+ this.$validate = function() {
+ // ignore $validate before model is initialized
+ if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
+ return;
+ }
+
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ // Note: we use the $$rawModelValue as $modelValue might have been
+ // set to undefined during a view -> model update that found validation
+ // errors. We can't parse the view here, since that could change
+ // the model although neither viewValue nor the model on the scope changed
+ var modelValue = ctrl.$$rawModelValue;
+
+ var prevValid = ctrl.$valid;
+ var prevModelValue = ctrl.$modelValue;
+
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+
+ ctrl.$$runValidators(modelValue, viewValue, function(allValid) {
+ // If there was no change in validity, don't update the model
+ // This prevents changing an invalid modelValue to undefined
+ if (!allowInvalid && prevValid !== allValid) {
+ // Note: Don't check ctrl.$valid here, as we could have
+ // external validators (e.g. calculated on the server),
+ // that just call $setValidity and need the model value
+ // to calculate their validity.
+ ctrl.$modelValue = allValid ? modelValue : undefined;
+
+ if (ctrl.$modelValue !== prevModelValue) {
+ ctrl.$$writeModelToScope();
+ }
+ }
+ });
+
+ };
+
+ this.$$runValidators = function(modelValue, viewValue, doneCallback) {
+ currentValidationRunId++;
+ var localValidationRunId = currentValidationRunId;
+
+ // check parser error
+ if (!processParseErrors()) {
+ validationDone(false);
+ return;
+ }
+ if (!processSyncValidators()) {
+ validationDone(false);
+ return;
+ }
+ processAsyncValidators();
+
+ function processParseErrors() {
+ var errorKey = ctrl.$$parserName || 'parse';
+ if (parserValid === undefined) {
+ setValidity(errorKey, null);
+ } else {
+ if (!parserValid) {
+ forEach(ctrl.$validators, function(v, name) {
+ setValidity(name, null);
+ });
+ forEach(ctrl.$asyncValidators, function(v, name) {
+ setValidity(name, null);
+ });
+ }
+ // Set the parse error last, to prevent unsetting it, should a $validators key == parserName
+ setValidity(errorKey, parserValid);
+ return parserValid;
+ }
+ return true;
+ }
+
+ function processSyncValidators() {
+ var syncValidatorsValid = true;
+ forEach(ctrl.$validators, function(validator, name) {
+ var result = validator(modelValue, viewValue);
+ syncValidatorsValid = syncValidatorsValid && result;
+ setValidity(name, result);
+ });
+ if (!syncValidatorsValid) {
+ forEach(ctrl.$asyncValidators, function(v, name) {
+ setValidity(name, null);
+ });
+ return false;
+ }
+ return true;
+ }
+
+ function processAsyncValidators() {
+ var validatorPromises = [];
+ var allValid = true;
+ forEach(ctrl.$asyncValidators, function(validator, name) {
+ var promise = validator(modelValue, viewValue);
+ if (!isPromiseLike(promise)) {
+ throw ngModelMinErr("$asyncValidators",
+ "Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
+ }
+ setValidity(name, undefined);
+ validatorPromises.push(promise.then(function() {
+ setValidity(name, true);
+ }, function(error) {
+ allValid = false;
+ setValidity(name, false);
+ }));
+ });
+ if (!validatorPromises.length) {
+ validationDone(true);
+ } else {
+ $q.all(validatorPromises).then(function() {
+ validationDone(allValid);
+ }, noop);
+ }
+ }
+
+ function setValidity(name, isValid) {
+ if (localValidationRunId === currentValidationRunId) {
+ ctrl.$setValidity(name, isValid);
+ }
+ }
+
+ function validationDone(allValid) {
+ if (localValidationRunId === currentValidationRunId) {
+
+ doneCallback(allValid);
+ }
+ }
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$commitViewValue
+ *
+ * @description
+ * Commit a pending update 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.
+ */
+ this.$commitViewValue = function() {
+ var viewValue = ctrl.$viewValue;
+
+ $timeout.cancel(pendingDebounce);
+
+ // If the view value has not changed then we should just exit, except in the case where there is
+ // a native validator on the element. In this case the validation state may have changed even though
+ // the viewValue has stayed empty.
+ if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
+ return;
+ }
+ ctrl.$$lastCommittedViewValue = viewValue;
+
+ // change to dirty
+ if (ctrl.$pristine) {
+ this.$setDirty();
+ }
+ this.$$parseAndValidate();
+ };
+
+ this.$$parseAndValidate = function() {
+ var viewValue = ctrl.$$lastCommittedViewValue;
+ var modelValue = viewValue;
+ parserValid = isUndefined(modelValue) ? undefined : true;
+
+ if (parserValid) {
+ for (var i = 0; i < ctrl.$parsers.length; i++) {
+ modelValue = ctrl.$parsers[i](modelValue);
+ if (isUndefined(modelValue)) {
+ parserValid = false;
+ break;
+ }
+ }
+ }
+ if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
+ // ctrl.$modelValue has not been touched yet...
+ ctrl.$modelValue = ngModelGet($scope);
+ }
+ var prevModelValue = ctrl.$modelValue;
+ var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+ ctrl.$$rawModelValue = modelValue;
+
+ if (allowInvalid) {
+ ctrl.$modelValue = modelValue;
+ writeToModelIfNeeded();
+ }
+
+ // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date.
+ // This can happen if e.g. $setViewValue is called from inside a parser
+ ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) {
+ if (!allowInvalid) {
+ // Note: Don't check ctrl.$valid here, as we could have
+ // external validators (e.g. calculated on the server),
+ // that just call $setValidity and need the model value
+ // to calculate their validity.
+ ctrl.$modelValue = allValid ? modelValue : undefined;
+ writeToModelIfNeeded();
+ }
+ });
+
+ function writeToModelIfNeeded() {
+ if (ctrl.$modelValue !== prevModelValue) {
+ ctrl.$$writeModelToScope();
+ }
+ }
+ };
+
+ this.$$writeModelToScope = function() {
+ ngModelSet($scope, ctrl.$modelValue);
+ forEach(ctrl.$viewChangeListeners, function(listener) {
+ try {
+ listener();
+ } catch (e) {
+ $exceptionHandler(e);
+ }
+ });
+ };
+
+ /**
+ * @ngdoc method
+ * @name ngModel.NgModelController#$setViewValue
+ *
+ * @description
+ * Update the view value.
+ *
+ * This method should be called when an input directive want to change the view value; typically,
+ * this is done from within a DOM event handler.
+ *
+ * For example {@link ng.directive:input input} calls it when the value of the input changes and
+ * {@link ng.directive:select select} calls it when an option is selected.
+ *
+ * If the new `value` is an object (rather than a string or a number), we should make a copy of the
+ * object before passing it to `$setViewValue`. This is because `ngModel` does not perform a deep
+ * watch of objects, it only looks for a change of identity. If you only change the property of
+ * the object then ngModel will not realise that the object has changed and will not invoke the
+ * `$parsers` and `$validators` pipelines.
+ *
+ * For this reason, you should not change properties of the copy once it has been passed to
+ * `$setViewValue`. Otherwise you may cause the model value on the scope to change incorrectly.
+ *
+ * When this method is called, the new `value` will be staged for committing through the `$parsers`
+ * and `$validators` pipelines. If there are no special {@link ngModelOptions} specified then the staged
+ * value sent directly for processing, finally to be applied to `$modelValue` and then the
+ * **expression** specified in the `ng-model` attribute.
+ *
+ * Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
+ *
+ * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
+ * and the `default` trigger is not listed, all those actions will remain pending until one of the
+ * `updateOn` events is triggered on the DOM element.
+ * All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
+ * directive is used with a custom debounce for this particular event.
+ *
+ * Note that calling this function does not trigger a `$digest`.
+ *
+ * @param {string} value Value from the view.
+ * @param {string} trigger Event that triggered the update.
+ */
+ this.$setViewValue = function(value, trigger) {
+ ctrl.$viewValue = value;
+ if (!ctrl.$options || ctrl.$options.updateOnDefault) {
+ ctrl.$$debounceViewValueCommit(trigger);
+ }
+ };
+
+ this.$$debounceViewValueCommit = function(trigger) {
+ var debounceDelay = 0,
+ options = ctrl.$options,
+ debounce;
+
+ if (options && isDefined(options.debounce)) {
+ debounce = options.debounce;
+ if (isNumber(debounce)) {
+ debounceDelay = debounce;
+ } else if (isNumber(debounce[trigger])) {
+ debounceDelay = debounce[trigger];
+ } else if (isNumber(debounce['default'])) {
+ debounceDelay = debounce['default'];
+ }
+ }
+
+ $timeout.cancel(pendingDebounce);
+ if (debounceDelay) {
+ pendingDebounce = $timeout(function() {
+ ctrl.$commitViewValue();
+ }, debounceDelay);
+ } else if ($rootScope.$$phase) {
+ ctrl.$commitViewValue();
+ } else {
+ $scope.$apply(function() {
+ ctrl.$commitViewValue();
+ });
+ }
+ };
+
+ // model -> value
+ // Note: we cannot use a normal scope.$watch as we want to detect the following:
+ // 1. scope value is 'a'
+ // 2. user enters 'b'
+ // 3. ng-change kicks in and reverts scope value to 'a'
+ // -> scope value did not change since the last digest as
+ // ng-change executes in apply phase
+ // 4. view should be changed back to 'a'
+ $scope.$watch(function ngModelWatch() {
+ var modelValue = ngModelGet($scope);
+
+ // if scope model value and ngModel value are out of sync
+ // TODO(perf): why not move this to the action fn?
+ if (modelValue !== ctrl.$modelValue &&
+ // checks for NaN is needed to allow setting the model to NaN when there's an asyncValidator
+ (ctrl.$modelValue === ctrl.$modelValue || modelValue === modelValue)
+ ) {
+ ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
+ parserValid = undefined;
+
+ var formatters = ctrl.$formatters,
+ idx = formatters.length;
+
+ var viewValue = modelValue;
+ while (idx--) {
+ viewValue = formatters[idx](viewValue);
+ }
+ if (ctrl.$viewValue !== viewValue) {
+ ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
+ ctrl.$render();
+
+ ctrl.$$runValidators(modelValue, viewValue, noop);
+ }
+ }
+
+ return modelValue;
+ });
+}];
+
+
+/**
+ * @ngdoc directive
+ * @name ngModel
+ *
+ * @element input
+ * @priority 1
+ *
+ * @description
+ * The `ngModel` directive binds an `input`,`select`, `textarea` (or custom form control) to a
+ * property on the scope using {@link ngModel.NgModelController NgModelController},
+ * which is created and exposed by this directive.
+ *
+ * `ngModel` is responsible for:
+ *
+ * - Binding the view into the model, which other directives such as `input`, `textarea` or `select`
+ * require.
+ * - Providing validation behavior (i.e. required, number, email, url).
+ * - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
+ * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
+ * - Registering the control with its parent {@link ng.directive:form form}.
+ *
+ * Note: `ngModel` will try to bind to the property given by evaluating the expression on the
+ * current scope. If the property doesn't already exist on this scope, it will be created
+ * implicitly and added to the scope.
+ *
+ * For best practices on using `ngModel`, see:
+ *
+ * - [Understanding Scopes](https://github.com/angular/angular.js/wiki/Understanding-Scopes)
+ *
+ * For basic examples, how to use `ngModel`, see:
+ *
+ * - {@link ng.directive:input input}
+ * - {@link input[text] text}
+ * - {@link input[checkbox] checkbox}
+ * - {@link input[radio] radio}
+ * - {@link input[number] number}
+ * - {@link input[email] email}
+ * - {@link input[url] url}
+ * - {@link input[date] date}
+ * - {@link input[datetime-local] datetime-local}
+ * - {@link input[time] time}
+ * - {@link input[month] month}
+ * - {@link input[week] week}
+ * - {@link ng.directive:select select}
+ * - {@link ng.directive:textarea textarea}
+ *
+ * # CSS classes
+ * The following CSS classes are added and removed on the associated input/select/textarea element
+ * depending on the validity of the model.
+ *
+ * - `ng-valid`: the model is valid
+ * - `ng-invalid`: the model is invalid
+ * - `ng-valid-[key]`: for each valid key added by `$setValidity`
+ * - `ng-invalid-[key]`: for each invalid key added by `$setValidity`
+ * - `ng-pristine`: the control hasn't been interacted with yet
+ * - `ng-dirty`: the control has been interacted with
+ * - `ng-touched`: the control has been blurred
+ * - `ng-untouched`: the control hasn't been blurred
+ * - `ng-pending`: any `$asyncValidators` are unfulfilled
+ *
+ * Keep in mind that ngAnimate can detect each of these classes when added and removed.
+ *
+ * ## Animation Hooks
+ *
+ * Animations within models are triggered when any of the associated CSS classes are added and removed
+ * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
+ * `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
+ * The animations that are triggered within ngModel are similar to how they work in ngClass and
+ * animations can be hooked into using CSS transitions, keyframes as well as JS animations.
+ *
+ * The following example shows a simple way to utilize CSS transitions to style an input element
+ * that has been rendered as invalid after it has been validated:
+ *
+ * <pre>
+ * //be sure to include ngAnimate as a module to hook into more
+ * //advanced animations
+ * .my-input {
+ * transition:0.5s linear all;
+ * background: white;
+ * }
+ * .my-input.ng-invalid {
+ * background: red;
+ * color:white;
+ * }
+ * </pre>
+ *
+ * @example
+ * <example deps="angular-animate.js" animations="true" fixBase="true" module="inputExample">
+ <file name="index.html">
+ <script>
+ angular.module('inputExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.val = '1';
+ }]);
+ </script>
+ <style>
+ .my-input {
+ -webkit-transition:all linear 0.5s;
+ transition:all linear 0.5s;
+ background: transparent;
+ }
+ .my-input.ng-invalid {
+ color:white;
+ background: red;
+ }
+ </style>
+ Update input to see transitions when valid/invalid.
+ Integer is a valid value.
+ <form name="testForm" ng-controller="ExampleController">
+ <input ng-model="val" ng-pattern="/^\d+$/" name="anim" class="my-input" />
+ </form>
+ </file>
+ * </example>
+ *
+ * ## Binding to a getter/setter
+ *
+ * Sometimes it's helpful to bind `ngModel` to a getter/setter function. A getter/setter is a
+ * function that returns a representation of the model when called with zero arguments, and sets
+ * the internal state of a model when called with an argument. It's sometimes useful to use this
+ * for models that have an internal representation that's different than what the model exposes
+ * to the view.
+ *
+ * <div class="alert alert-success">
+ * **Best Practice:** It's best to keep getters fast because Angular is likely to call them more
+ * frequently than other parts of your code.
+ * </div>
+ *
+ * You use this behavior by adding `ng-model-options="{ getterSetter: true }"` to an element that
+ * has `ng-model` attached to it. You can also add `ng-model-options="{ getterSetter: true }"` to
+ * a `<form>`, which will enable this behavior for all `<input>`s within it. See
+ * {@link ng.directive:ngModelOptions `ngModelOptions`} for more.
+ *
+ * The following example shows how to use `ngModel` with a getter/setter:
+ *
+ * @example
+ * <example name="ngModel-getter-setter" module="getterSetterExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ getterSetter: true }" />
+ </form>
+ <pre>user.name = <span ng-bind="user.name()"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('getterSetterExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ var _name = 'Brian';
+ $scope.user = {
+ name: function(newName) {
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
+ }
+ };
+ }]);
+ </file>
+ * </example>
+ */
+var ngModelDirective = ['$rootScope', function($rootScope) {
+ return {
+ restrict: 'A',
+ require: ['ngModel', '^?form', '^?ngModelOptions'],
+ controller: NgModelController,
+ // Prelink needs to run before any input directive
+ // so that we can set the NgModelOptions in NgModelController
+ // before anyone else uses it.
+ priority: 1,
+ compile: function ngModelCompile(element) {
+ // Setup initial state of the control
+ element.addClass(PRISTINE_CLASS).addClass(UNTOUCHED_CLASS).addClass(VALID_CLASS);
+
+ return {
+ pre: function ngModelPreLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0],
+ formCtrl = ctrls[1] || nullFormCtrl;
+
+ modelCtrl.$$setOptions(ctrls[2] && ctrls[2].$options);
+
+ // notify others, especially parent forms
+ formCtrl.$addControl(modelCtrl);
+
+ attr.$observe('name', function(newValue) {
+ if (modelCtrl.$name !== newValue) {
+ formCtrl.$$renameControl(modelCtrl, newValue);
+ }
+ });
+
+ scope.$on('$destroy', function() {
+ formCtrl.$removeControl(modelCtrl);
+ });
+ },
+ post: function ngModelPostLink(scope, element, attr, ctrls) {
+ var modelCtrl = ctrls[0];
+ if (modelCtrl.$options && modelCtrl.$options.updateOn) {
+ element.on(modelCtrl.$options.updateOn, function(ev) {
+ modelCtrl.$$debounceViewValueCommit(ev && ev.type);
+ });
+ }
+
+ element.on('blur', function(ev) {
+ if (modelCtrl.$touched) return;
+
+ if ($rootScope.$$phase) {
+ scope.$evalAsync(modelCtrl.$setTouched);
+ } else {
+ scope.$apply(modelCtrl.$setTouched);
+ }
+ });
+ }
+ };
+ }
+ };
+}];
+
+var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
+
+/**
+ * @ngdoc directive
+ * @name ngModelOptions
+ *
+ * @description
+ * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
+ * events that will trigger a model update and/or a debouncing delay so that the actual update only
+ * takes place when a timer expires; this timer will be reset after another change takes place.
+ *
+ * Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
+ * be different than the value in the actual model. This means that if you update the model you
+ * should also invoke {@link ngModel.NgModelController `$rollbackViewValue`} on the relevant input field in
+ * order to make sure it is synchronized with the model and that any debounced action is canceled.
+ *
+ * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue`}
+ * method is by making sure the input is placed inside a form that has a `name` attribute. This is
+ * important because `form` controllers are published to the related scope under the name in their
+ * `name` attribute.
+ *
+ * Any pending changes will take place immediately when an enclosing form is submitted via the
+ * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
+ * to have access to the updated model.
+ *
+ * `ngModelOptions` has an effect on the element it's declared on and its descendants.
+ *
+ * @param {Object} ngModelOptions options to apply to the current model. Valid keys are:
+ * - `updateOn`: string specifying which event should the input be bound to. You can set several
+ * events using an space delimited list. There is a special event called `default` that
+ * matches the default events belonging of the control.
+ * - `debounce`: integer value which contains the debounce model update value in milliseconds. A
+ * value of 0 triggers an immediate update. If an object is supplied instead, you can specify a
+ * custom value for each event. For example:
+ * `ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }"`
+ * - `allowInvalid`: boolean value which indicates that the model can be set with values that did
+ * not validate correctly instead of the default behavior of setting the model to undefined.
+ * - `getterSetter`: boolean value which determines whether or not to treat functions bound to
+ `ngModel` as getters/setters.
+ * - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
+ * `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
+ * otherwise the default timezone of the browser will be used.
+ *
+ * @example
+
+ The following example shows how to override immediate updates. Changes on the inputs within the
+ form will update the model only when the control loses focus (blur event). If `escape` key is
+ pressed while the input field is focused, the value is reset to the value in the current model.
+
+ <example name="ngModelOptions-directive-blur" module="optionsExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ updateOn: 'blur' }"
+ ng-keyup="cancel($event)" /><br />
+
+ Other data:
+ <input type="text" ng-model="user.data" /><br />
+ </form>
+ <pre>user.name = <span ng-bind="user.name"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('optionsExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.user = { name: 'say', data: '' };
+
+ $scope.cancel = function(e) {
+ if (e.keyCode == 27) {
+ $scope.userForm.userName.$rollbackViewValue();
+ }
+ };
+ }]);
+ </file>
+ <file name="protractor.js" type="protractor">
+ var model = element(by.binding('user.name'));
+ var input = element(by.model('user.name'));
+ var other = element(by.model('user.data'));
+
+ it('should allow custom events', function() {
+ input.sendKeys(' hello');
+ input.click();
+ expect(model.getText()).toEqual('say');
+ other.click();
+ expect(model.getText()).toEqual('say hello');
+ });
+
+ it('should $rollbackViewValue when model changes', function() {
+ input.sendKeys(' hello');
+ expect(input.getAttribute('value')).toEqual('say hello');
+ input.sendKeys(protractor.Key.ESCAPE);
+ expect(input.getAttribute('value')).toEqual('say');
+ other.click();
+ expect(model.getText()).toEqual('say');
+ });
+ </file>
+ </example>
+
+ This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
+ If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
+
+ <example name="ngModelOptions-directive-debounce" module="optionsExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ debounce: 1000 }" />
+ <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
+ </form>
+ <pre>user.name = <span ng-bind="user.name"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('optionsExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ $scope.user = { name: 'say' };
+ }]);
+ </file>
+ </example>
+
+ This one shows how to bind to getter/setters:
+
+ <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
+ <file name="index.html">
+ <div ng-controller="ExampleController">
+ <form name="userForm">
+ Name:
+ <input type="text" name="userName"
+ ng-model="user.name"
+ ng-model-options="{ getterSetter: true }" />
+ </form>
+ <pre>user.name = <span ng-bind="user.name()"></span></pre>
+ </div>
+ </file>
+ <file name="app.js">
+ angular.module('getterSetterExample', [])
+ .controller('ExampleController', ['$scope', function($scope) {
+ var _name = 'Brian';
+ $scope.user = {
+ name: function(newName) {
+ // Note that newName can be undefined for two reasons:
+ // 1. Because it is called as a getter and thus called with no arguments
+ // 2. Because the property should actually be set to undefined. This happens e.g. if the
+ // input is invalid
+ return arguments.length ? (_name = newName) : _name;
+ }
+ };
+ }]);
+ </file>
+ </example>
+ */
+var ngModelOptionsDirective = function() {
+ return {
+ restrict: 'A',
+ controller: ['$scope', '$attrs', function($scope, $attrs) {
+ var that = this;
+ this.$options = $scope.$eval($attrs.ngModelOptions);
+ // Allow adding/overriding bound events
+ if (this.$options.updateOn !== undefined) {
+ this.$options.updateOnDefault = false;
+ // extract "default" pseudo-event from list of events that can trigger a model update
+ this.$options.updateOn = trim(this.$options.updateOn.replace(DEFAULT_REGEXP, function() {
+ that.$options.updateOnDefault = true;
+ return ' ';
+ }));
+ } else {
+ this.$options.updateOnDefault = true;
+ }
+ }]
+ };
+};
+
+
+
+// helper methods
+function addSetValidityMethod(context) {
+ var ctrl = context.ctrl,
+ $element = context.$element,
+ classCache = {},
+ set = context.set,
+ unset = context.unset,
+ parentForm = context.parentForm,
+ $animate = context.$animate;
+
+ classCache[INVALID_CLASS] = !(classCache[VALID_CLASS] = $element.hasClass(VALID_CLASS));
+
+ ctrl.$setValidity = setValidity;
+
+ function setValidity(validationErrorKey, state, controller) {
+ if (state === undefined) {
+ createAndSet('$pending', validationErrorKey, controller);
+ } else {
+ unsetAndCleanup('$pending', validationErrorKey, controller);
+ }
+ if (!isBoolean(state)) {
+ unset(ctrl.$error, validationErrorKey, controller);
+ unset(ctrl.$$success, validationErrorKey, controller);
+ } else {
+ if (state) {
+ unset(ctrl.$error, validationErrorKey, controller);
+ set(ctrl.$$success, validationErrorKey, controller);
+ } else {
+ set(ctrl.$error, validationErrorKey, controller);
+ unset(ctrl.$$success, validationErrorKey, controller);
+ }
+ }
+ if (ctrl.$pending) {
+ cachedToggleClass(PENDING_CLASS, true);
+ ctrl.$valid = ctrl.$invalid = undefined;
+ toggleValidationCss('', null);
+ } else {
+ cachedToggleClass(PENDING_CLASS, false);
+ ctrl.$valid = isObjectEmpty(ctrl.$error);
+ ctrl.$invalid = !ctrl.$valid;
+ toggleValidationCss('', ctrl.$valid);
+ }
+
+ // re-read the state as the set/unset methods could have
+ // combined state in ctrl.$error[validationError] (used for forms),
+ // where setting/unsetting only increments/decrements the value,
+ // and does not replace it.
+ var combinedState;
+ if (ctrl.$pending && ctrl.$pending[validationErrorKey]) {
+ combinedState = undefined;
+ } else if (ctrl.$error[validationErrorKey]) {
+ combinedState = false;
+ } else if (ctrl.$$success[validationErrorKey]) {
+ combinedState = true;
+ } else {
+ combinedState = null;
+ }
+
+ toggleValidationCss(validationErrorKey, combinedState);
+ parentForm.$setValidity(validationErrorKey, combinedState, ctrl);
+ }
+
+ function createAndSet(name, value, controller) {
+ if (!ctrl[name]) {
+ ctrl[name] = {};
+ }
+ set(ctrl[name], value, controller);
+ }
+
+ function unsetAndCleanup(name, value, controller) {
+ if (ctrl[name]) {
+ unset(ctrl[name], value, controller);
+ }
+ if (isObjectEmpty(ctrl[name])) {
+ ctrl[name] = undefined;
+ }
+ }
+
+ function cachedToggleClass(className, switchValue) {
+ if (switchValue && !classCache[className]) {
+ $animate.addClass($element, className);
+ classCache[className] = true;
+ } else if (!switchValue && classCache[className]) {
+ $animate.removeClass($element, className);
+ classCache[className] = false;
+ }
+ }
+
+ function toggleValidationCss(validationErrorKey, isValid) {
+ validationErrorKey = validationErrorKey ? '-' + snake_case(validationErrorKey, '-') : '';
+
+ cachedToggleClass(VALID_CLASS + validationErrorKey, isValid === true);
+ cachedToggleClass(INVALID_CLASS + validationErrorKey, isValid === false);
+ }
+}
+
+function isObjectEmpty(obj) {
+ if (obj) {
+ for (var prop in obj) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * @ngdoc directive
* @name ngNonBindable
* @restrict AC
* @priority 1000
@@ -24130,6 +24338,78 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
* Creating aliases for these properties is possible with {@link ng.directive:ngInit `ngInit`}.
* This may be useful when, for instance, nesting ngRepeats.
*
+ * # Iterating over object properties
+ *
+ * It is possible to get `ngRepeat` to iterate over the properties of an object using the following
+ * syntax:
+ *
+ * ```js
+ * <div ng-repeat="(key, value) in myObj"> ... </div>
+ * ```
+ *
+ * You need to be aware that the JavaScript specification does not define what order
+ * it will return the keys for an object. In order to have a guaranteed deterministic order
+ * for the keys, Angular versions up to and including 1.3 **sort the keys alphabetically**.
+ *
+ * If this is not desired, the recommended workaround is to convert your object into an array
+ * that is sorted into the order that you prefer before providing it to `ngRepeat`. You could
+ * do this with a filter such as [toArrayFilter](http://ngmodules.org/modules/angular-toArrayFilter)
+ * or implement a `$watch` on the object yourself.
+ *
+ * In version 1.4 we will remove the sorting, since it seems that browsers generally follow the
+ * strategy of providing keys in the order in which they were defined, although there are exceptions
+ * when keys are deleted and reinstated.
+ *
+ *
+ * # Tracking and Duplicates
+ *
+ * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM:
+ *
+ * * When an item is added, a new instance of the template is added to the DOM.
+ * * When an item is removed, its template instance is removed from the DOM.
+ * * When items are reordered, their respective templates are reordered in the DOM.
+ *
+ * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when
+ * there are duplicates, it is not possible to maintain a one-to-one mapping between collection
+ * items and DOM elements.
+ *
+ * If you do need to repeat duplicate items, you can substitute the default tracking behavior
+ * with your own using the `track by` expression.
+ *
+ * For example, you may track items by the index of each item in the collection, using the
+ * special scope property `$index`:
+ * ```html
+ * <div ng-repeat="n in [42, 42, 43, 43] track by $index">
+ * {{n}}
+ * </div>
+ * ```
+ *
+ * You may use arbitrary expressions in `track by`, including references to custom functions
+ * on the scope:
+ * ```html
+ * <div ng-repeat="n in [42, 42, 43, 43] track by myTrackingFunction(n)">
+ * {{n}}
+ * </div>
+ * ```
+ *
+ * If you are working with objects that have an identifier property, you can track
+ * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat`
+ * will not have to rebuild the DOM elements for items it has already rendered, even if the
+ * JavaScript objects in the collection have been substituted for new ones:
+ * ```html
+ * <div ng-repeat="model in collection track by model.id">
+ * {{model.name}}
+ * </div>
+ * ```
+ *
+ * When no `track by` expression is provided, it is equivalent to tracking by the built-in
+ * `$id` function, which tracks items by their identity:
+ * ```html
+ * <div ng-repeat="obj in collection track by $id(obj)">
+ * {{obj.prop}}
+ * </div>
+ * ```
+ *
* # Special repeat start and end points
* To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending
* the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively.
@@ -24197,12 +24477,12 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
*
* For example: `(name, age) in {'adam':10, 'amalie':12}`.
*
- * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function
- * which can be used to associate the objects in the collection with the DOM elements. If no tracking function
- * is specified the ng-repeat associates elements by identity in the collection. It is an error to have
- * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are
- * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression,
- * before specifying a tracking expression.
+ * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression
+ * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression
+ * is specified, ng-repeat associates elements by identity. It is an error to have
+ * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are
+ * mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be
+ * applied before the tracking expression.
*
* For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements
* will be associated by item identity in the array.
@@ -24374,7 +24654,7 @@ var ngRepeatDirective = ['$parse', '$animate', function($parse, $animate) {
var keyIdentifier = match[2];
if (aliasAs && (!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(aliasAs) ||
- /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent)$/.test(aliasAs))) {
+ /^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(aliasAs))) {
throw ngRepeatMinErr('badident', "alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.",
aliasAs);
}
@@ -24586,10 +24866,11 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
*
* By default, the `.ng-hide` class will style the element with `display: none!important`. If you wish to change
* the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
- * class in CSS:
+ * class CSS. Note that the selector that needs to be used is actually `.ng-hide:not(.ng-hide-animate)` to cope
+ * with extra animation classes that can be added.
*
* ```css
- * .ng-hide {
+ * .ng-hide:not(.ng-hide-animate) {
* /&#42; this is just another form of hiding an element &#42;/
* display: block!important;
* position: absolute;
@@ -24978,7 +25259,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
*
* @scope
* @priority 1200
- * @param {*} ngSwitch|on expression to match against <tt>ng-switch-when</tt>.
+ * @param {*} ngSwitch|on expression to match against <code>ng-switch-when</code>.
* On child elements add:
*
* * `ngSwitchWhen`: the case statement to match against. If match then this
@@ -24995,7 +25276,7 @@ var ngStyleDirective = ngDirective(function(scope, element, attr) {
<div ng-controller="ExampleController">
<select ng-model="selection" ng-options="item for item in items">
</select>
- <tt>selection={{selection}}</tt>
+ <code>selection={{selection}}</code>
<hr/>
<div class="animate-switch-container"
ng-switch on="selection">
@@ -25273,14 +25554,15 @@ var ngOptionsMinErr = minErr('ngOptions');
*
* The `ngOptions` attribute can be used to dynamically generate a list of `<option>`
* elements for the `<select>` element using the array or object obtained by evaluating the
- * `ngOptions` comprehension_expression.
+ * `ngOptions` comprehension expression.
*
* In many cases, `ngRepeat` can be used on `<option>` elements instead of `ngOptions` to achieve a
- * similar result. However, the `ngOptions` provides some benefits such as reducing memory and
+ * similar result. However, `ngOptions` provides some benefits such as reducing memory and
* increasing speed by not creating a new scope for each repeated instance, as well as providing
- * more flexibility in how the `select`'s model is assigned via `select as`. `ngOptions` should be
- * used when the `select` model needs to be bound to a non-string value. This is because an option
- * element can only be bound to string values at present.
+ * more flexibility in how the `<select>`'s model is assigned via the `select` **`as`** part of the
+ * comprehension expression. `ngOptions` should be used when the `<select>` model needs to be bound
+ * to a non-string value. This is because an option element can only be bound to string values at
+ * present.
*
* When an item in the `<select>` menu is selected, the array element or object property
* represented by the selected option will be bound to the model identified by the `ngModel`
@@ -25295,28 +25577,51 @@ var ngOptionsMinErr = minErr('ngOptions');
* array of objects. See an example [in this jsfiddle](http://jsfiddle.net/qWzTb/).
* </div>
*
- * ## `select as`
+ * ## `select` **`as`**
*
- * Using `select as` will bind the result of the `select as` expression to the model, but
+ * Using `select` **`as`** will bind the result of the `select` expression to the model, but
* the value of the `<select>` and `<option>` html elements will be either the index (for array data sources)
- * or property name (for object data sources) of the value within the collection. If a `track by` expression
+ * or property name (for object data sources) of the value within the collection. If a **`track by`** expression
* is used, the result of that expression will be set as the value of the `option` and `select` elements.
*
- * ### `select as` with `track by`
*
- * Using `select as` together with `track by` is not recommended. Reasoning:
+ * ### `select` **`as`** and **`track by`**
+ *
+ * <div class="alert alert-warning">
+ * Do not use `select` **`as`** and **`track by`** in the same expression. They are not designed to work together.
+ * </div>
+ *
+ * Consider the following example:
+ *
+ * ```html
+ * <select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected">
+ * ```
+ *
+ * ```js
+ * $scope.values = [{
+ * id: 1,
+ * label: 'aLabel',
+ * subItem: { name: 'aSubItem' }
+ * }, {
+ * id: 2,
+ * label: 'bLabel',
+ * subItem: { name: 'bSubItem' }
+ * }];
+ *
+ * $scope.selected = { name: 'aSubItem' };
+ * ```
+ *
+ * With the purpose of preserving the selection, the **`track by`** expression is always applied to the element
+ * of the data source (to `item` in this example). To calculate whether an element is selected, we do the
+ * following:
+ *
+ * 1. Apply **`track by`** to the elements in the array. In the example: `[1, 2]`
+ * 2. Apply **`track by`** to the already selected value in `ngModel`.
+ * In the example: this is not possible as **`track by`** refers to `item.id`, but the selected
+ * value from `ngModel` is `{name: 'aSubItem'}`, so the **`track by`** expression is applied to
+ * a wrong object, the selected element can't be found, `<select>` is always reset to the "not
+ * selected" option.
*
- * - Example: &lt;select ng-options="item.subItem as item.label for item in values track by item.id" ng-model="selected"&gt;
- * values: [{id: 1, label: 'aLabel', subItem: {name: 'aSubItem'}}, {id: 2, label: 'bLabel', subItem: {name: 'bSubItem'}}],
- * $scope.selected = {name: 'aSubItem'};
- * - track by is always applied to `value`, with the purpose of preserving the selection,
- * (to `item` in this case)
- * - to calculate whether an item is selected we do the following:
- * 1. apply `track by` to the values in the array, e.g.
- * In the example: [1,2]
- * 2. apply `track by` to the already selected value in `ngModel`:
- * In the example: this is not possible, as `track by` refers to `item.id`, but the selected
- * value from `ngModel` is `{name: aSubItem}`.
*
* @param {string} ngModel Assignable angular expression to data-bind to.
* @param {string=} name Property name of the form under which the control is published.
@@ -25552,7 +25857,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
selectElement.val(viewValue);
if (viewValue === '') emptyOption.prop('selected', true); // to make IE9 happy
} else {
- if (isUndefined(viewValue) && emptyOption) {
+ if (viewValue == null && emptyOption) {
selectElement.val('');
} else {
selectCtrl.renderUnknownOption(viewValue);
@@ -26016,6 +26321,96 @@ var styleDirective = valueFn({
terminal: false
});
+var requiredDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+ attr.required = true; // force truthy in case we are on non input element
+
+ ctrl.$validators.required = function(modelValue, viewValue) {
+ return !attr.required || !ctrl.$isEmpty(viewValue);
+ };
+
+ attr.$observe('required', function() {
+ ctrl.$validate();
+ });
+ }
+ };
+};
+
+
+var patternDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var regexp, patternExp = attr.ngPattern || attr.pattern;
+ attr.$observe('pattern', function(regex) {
+ if (isString(regex) && regex.length > 0) {
+ regex = new RegExp('^' + regex + '$');
+ }
+
+ if (regex && !regex.test) {
+ throw minErr('ngPattern')('noregexp',
+ 'Expected {0} to be a RegExp but was {1}. Element: {2}', patternExp,
+ regex, startingTag(elm));
+ }
+
+ regexp = regex || undefined;
+ ctrl.$validate();
+ });
+
+ ctrl.$validators.pattern = function(value) {
+ return ctrl.$isEmpty(value) || isUndefined(regexp) || regexp.test(value);
+ };
+ }
+ };
+};
+
+
+var maxlengthDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var maxlength = -1;
+ attr.$observe('maxlength', function(value) {
+ var intVal = int(value);
+ maxlength = isNaN(intVal) ? -1 : intVal;
+ ctrl.$validate();
+ });
+ ctrl.$validators.maxlength = function(modelValue, viewValue) {
+ return (maxlength < 0) || ctrl.$isEmpty(viewValue) || (viewValue.length <= maxlength);
+ };
+ }
+ };
+};
+
+var minlengthDirective = function() {
+ return {
+ restrict: 'A',
+ require: '?ngModel',
+ link: function(scope, elm, attr, ctrl) {
+ if (!ctrl) return;
+
+ var minlength = 0;
+ attr.$observe('minlength', function(value) {
+ minlength = int(value) || 0;
+ ctrl.$validate();
+ });
+ ctrl.$validators.minlength = function(modelValue, viewValue) {
+ return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
+ };
+ }
+ };
+};
+
if (window.angular.bootstrap) {
//AngularJS is already loaded, so we can return here...
console.log('WARNING: Tried to load angular more than once.');
@@ -26034,4 +26429,4 @@ var styleDirective = valueFn({
})(window, document);
-!window.angular.$$csp() && window.angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}</style>'); \ No newline at end of file
+!window.angular.$$csp() && window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}</style>'); \ No newline at end of file
diff --git a/xstatic/pkg/angular/data/errors.json b/xstatic/pkg/angular/data/errors.json
index 3f10e70..16dc001 100644
--- a/xstatic/pkg/angular/data/errors.json
+++ b/xstatic/pkg/angular/data/errors.json
@@ -1 +1 @@
-{"id":"ng","generated":"Mon Dec 15 2014 13:55:17 GMT-0800 (PST)","errors":{"$http":{"badreq":"Http request configuration must be an object. Received: {0}"},"ngRepeat":{"badident":"alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.","iexp":"Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.","dupes":"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}","iidexp":"'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'."},"$sce":{"imatcher":"Matchers may only be \"self\", string patterns or RegExp objects","icontext":"Attempted to trust a value in invalid context. Context: {0}; Value: {1}","iwcard":"Illegal sequence *** in string matcher. String: {0}","insecurl":"Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}","iequirks":"Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks mode. You can fix this by adding the text <!doctype html> to the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more information.","unsafe":"Attempting to use an unsafe value in a safe context.","itype":"Attempted to trust a non-string value in a content requiring a string: Context: {0}"},"ngPattern":{"noregexp":"Expected {0} to be a RegExp but was {1}. Element: {2}"},"$controller":{"noscp":"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`."},"$ngModel":{"nonassign":"Expression '{0}' is non-assignable. Element: {1}","datefmt":"Expected `{0}` to be a date","$asyncValidators":"Expected asynchronous validator to return a promise but got '{0}' instead.","numfmt":"Expected `{0}` to be a number"},"$parse":{"isecfn":"Referencing Function in Angular expressions is disallowed! Expression: {0}","isecwindow":"Referencing the Window in Angular expressions is disallowed! Expression: {0}","ueoe":"Unexpected end of expression: {0}","isecdom":"Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}","lexerr":"Lexer Error: {0} at column{1} in expression [{2}].","isecobj":"Referencing Object in Angular expressions is disallowed! Expression: {0}","isecff":"Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}","syntax":"Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].","isecfld":"Attempting to access a disallowed field in Angular expressions! Expression: {0}"},"jqLite":{"offargs":"jqLite#off() does not support the `selector` argument","onargs":"jqLite#on() does not support the `selector` or `eventData` parameters","nosel":"Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element"},"$animate":{"notcsel":"Expecting class selector starting with '.' got '{0}'."},"$q":{"norslvr":"Expected resolverFn, got '{0}'","qcycle":"Expected promise to be resolved with value other than itself '{0}'"},"$injector":{"pget":"Provider '{0}' must define $get factory method.","cdep":"Circular dependency found: {0}","nomod":"Module '{0}' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.","strictdi":"{0} is not using explicit annotation and cannot be invoked in strict mode","modulerr":"Failed to instantiate module {0} due to:\n{1}","undef":"Provider '{0}' must return a value from $get factory method.","unpr":"Unknown provider: {0}","itkn":"Incorrect injection token! Expected service name as string, got {0}"},"ngTransclude":{"orphan":"Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: {0}"},"ngModel":{"constexpr":"Expected constant expression for `{0}`, but saw `{1}`."},"$location":{"nostate":"History API state support is available only in HTML5 mode and only in browsers supporting HTML5 History API","ipthprfx":"Invalid url \"{0}\", missing path prefix \"{1}\".","isrcharg":"The first argument of the `$location#search()` call must be a string or an object.","nobase":"$location in HTML5 mode requires a <base> tag to be present!"},"ng":{"areq":"Argument '{0}' is {1}","test":"no injector found for element argument to getTestability","cpws":"Can't copy! Making copies of Window or Scope instances is not supported.","btstrpd":"App Already Bootstrapped with this Element '{0}'","cpi":"Can't copy! Source and destination are identical.","badname":"hasOwnProperty is not a valid {0} name"},"$cacheFactory":{"iid":"CacheId '{0}' is already taken!"},"$interpolate":{"noconcat":"Error while interpolating: {0}\nStrict Contextual Escaping disallows interpolations that concatenate multiple expressions when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce","interr":"Can't interpolate: {0}\n{1}"},"ngOptions":{"iexp":"Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}"},"$rootScope":{"inprog":"{0} already in progress","infdig":"{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}"},"$compile":{"selmulti":"Binding to the 'multiple' attribute is not supported. Element: {0}","nodomevents":"Interpolations for HTML DOM event attributes are disallowed. Please use the ng- versions (such as ng-click instead of onclick) instead.","ctreq":"Controller '{0}', required by directive '{1}', can't be found!","nonassign":"Expression '{0}' used with directive '{1}' is non-assignable!","tplrt":"Template for directive '{0}' must have exactly one root element. {1}","iscp":"Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}","multidir":"Multiple directives [{0}, {1}] asking for {2} on: {3}","tpload":"Failed to load template: {0}","uterdir":"Unterminated attribute, found '{0}' but no matching '{1}' found."},"$resource":{"badargs":"Expected up to 4 arguments [params, data, success, error], got {0} arguments","badmember":"Dotted member path \"@{0}\" is invalid.","badname":"hasOwnProperty is not a valid parameter name.","badcfg":"Error in resource configuration for action `{0}`. Expected response to contain an {1} but got an {2}"},"$route":{"norout":"Tried updating route when with no current route"},"$sanitize":{"badparse":"The sanitizer was unable to parse the following block of html: {0}"}}} \ No newline at end of file
+{"id":"ng","generated":"Tue Aug 18 2015 15:31:50 GMT-0700 (PDT)","errors":{"$http":{"badreq":"Http request configuration must be an object. Received: {0}"},"ngRepeat":{"badident":"alias '{0}' is invalid --- must be a valid JS identifier which is not a reserved name.","iexp":"Expected expression in form of '_item_ in _collection_[ track by _id_]' but got '{0}'.","dupes":"Duplicates in a repeater are not allowed. Use 'track by' expression to specify unique keys. Repeater: {0}, Duplicate key: {1}, Duplicate value: {2}","iidexp":"'_item_' in '_item_ in _collection_' should be an identifier or '(_key_, _value_)' expression, but got '{0}'."},"$sce":{"imatcher":"Matchers may only be \"self\", string patterns or RegExp objects","icontext":"Attempted to trust a value in invalid context. Context: {0}; Value: {1}","iwcard":"Illegal sequence *** in string matcher. String: {0}","insecurl":"Blocked loading resource from url not allowed by $sceDelegate policy. URL: {0}","iequirks":"Strict Contextual Escaping does not support Internet Explorer version < 11 in quirks mode. You can fix this by adding the text <!doctype html> to the top of your HTML document. See http://docs.angularjs.org/api/ng.$sce for more information.","unsafe":"Attempting to use an unsafe value in a safe context.","itype":"Attempted to trust a non-string value in a content requiring a string: Context: {0}"},"ngPattern":{"noregexp":"Expected {0} to be a RegExp but was {1}. Element: {2}"},"$controller":{"ctrlfmt":"Badly formed controller string '{0}'. Must match `__name__ as __id__` or `__name__`.","noscp":"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`."},"$parse":{"isecfn":"Referencing Function in Angular expressions is disallowed! Expression: {0}","isecwindow":"Referencing the Window in Angular expressions is disallowed! Expression: {0}","ueoe":"Unexpected end of expression: {0}","isecdom":"Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}","lexerr":"Lexer Error: {0} at column{1} in expression [{2}].","isecobj":"Referencing Object in Angular expressions is disallowed! Expression: {0}","isecff":"Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}","syntax":"Syntax Error: Token '{0}' {1} at column {2} of the expression [{3}] starting at [{4}].","isecfld":"Attempting to access a disallowed field in Angular expressions! Expression: {0}"},"jqLite":{"offargs":"jqLite#off() does not support the `selector` argument","onargs":"jqLite#on() does not support the `selector` or `eventData` parameters","nosel":"Looking up elements via selectors is not supported by jqLite! See: http://docs.angularjs.org/api/angular.element"},"$animate":{"notcsel":"Expecting class selector starting with '.' got '{0}'."},"$q":{"norslvr":"Expected resolverFn, got '{0}'","qcycle":"Expected promise to be resolved with value other than itself '{0}'"},"$injector":{"pget":"Provider '{0}' must define $get factory method.","cdep":"Circular dependency found: {0}","nomod":"Module '{0}' is not available! You either misspelled the module name or forgot to load it. If registering a module ensure that you specify the dependencies as the second argument.","strictdi":"{0} is not using explicit annotation and cannot be invoked in strict mode","modulerr":"Failed to instantiate module {0} due to:\n{1}","undef":"Provider '{0}' must return a value from $get factory method.","unpr":"Unknown provider: {0}","itkn":"Incorrect injection token! Expected service name as string, got {0}"},"ngTransclude":{"orphan":"Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: {0}"},"ngModel":{"nonassign":"Expression '{0}' is non-assignable. Element: {1}","datefmt":"Expected `{0}` to be a date","$asyncValidators":"Expected asynchronous validator to return a promise but got '{0}' instead.","constexpr":"Expected constant expression for `{0}`, but saw `{1}`.","numfmt":"Expected `{0}` to be a number"},"$location":{"nostate":"History API state support is available only in HTML5 mode and only in browsers supporting HTML5 History API","ipthprfx":"Invalid url \"{0}\", missing path prefix \"{1}\".","isrcharg":"The first argument of the `$location#search()` call must be a string or an object.","nobase":"$location in HTML5 mode requires a <base> tag to be present!"},"ng":{"areq":"Argument '{0}' is {1}","test":"no injector found for element argument to getTestability","cpws":"Can't copy! Making copies of Window or Scope instances is not supported.","btstrpd":"App Already Bootstrapped with this Element '{0}'","cpi":"Can't copy! Source and destination are identical.","badname":"hasOwnProperty is not a valid {0} name"},"$cacheFactory":{"iid":"CacheId '{0}' is already taken!"},"$interpolate":{"noconcat":"Error while interpolating: {0}\nStrict Contextual Escaping disallows interpolations that concatenate multiple expressions when a trusted value is required. See http://docs.angularjs.org/api/ng.$sce","interr":"Can't interpolate: {0}\n{1}"},"ngOptions":{"iexp":"Expected expression in form of '_select_ (as _label_)? for (_key_,)?_value_ in _collection_' but got '{0}'. Element: {1}"},"$rootScope":{"inprog":"{0} already in progress","infdig":"{0} $digest() iterations reached. Aborting!\nWatchers fired in the last 5 iterations: {1}"},"$compile":{"selmulti":"Binding to the 'multiple' attribute is not supported. Element: {0}","nodomevents":"Interpolations for HTML DOM event attributes are disallowed. Please use the ng- versions (such as ng-click instead of onclick) instead.","ctreq":"Controller '{0}', required by directive '{1}', can't be found!","nonassign":"Expression '{0}' used with directive '{1}' is non-assignable!","tplrt":"Template for directive '{0}' must have exactly one root element. {1}","iscp":"Invalid isolate scope definition for directive '{0}'. Definition: {... {1}: '{2}' ...}","baddir":"Directive name '{0}' is invalid. The first character must be a lowercase letter","multidir":"Multiple directives [{0}, {1}] asking for {2} on: {3}","tpload":"Failed to load template: {0}","uterdir":"Unterminated attribute, found '{0}' but no matching '{1}' found."},"$resource":{"badargs":"Expected up to 4 arguments [params, data, success, error], got {0} arguments","badmember":"Dotted member path \"@{0}\" is invalid.","badname":"hasOwnProperty is not a valid parameter name.","badcfg":"Error in resource configuration for action `{0}`. Expected response to contain an {1} but got an {2}"},"$route":{"norout":"Tried updating route when with no current route"},"$sanitize":{"badparse":"The sanitizer was unable to parse the following block of html: {0}"}}} \ No newline at end of file
diff --git a/xstatic/pkg/angular/data/version.json b/xstatic/pkg/angular/data/version.json
index f9c7b57..3294b3f 100644
--- a/xstatic/pkg/angular/data/version.json
+++ b/xstatic/pkg/angular/data/version.json
@@ -1 +1 @@
-{"raw":"v1.3.7","major":1,"minor":3,"patch":7,"prerelease":[],"build":[],"version":"1.3.7","codeName":"leaky-obstruction","full":"1.3.7","branch":"v1.3.x","cdn":{"raw":"v1.3.6","major":1,"minor":3,"patch":6,"prerelease":[],"build":[],"version":"1.3.6","docsUrl":"http://code.angularjs.org/1.3.6/docs"}} \ No newline at end of file
+{"raw":"v1.3.18","major":1,"minor":3,"patch":18,"prerelease":[],"build":[],"version":"1.3.18","codeName":"collective-penmanship","full":"1.3.18","branch":"v1.3.x","cdn":{"raw":"v1.3.17","major":1,"minor":3,"patch":17,"prerelease":[],"build":[],"version":"1.3.17","docsUrl":"http://code.angularjs.org/1.3.17/docs"}} \ No newline at end of file
diff --git a/xstatic/pkg/angular/data/version.txt b/xstatic/pkg/angular/data/version.txt
index 8ed486a..672b322 100644
--- a/xstatic/pkg/angular/data/version.txt
+++ b/xstatic/pkg/angular/data/version.txt
@@ -1 +1 @@
-1.3.7 \ No newline at end of file
+1.3.18 \ No newline at end of file