summaryrefslogtreecommitdiffstats
path: root/js/vendor/angular/angular.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/vendor/angular/angular.js')
-rw-r--r--js/vendor/angular/angular.js544
1 files changed, 354 insertions, 190 deletions
diff --git a/js/vendor/angular/angular.js b/js/vendor/angular/angular.js
index 57da51b3d..d76150b0e 100644
--- a/js/vendor/angular/angular.js
+++ b/js/vendor/angular/angular.js
@@ -1,5 +1,5 @@
/**
- * @license AngularJS v1.3.3
+ * @license AngularJS v1.3.4
* (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.3/' +
+ message = message + '\nhttp://errors.angularjs.org/1.3.4/' +
(module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
@@ -426,7 +426,7 @@ function int(str) {
function inherit(parent, extra) {
- return extend(new (extend(function() {}, {prototype:parent}))(), extra);
+ return extend(Object.create(parent), extra);
}
/**
@@ -689,7 +689,7 @@ function makeMap(str) {
function nodeName_(element) {
- return lowercase(element.nodeName || element[0].nodeName);
+ return lowercase(element.nodeName || (element[0] && element[0].nodeName));
}
function includes(array, obj) {
@@ -1395,8 +1395,8 @@ function angularInit(element, bootstrap) {
* @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported:
*
- * - `strictDi`: disable automatic function annotation for the application. This is meant to
- * assist in finding bugs which break minified code.
+ * * `strictDi` - disable automatic function annotation for the application. This is meant to
+ * assist in finding bugs which break minified code. Defaults to `false`.
*
* @returns {auto.$injector} Returns the newly created injector for this app.
*/
@@ -2100,11 +2100,11 @@ function toDebugString(obj) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/
var version = {
- full: '1.3.3', // all of these placeholder strings will be replaced by grunt's
+ full: '1.3.4', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task
minor: 3,
- dot: 3,
- codeName: 'undersea-arithmetic'
+ dot: 4,
+ codeName: 'highfalutin-petroglyph'
};
@@ -2327,10 +2327,12 @@ function publishExternalAPI(angular) {
* `'ngModel'`).
* - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current
- * element or its parent.
+ * element or its parent. Requires {@link guide/production#disabling-debug-data Debug Data} to
+ * be enabled.
* - `isolateScope()` - retrieves an isolate {@link ng.$rootScope.Scope scope} if one is attached directly to the
* current element. This getter should be used only on elements that contain a directive which starts a new isolate
* scope. Calling `scope()` on this element always returns the original non-isolate scope.
+ * Requires {@link guide/production#disabling-debug-data Debug Data} to be enabled.
* - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached.
*
@@ -3325,9 +3327,10 @@ HashMap.prototype = {
* Creates an injector object that can be used for retrieving services as well as for
* dependency injection (see {@link guide/di dependency injection}).
*
-
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See
- * {@link angular.module}. The `ng` module must be explicitly added.
+ * {@link angular.module}. The `ng` module must be explicitly added.
+ * @param {boolean=} [strictDi=false] Whether the injector should be in strict mode, which
+ * disallows argument name annotation inference.
* @returns {injector} Injector object. See {@link auto.$injector $injector}.
*
* @example
@@ -3473,8 +3476,10 @@ function annotate(fn, strictDi, name) {
* ## Inference
*
* In JavaScript calling `toString()` on a function returns the function definition. The definition
- * can then be parsed and the function arguments can be extracted. *NOTE:* This does not work with
- * minification, and obfuscation tools since these tools change the argument names.
+ * can then be parsed and the function arguments can be extracted. This method of discovering
+ * annotations is disallowed when the injector is in strict mode.
+ * *NOTE:* This does not work with minification, and obfuscation tools since these tools change the
+ * argument names.
*
* ## `$inject` Annotation
* By adding an `$inject` property onto a function the injection parameters can be specified.
@@ -3559,6 +3564,8 @@ function annotate(fn, strictDi, name) {
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']);
* ```
*
+ * You can disallow this method by using strict injection mode.
+ *
* This method does not work with code minification / obfuscation. For this reason the following
* annotation strategies are supported.
*
@@ -3611,6 +3618,8 @@ function annotate(fn, strictDi, name) {
* @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
* be retrieved as described above.
*
+ * @param {boolean=} [strictDi=false] Disallow argument name annotation inference.
+ *
* @returns {Array.<string>} The names of the services which the function requires.
*/
@@ -4130,14 +4139,11 @@ function createInjector(modulesToLoad, strictDi) {
}
function instantiate(Type, locals, serviceName) {
- var Constructor = function() {},
- instance, returnedValue;
-
// 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) {}]);
- Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype;
- instance = new Constructor();
- returnedValue = invoke(Type, instance, locals, serviceName);
+ // Object creation: http://jsperf.com/create-constructor/2
+ var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
+ var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
}
@@ -4973,7 +4979,7 @@ function Browser(window, document, $log, $sniffer) {
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
// See https://github.com/angular/angular.js/commit/ffb2701
if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
- return;
+ return self;
}
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
@@ -7885,10 +7891,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var childBoundTranscludeFn = boundTranscludeFn;
if (scope.$$destroyed) return;
if (linkQueue) {
- linkQueue.push(scope);
- linkQueue.push(node);
- linkQueue.push(rootElement);
- linkQueue.push(childBoundTranscludeFn);
+ linkQueue.push(scope,
+ node,
+ rootElement,
+ childBoundTranscludeFn);
} else {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
@@ -8357,10 +8363,10 @@ function $ControllerProvider() {
//
// This feature is not intended for use by applications, and is thus not documented
// publicly.
- var Constructor = function() {};
- Constructor.prototype = (isArray(expression) ?
+ // Object creation: http://jsperf.com/create-constructor/2
+ var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype;
- instance = new Constructor();
+ instance = Object.create(controllerPrototype);
if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name);
@@ -8501,7 +8507,7 @@ function defaultHttpResponseTransform(data, headers) {
* @returns {Object} Parsed headers as key value object
*/
function parseHeaders(headers) {
- var parsed = {}, key, val, i;
+ var parsed = createMap(), key, val, i;
if (!headers) return parsed;
@@ -8538,7 +8544,11 @@ function headersGetter(headers) {
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
- return headersObj[lowercase(name)] || null;
+ var value = headersObj[lowercase(name)];
+ if (value === void 0) {
+ value = null;
+ }
+ return value;
}
return headersObj;
@@ -8587,6 +8597,11 @@ function $HttpProvider() {
*
* Object containing default values for all {@link ng.$http $http} requests.
*
+ * - **`defaults.cache`** - {Object} - an object built with {@link ng.$cacheFactory `$cacheFactory`}
+ * that will provide the cache for all requests who set their `cache` property to `true`.
+ * If you set the `default.cache = false` then only requests that specify their own custom
+ * cache object will be cached. See {@link $http#caching $http Caching} for more information.
+ *
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`.
*
@@ -8600,6 +8615,7 @@ function $HttpProvider() {
* - **`defaults.headers.post`**
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
+ *
**/
var defaults = this.defaults = {
// transform incoming response data
@@ -8814,6 +8830,21 @@ function $HttpProvider() {
* In addition, you can supply a `headers` property in the config object passed when
* calling `$http(config)`, which overrides the defaults without changing them globally.
*
+ * To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
+ * Use the `headers` property, setting the desired header to `undefined`. For example:
+ *
+ * ```js
+ * var req = {
+ * method: 'POST',
+ * url: 'http://example.com',
+ * headers: {
+ * 'Content-Type': undefined
+ * },
+ * data: { test: 'test' },
+ * }
+ *
+ * $http(req).success(function(){...}).error(function(){...});
+ * ```
*
* ## Transforming Requests and Responses
*
@@ -9193,6 +9224,10 @@ function $HttpProvider() {
};
var headers = mergeHeaders(requestConfig);
+ if (!angular.isObject(requestConfig)) {
+ throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
+ }
+
extend(config, requestConfig);
config.headers = headers;
config.method = uppercase(config.method);
@@ -10687,6 +10722,13 @@ var locationPrototype = {
* Return full url representation with all segments encoded according to rules specified in
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt).
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var absUrl = $location.absUrl();
+ * // => "http://example.com/#/some/path?foo=bar&baz=xoxo"
+ * ```
+ *
* @return {string} full url
*/
absUrl: locationGetter('$$absUrl'),
@@ -10702,6 +10744,13 @@ var locationPrototype = {
*
* Change path, search and hash, when called with parameter and return `$location`.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var url = $location.url();
+ * // => "/some/path?foo=bar&baz=xoxo"
+ * ```
+ *
* @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
* @return {string} url
*/
@@ -10710,8 +10759,8 @@ var locationPrototype = {
return this.$$url;
var match = PATH_MATCH.exec(url);
- if (match[1]) this.path(decodeURIComponent(match[1]));
- if (match[2] || match[1]) this.search(match[3] || '');
+ if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
+ if (match[2] || match[1] || url === '') this.search(match[3] || '');
this.hash(match[5] || '');
return this;
@@ -10726,6 +10775,13 @@ var locationPrototype = {
*
* Return protocol of current url.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var protocol = $location.protocol();
+ * // => "http"
+ * ```
+ *
* @return {string} protocol of current url
*/
protocol: locationGetter('$$protocol'),
@@ -10739,6 +10795,13 @@ var locationPrototype = {
*
* Return host of current url.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var host = $location.host();
+ * // => "example.com"
+ * ```
+ *
* @return {string} host of current url.
*/
host: locationGetter('$$host'),
@@ -10752,6 +10815,13 @@ var locationPrototype = {
*
* Return port of current url.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var port = $location.port();
+ * // => 80
+ * ```
+ *
* @return {Number} port
*/
port: locationGetter('$$port'),
@@ -10770,6 +10840,13 @@ var locationPrototype = {
* Note: Path should always begin with forward slash (/), this method will add the forward slash
* if it is missing.
*
+ *
+ * ```js
+ * // given url http://example.com/#/some/path?foo=bar&baz=xoxo
+ * var path = $location.path();
+ * // => "/some/path"
+ * ```
+ *
* @param {(string|number)=} path New path
* @return {string} path
*/
@@ -10795,10 +10872,9 @@ var locationPrototype = {
* var searchObject = $location.search();
* // => {foo: 'bar', baz: 'xoxo'}
*
- *
* // set foo to 'yipee'
* $location.search('foo', 'yipee');
- * // => $location
+ * // $location.search() => {foo: 'yipee', baz: 'xoxo'}
* ```
*
* @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
@@ -10868,6 +10944,13 @@ var locationPrototype = {
*
* Change hash fragment when called with parameter and return `$location`.
*
+ *
+ * ```js
+ * // given url http://example.com/some/path?foo=bar&baz=xoxo#hashValue
+ * var hash = $location.hash();
+ * // => "hashValue"
+ * ```
+ *
* @param {(string|number)=} hash New hash fragment
* @return {string} hash
*/
@@ -16599,7 +16682,7 @@ function filterFilter() {
*
* @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed.
- * @param {number=} fractionSize Number of decimal places to round the amount to.
+ * @param {number=} fractionSize Number of decimal places to round the amount to, defaults to default max fraction size for current locale
* @returns {string} Formatted number.
*
*
@@ -16649,8 +16732,7 @@ function currencyFilter($locale) {
}
if (isUndefined(fractionSize)) {
- // TODO: read the default value from the locale file
- fractionSize = 2;
+ fractionSize = formats.PATTERNS[1].maxFrac;
}
// if null or undefined pass it through
@@ -16802,9 +16884,9 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
}
}
- parts.push(isNegative ? pattern.negPre : pattern.posPre);
- parts.push(formatedText);
- parts.push(isNegative ? pattern.negSuf : pattern.posSuf);
+ parts.push(isNegative ? pattern.negPre : pattern.posPre,
+ formatedText,
+ isNegative ? pattern.negSuf : pattern.posSuf);
return parts.join('');
}
@@ -18384,9 +18466,7 @@ var formDirectiveFactory = function(isNgForm) {
controller.$setSubmitted();
});
- event.preventDefault
- ? event.preventDefault()
- : event.returnValue = false; // IE
+ event.preventDefault();
};
addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
@@ -18473,7 +18553,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
@@ -19021,7 +19102,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
@@ -19108,7 +19190,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
@@ -19196,7 +19279,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of
+ * any length.
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
@@ -19467,7 +19551,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
element.on('change', listener);
ctrl.$render = function() {
- element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue);
+ element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
};
}
@@ -19577,10 +19661,10 @@ function createDateInputType(type, regexp, parseDate, format) {
});
ctrl.$formatters.push(function(value) {
- if (!ctrl.$isEmpty(value)) {
- if (!isDate(value)) {
- throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
- }
+ if (value && !isDate(value)) {
+ throw $ngModelMinErr('datefmt', 'Expected `{0}` to be a date', value);
+ }
+ if (isValidDate(value)) {
previousDate = value;
if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
@@ -19589,14 +19673,14 @@ function createDateInputType(type, regexp, parseDate, format) {
return $filter('date')(value, format, timezone);
} else {
previousDate = null;
+ return '';
}
- return '';
});
if (isDefined(attr.min) || attr.ngMin) {
var minVal;
ctrl.$validators.min = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(minVal) || parseDate(value) >= minVal;
+ return !isValidDate(value) || isUndefined(minVal) || parseDate(value) >= minVal;
};
attr.$observe('min', function(val) {
minVal = parseObservedDateValue(val);
@@ -19607,18 +19691,18 @@ function createDateInputType(type, regexp, parseDate, format) {
if (isDefined(attr.max) || attr.ngMax) {
var maxVal;
ctrl.$validators.max = function(value) {
- return ctrl.$isEmpty(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
+ return !isValidDate(value) || isUndefined(maxVal) || parseDate(value) <= maxVal;
};
attr.$observe('max', function(val) {
maxVal = parseObservedDateValue(val);
ctrl.$validate();
});
}
- // Override the standard $isEmpty to detect invalid dates as well
- ctrl.$isEmpty = function(value) {
+
+ function isValidDate(value) {
// Invalid Date: getTime() returns NaN
- return !value || (value.getTime && value.getTime() !== value.getTime());
- };
+ return value && !(value.getTime && value.getTime() !== value.getTime());
+ }
function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
@@ -19702,7 +19786,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
stringBasedInputType(ctrl);
ctrl.$$parserName = 'url';
- ctrl.$validators.url = function(value) {
+ ctrl.$validators.url = function(modelValue, viewValue) {
+ var value = modelValue || viewValue;
return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
};
}
@@ -19714,7 +19799,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
stringBasedInputType(ctrl);
ctrl.$$parserName = 'email';
- ctrl.$validators.email = function(value) {
+ ctrl.$validators.email = function(modelValue, viewValue) {
+ var value = modelValue || viewValue;
return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
};
}
@@ -19768,9 +19854,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
element[0].checked = ctrl.$viewValue;
};
- // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue
+ // Override the standard `$isEmpty` because the $viewValue of an empty checkbox is always set to `false`
+ // This is because of the parser below, which compares the `$modelValue` with `trueValue` to convert
+ // it to a boolean.
ctrl.$isEmpty = function(value) {
- return value !== trueValue;
+ return value === false;
};
ctrl.$formatters.push(function(value) {
@@ -19802,7 +19890,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
+ * length.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
@@ -19834,7 +19923,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
- * maxlength.
+ * maxlength. Setting the attribute to a negative or non-numeric value, allows view values of any
+ * length.
* @param {string=} ngPattern Sets `pattern` validation error key if the value does not match the
* RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions.
@@ -20050,13 +20140,18 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`.
*
* @description
*
- * `NgModelController` provides API for the `ng-model` 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.
+ * `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.
*
- * ## Custom Control Example
+ * @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.
@@ -20153,6 +20248,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
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 = [];
@@ -20171,32 +20267,33 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
var parsedNgModel = $parse($attr.ngModel),
+ parsedNgModelAssign = parsedNgModel.assign,
+ ngModelGet = parsedNgModel,
+ ngModelSet = parsedNgModelAssign,
pendingDebounce = null,
ctrl = this;
- var ngModelGet = function ngModelGet() {
- var modelValue = parsedNgModel($scope);
- if (ctrl.$options && ctrl.$options.getterSetter && isFunction(modelValue)) {
- modelValue = modelValue();
- }
- return modelValue;
- };
-
- var ngModelSet = function ngModelSet(newValue) {
- var getterSetter;
- if (ctrl.$options && ctrl.$options.getterSetter &&
- isFunction(getterSetter = parsedNgModel($scope))) {
-
- getterSetter(ctrl.$modelValue);
- } else {
- parsedNgModel.assign($scope, ctrl.$modelValue);
- }
- };
-
this.$$setOptions = function(options) {
ctrl.$options = options;
-
- if (!parsedNgModel.assign && (!options || !options.getterSetter)) {
+ 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));
}
@@ -20229,17 +20326,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @name ngModel.NgModelController#$isEmpty
*
* @description
- * This is called when we need to determine if the value of the input is empty.
+ * 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 Model value to check.
- * @returns {boolean} True if `value` is 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;
@@ -20290,9 +20388,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @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 model has not been changed
- * from when first compiled within then form.
+ * 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;
@@ -20303,13 +20401,32 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
/**
* @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
+ * 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.
*/
@@ -20326,10 +20443,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @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 interacted (focussed) on the model input element and then shifted focus away (blurred)
- * from the input element.
+ * 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;
@@ -20407,14 +20523,51 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @name ngModel.NgModelController#$validate
*
* @description
- * Runs each of the registered validators (first synchronous validators and then asynchronous validators).
+ * 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;
}
- this.$$parseAndValidate();
+
+ 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) {
@@ -20533,11 +20686,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// change to dirty
if (ctrl.$pristine) {
- ctrl.$dirty = true;
- ctrl.$pristine = false;
- $animate.removeClass($element, PRISTINE_CLASS);
- $animate.addClass($element, DIRTY_CLASS);
- parentForm.$setDirty();
+ this.$setDirty();
}
this.$$parseAndValidate();
};
@@ -20558,10 +20707,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
}
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
// ctrl.$modelValue has not been touched yet...
- ctrl.$modelValue = ngModelGet();
+ ctrl.$modelValue = ngModelGet($scope);
}
var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
+ ctrl.$$rawModelValue = modelValue;
if (allowInvalid) {
ctrl.$modelValue = modelValue;
writeToModelIfNeeded();
@@ -20585,7 +20735,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
};
this.$$writeModelToScope = function() {
- ngModelSet(ctrl.$modelValue);
+ ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) {
try {
listener();
@@ -20681,12 +20831,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// ng-change executes in apply phase
// 4. view should be changed back to 'a'
$scope.$watch(function ngModelWatch() {
- var modelValue = ngModelGet();
+ 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 = modelValue;
+ ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
var formatters = ctrl.$formatters,
idx = formatters.length;
@@ -20871,7 +21021,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
</file>
* </example>
*/
-var ngModelDirective = function() {
+var ngModelDirective = ['$rootScope', function($rootScope) {
return {
restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'],
@@ -20915,15 +21065,17 @@ var ngModelDirective = function() {
element.on('blur', function(ev) {
if (modelCtrl.$touched) return;
- scope.$apply(function() {
- modelCtrl.$setTouched();
- });
+ if ($rootScope.