Commit a1e91120 authored by Pascal Hartig's avatar Pascal Hartig

AngularJS: Upgrade to 1.3.4

parent 83d21c67
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
"name": "todomvc-angular", "name": "todomvc-angular",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"angular": "1.3.3", "angular": "1.3.4",
"todomvc-common": "~0.3.0" "todomvc-common": "~0.3.0"
}, },
"devDependencies": { "devDependencies": {
"angular-mocks": "1.3.3", "angular-mocks": "1.3.4",
"angular-route": "1.3.3" "angular-route": "1.3.4"
} }
} }
/** /**
* @license AngularJS v1.3.3 * @license AngularJS v1.3.4
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
...@@ -41,7 +41,7 @@ var ngRouteModule = angular.module('ngRoute', ['ng']). ...@@ -41,7 +41,7 @@ var ngRouteModule = angular.module('ngRoute', ['ng']).
*/ */
function $RouteProvider() { function $RouteProvider() {
function inherit(parent, extra) { function inherit(parent, extra) {
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); return angular.extend(Object.create(parent), extra);
} }
var routes = {}; var routes = {};
...@@ -657,7 +657,7 @@ function $RouteProvider() { ...@@ -657,7 +657,7 @@ function $RouteProvider() {
if (i === 0) { if (i === 0) {
result.push(segment); result.push(segment);
} else { } else {
var segmentMatch = segment.match(/(\w+)(.*)/); var segmentMatch = segment.match(/(\w+)(?:[?*])?(.*)/);
var key = segmentMatch[1]; var key = segmentMatch[1];
result.push(params[key]); result.push(params[key]);
result.push(segmentMatch[2] || ''); result.push(segmentMatch[2] || '');
......
/** /**
* @license AngularJS v1.3.3 * @license AngularJS v1.3.4
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
...@@ -54,7 +54,7 @@ function minErr(module, ErrorConstructor) { ...@@ -54,7 +54,7 @@ function minErr(module, ErrorConstructor) {
return match; return match;
}); });
message = message + '\nhttp://errors.angularjs.org/1.3.3/' + message = message + '\nhttp://errors.angularjs.org/1.3.4/' +
(module ? module + '/' : '') + code; (module ? module + '/' : '') + code;
for (i = 2; i < arguments.length; i++) { for (i = 2; i < arguments.length; i++) {
message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' +
...@@ -426,7 +426,7 @@ function int(str) { ...@@ -426,7 +426,7 @@ function int(str) {
function inherit(parent, extra) { 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) { ...@@ -689,7 +689,7 @@ function makeMap(str) {
function nodeName_(element) { function nodeName_(element) {
return lowercase(element.nodeName || element[0].nodeName); return lowercase(element.nodeName || (element[0] && element[0].nodeName));
} }
function includes(array, obj) { function includes(array, obj) {
...@@ -1395,8 +1395,8 @@ function angularInit(element, bootstrap) { ...@@ -1395,8 +1395,8 @@ function angularInit(element, bootstrap) {
* @param {Object=} config an object for defining configuration options for the application. The * @param {Object=} config an object for defining configuration options for the application. The
* following keys are supported: * following keys are supported:
* *
* - `strictDi`: disable automatic function annotation for the application. This is meant to * * `strictDi` - disable automatic function annotation for the application. This is meant to
* assist in finding bugs which break minified code. * assist in finding bugs which break minified code. Defaults to `false`.
* *
* @returns {auto.$injector} Returns the newly created injector for this app. * @returns {auto.$injector} Returns the newly created injector for this app.
*/ */
...@@ -2100,11 +2100,11 @@ function toDebugString(obj) { ...@@ -2100,11 +2100,11 @@ function toDebugString(obj) {
* - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat".
*/ */
var version = { 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 major: 1, // package task
minor: 3, minor: 3,
dot: 3, dot: 4,
codeName: 'undersea-arithmetic' codeName: 'highfalutin-petroglyph'
}; };
...@@ -2327,10 +2327,12 @@ function publishExternalAPI(angular) { ...@@ -2327,10 +2327,12 @@ function publishExternalAPI(angular) {
* `'ngModel'`). * `'ngModel'`).
* - `injector()` - retrieves the injector of the current element or its parent. * - `injector()` - retrieves the injector of the current element or its parent.
* - `scope()` - retrieves the {@link ng.$rootScope.Scope scope} of the current * - `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 * - `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 * 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. * 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 * - `inheritedData()` - same as `data()`, but walks up the DOM until a value is found or the top
* parent element is reached. * parent element is reached.
* *
...@@ -3325,9 +3327,10 @@ HashMap.prototype = { ...@@ -3325,9 +3327,10 @@ HashMap.prototype = {
* Creates an injector object that can be used for retrieving services as well as for * Creates an injector object that can be used for retrieving services as well as for
* dependency injection (see {@link guide/di dependency injection}). * dependency injection (see {@link guide/di dependency injection}).
* *
* @param {Array.<string|Function>} modules A list of module functions or their aliases. See * @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}. * @returns {injector} Injector object. See {@link auto.$injector $injector}.
* *
* @example * @example
...@@ -3473,8 +3476,10 @@ function annotate(fn, strictDi, name) { ...@@ -3473,8 +3476,10 @@ function annotate(fn, strictDi, name) {
* ## Inference * ## Inference
* *
* In JavaScript calling `toString()` on a function returns the function definition. The definition * 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 * can then be parsed and the function arguments can be extracted. This method of discovering
* minification, and obfuscation tools since these tools change the argument names. * 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 * ## `$inject` Annotation
* By adding an `$inject` property onto a function the injection parameters can be specified. * By adding an `$inject` property onto a function the injection parameters can be specified.
...@@ -3559,6 +3564,8 @@ function annotate(fn, strictDi, name) { ...@@ -3559,6 +3564,8 @@ function annotate(fn, strictDi, name) {
* expect(injector.annotate(MyController)).toEqual(['$scope', '$route']); * 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 * This method does not work with code minification / obfuscation. For this reason the following
* annotation strategies are supported. * annotation strategies are supported.
* *
...@@ -3611,6 +3618,8 @@ function annotate(fn, strictDi, name) { ...@@ -3611,6 +3618,8 @@ function annotate(fn, strictDi, name) {
* @param {Function|Array.<string|Function>} fn Function for which dependent service names need to * @param {Function|Array.<string|Function>} fn Function for which dependent service names need to
* be retrieved as described above. * 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. * @returns {Array.<string>} The names of the services which the function requires.
*/ */
...@@ -4130,14 +4139,11 @@ function createInjector(modulesToLoad, strictDi) { ...@@ -4130,14 +4139,11 @@ function createInjector(modulesToLoad, strictDi) {
} }
function instantiate(Type, locals, serviceName) { 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 // 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) {}]); // e.g. someModule.factory('greeter', ['$window', function(renamed$window) {}]);
Constructor.prototype = (isArray(Type) ? Type[Type.length - 1] : Type).prototype; // Object creation: http://jsperf.com/create-constructor/2
instance = new Constructor(); var instance = Object.create((isArray(Type) ? Type[Type.length - 1] : Type).prototype);
returnedValue = invoke(Type, instance, locals, serviceName); var returnedValue = invoke(Type, instance, locals, serviceName);
return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance; return isObject(returnedValue) || isFunction(returnedValue) ? returnedValue : instance;
} }
...@@ -4973,7 +4979,7 @@ function Browser(window, document, $log, $sniffer) { ...@@ -4973,7 +4979,7 @@ function Browser(window, document, $log, $sniffer) {
// IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. // IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode.
// See https://github.com/angular/angular.js/commit/ffb2701 // See https://github.com/angular/angular.js/commit/ffb2701
if (lastBrowserUrl === url && (!$sniffer.history || sameState)) { if (lastBrowserUrl === url && (!$sniffer.history || sameState)) {
return; return self;
} }
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url); var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url; lastBrowserUrl = url;
...@@ -7885,10 +7891,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { ...@@ -7885,10 +7891,10 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
var childBoundTranscludeFn = boundTranscludeFn; var childBoundTranscludeFn = boundTranscludeFn;
if (scope.$$destroyed) return; if (scope.$$destroyed) return;
if (linkQueue) { if (linkQueue) {
linkQueue.push(scope); linkQueue.push(scope,
linkQueue.push(node); node,
linkQueue.push(rootElement); rootElement,
linkQueue.push(childBoundTranscludeFn); childBoundTranscludeFn);
} else { } else {
if (afterTemplateNodeLinkFn.transcludeOnThisElement) { if (afterTemplateNodeLinkFn.transcludeOnThisElement) {
childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn); childBoundTranscludeFn = createBoundTranscludeFn(scope, afterTemplateNodeLinkFn.transclude, boundTranscludeFn);
...@@ -8357,10 +8363,10 @@ function $ControllerProvider() { ...@@ -8357,10 +8363,10 @@ function $ControllerProvider() {
// //
// This feature is not intended for use by applications, and is thus not documented // This feature is not intended for use by applications, and is thus not documented
// publicly. // publicly.
var Constructor = function() {}; // Object creation: http://jsperf.com/create-constructor/2
Constructor.prototype = (isArray(expression) ? var controllerPrototype = (isArray(expression) ?
expression[expression.length - 1] : expression).prototype; expression[expression.length - 1] : expression).prototype;
instance = new Constructor(); instance = Object.create(controllerPrototype);
if (identifier) { if (identifier) {
addIdentifier(locals, identifier, instance, constructor || expression.name); addIdentifier(locals, identifier, instance, constructor || expression.name);
...@@ -8501,7 +8507,7 @@ function defaultHttpResponseTransform(data, headers) { ...@@ -8501,7 +8507,7 @@ function defaultHttpResponseTransform(data, headers) {
* @returns {Object} Parsed headers as key value object * @returns {Object} Parsed headers as key value object
*/ */
function parseHeaders(headers) { function parseHeaders(headers) {
var parsed = {}, key, val, i; var parsed = createMap(), key, val, i;
if (!headers) return parsed; if (!headers) return parsed;
...@@ -8538,7 +8544,11 @@ function headersGetter(headers) { ...@@ -8538,7 +8544,11 @@ function headersGetter(headers) {
if (!headersObj) headersObj = parseHeaders(headers); if (!headersObj) headersObj = parseHeaders(headers);
if (name) { if (name) {
return headersObj[lowercase(name)] || null; var value = headersObj[lowercase(name)];
if (value === void 0) {
value = null;
}
return value;
} }
return headersObj; return headersObj;
...@@ -8587,6 +8597,11 @@ function $HttpProvider() { ...@@ -8587,6 +8597,11 @@ function $HttpProvider() {
* *
* Object containing default values for all {@link ng.$http $http} requests. * 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.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`. * Defaults value is `'XSRF-TOKEN'`.
* *
...@@ -8600,6 +8615,7 @@ function $HttpProvider() { ...@@ -8600,6 +8615,7 @@ function $HttpProvider() {
* - **`defaults.headers.post`** * - **`defaults.headers.post`**
* - **`defaults.headers.put`** * - **`defaults.headers.put`**
* - **`defaults.headers.patch`** * - **`defaults.headers.patch`**
*
**/ **/
var defaults = this.defaults = { var defaults = this.defaults = {
// transform incoming response data // transform incoming response data
...@@ -8814,6 +8830,21 @@ function $HttpProvider() { ...@@ -8814,6 +8830,21 @@ function $HttpProvider() {
* In addition, you can supply a `headers` property in the config object passed when * 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. * 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 * ## Transforming Requests and Responses
* *
...@@ -9193,6 +9224,10 @@ function $HttpProvider() { ...@@ -9193,6 +9224,10 @@ function $HttpProvider() {
}; };
var headers = mergeHeaders(requestConfig); 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); extend(config, requestConfig);
config.headers = headers; config.headers = headers;
config.method = uppercase(config.method); config.method = uppercase(config.method);
...@@ -10687,6 +10722,13 @@ var locationPrototype = { ...@@ -10687,6 +10722,13 @@ var locationPrototype = {
* Return full url representation with all segments encoded according to rules specified in * Return full url representation with all segments encoded according to rules specified in
* [RFC 3986](http://www.ietf.org/rfc/rfc3986.txt). * [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 * @return {string} full url
*/ */
absUrl: locationGetter('$$absUrl'), absUrl: locationGetter('$$absUrl'),
...@@ -10702,6 +10744,13 @@ var locationPrototype = { ...@@ -10702,6 +10744,13 @@ var locationPrototype = {
* *
* Change path, search and hash, when called with parameter and return `$location`. * 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`) * @param {string=} url New url without base prefix (e.g. `/path?a=b#hash`)
* @return {string} url * @return {string} url
*/ */
...@@ -10710,8 +10759,8 @@ var locationPrototype = { ...@@ -10710,8 +10759,8 @@ var locationPrototype = {
return this.$$url; return this.$$url;
var match = PATH_MATCH.exec(url); var match = PATH_MATCH.exec(url);
if (match[1]) this.path(decodeURIComponent(match[1])); if (match[1] || url === '') this.path(decodeURIComponent(match[1]));
if (match[2] || match[1]) this.search(match[3] || ''); if (match[2] || match[1] || url === '') this.search(match[3] || '');
this.hash(match[5] || ''); this.hash(match[5] || '');
return this; return this;
...@@ -10726,6 +10775,13 @@ var locationPrototype = { ...@@ -10726,6 +10775,13 @@ var locationPrototype = {
* *
* Return protocol of current url. * 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 * @return {string} protocol of current url
*/ */
protocol: locationGetter('$$protocol'), protocol: locationGetter('$$protocol'),
...@@ -10739,6 +10795,13 @@ var locationPrototype = { ...@@ -10739,6 +10795,13 @@ var locationPrototype = {
* *
* Return host of current url. * 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. * @return {string} host of current url.
*/ */
host: locationGetter('$$host'), host: locationGetter('$$host'),
...@@ -10752,6 +10815,13 @@ var locationPrototype = { ...@@ -10752,6 +10815,13 @@ var locationPrototype = {
* *
* Return port of current url. * 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 * @return {Number} port
*/ */
port: locationGetter('$$port'), port: locationGetter('$$port'),
...@@ -10770,6 +10840,13 @@ var locationPrototype = { ...@@ -10770,6 +10840,13 @@ var locationPrototype = {
* Note: Path should always begin with forward slash (/), this method will add the forward slash * Note: Path should always begin with forward slash (/), this method will add the forward slash
* if it is missing. * 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 * @param {(string|number)=} path New path
* @return {string} path * @return {string} path
*/ */
...@@ -10795,10 +10872,9 @@ var locationPrototype = { ...@@ -10795,10 +10872,9 @@ var locationPrototype = {
* var searchObject = $location.search(); * var searchObject = $location.search();
* // => {foo: 'bar', baz: 'xoxo'} * // => {foo: 'bar', baz: 'xoxo'}
* *
*
* // set foo to 'yipee' * // set foo to 'yipee'
* $location.search('foo', '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 * @param {string|Object.<string>|Object.<Array.<string>>} search New search params - string or
...@@ -10868,6 +10944,13 @@ var locationPrototype = { ...@@ -10868,6 +10944,13 @@ var locationPrototype = {
* *
* Change hash fragment when called with parameter and return `$location`. * 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 * @param {(string|number)=} hash New hash fragment
* @return {string} hash * @return {string} hash
*/ */
...@@ -16599,7 +16682,7 @@ function filterFilter() { ...@@ -16599,7 +16682,7 @@ function filterFilter() {
* *
* @param {number} amount Input to filter. * @param {number} amount Input to filter.
* @param {string=} symbol Currency symbol or identifier to be displayed. * @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. * @returns {string} Formatted number.
* *
* *
...@@ -16649,8 +16732,7 @@ function currencyFilter($locale) { ...@@ -16649,8 +16732,7 @@ function currencyFilter($locale) {
} }
if (isUndefined(fractionSize)) { if (isUndefined(fractionSize)) {
// TODO: read the default value from the locale file fractionSize = formats.PATTERNS[1].maxFrac;
fractionSize = 2;
} }
// if null or undefined pass it through // if null or undefined pass it through
...@@ -16802,9 +16884,9 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { ...@@ -16802,9 +16884,9 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
} }
} }
parts.push(isNegative ? pattern.negPre : pattern.posPre); parts.push(isNegative ? pattern.negPre : pattern.posPre,
parts.push(formatedText); formatedText,
parts.push(isNegative ? pattern.negSuf : pattern.posSuf); isNegative ? pattern.negSuf : pattern.posSuf);
return parts.join(''); return parts.join('');
} }
...@@ -18384,9 +18466,7 @@ var formDirectiveFactory = function(isNgForm) { ...@@ -18384,9 +18466,7 @@ var formDirectiveFactory = function(isNgForm) {
controller.$setSubmitted(); controller.$setSubmitted();
}); });
event.preventDefault event.preventDefault();
? event.preventDefault()
: event.returnValue = false; // IE
}; };
addEventListenerFn(formElement[0], 'submit', handleFormSubmission); addEventListenerFn(formElement[0], 'submit', handleFormSubmission);
...@@ -18473,7 +18553,8 @@ var inputType = { ...@@ -18473,7 +18553,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive. * as in the ngPattern directive.
...@@ -19021,7 +19102,8 @@ var inputType = { ...@@ -19021,7 +19102,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive. * as in the ngPattern directive.
...@@ -19108,7 +19190,8 @@ var inputType = { ...@@ -19108,7 +19190,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive. * as in the ngPattern directive.
...@@ -19196,7 +19279,8 @@ var inputType = { ...@@ -19196,7 +19279,8 @@ var inputType = {
* @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive. * as in the ngPattern directive.
...@@ -19467,7 +19551,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -19467,7 +19551,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
element.on('change', listener); element.on('change', listener);
ctrl.$render = function() { 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) { ...@@ -19577,10 +19661,10 @@ function createDateInputType(type, regexp, parseDate, format) {
}); });
ctrl.$formatters.push(function(value) { ctrl.$formatters.push(function(value) {
if (!ctrl.$isEmpty(value)) { if (value && !isDate(value)) {
if (!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; previousDate = value;
if (previousDate && timezone === 'UTC') { if (previousDate && timezone === 'UTC') {
var timezoneOffset = 60000 * previousDate.getTimezoneOffset(); var timezoneOffset = 60000 * previousDate.getTimezoneOffset();
...@@ -19589,14 +19673,14 @@ function createDateInputType(type, regexp, parseDate, format) { ...@@ -19589,14 +19673,14 @@ function createDateInputType(type, regexp, parseDate, format) {
return $filter('date')(value, format, timezone); return $filter('date')(value, format, timezone);
} else { } else {
previousDate = null; previousDate = null;
return '';
} }
return '';
}); });
if (isDefined(attr.min) || attr.ngMin) { if (isDefined(attr.min) || attr.ngMin) {
var minVal; var minVal;
ctrl.$validators.min = function(value) { 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) { attr.$observe('min', function(val) {
minVal = parseObservedDateValue(val); minVal = parseObservedDateValue(val);
...@@ -19607,18 +19691,18 @@ function createDateInputType(type, regexp, parseDate, format) { ...@@ -19607,18 +19691,18 @@ function createDateInputType(type, regexp, parseDate, format) {
if (isDefined(attr.max) || attr.ngMax) { if (isDefined(attr.max) || attr.ngMax) {
var maxVal; var maxVal;
ctrl.$validators.max = function(value) { 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) { attr.$observe('max', function(val) {
maxVal = parseObservedDateValue(val); maxVal = parseObservedDateValue(val);
ctrl.$validate(); ctrl.$validate();
}); });
} }
// Override the standard $isEmpty to detect invalid dates as well
ctrl.$isEmpty = function(value) { function isValidDate(value) {
// Invalid Date: getTime() returns NaN // Invalid Date: getTime() returns NaN
return !value || (value.getTime && value.getTime() !== value.getTime()); return value && !(value.getTime && value.getTime() !== value.getTime());
}; }
function parseObservedDateValue(val) { function parseObservedDateValue(val) {
return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined; return isDefined(val) ? (isDate(val) ? val : parseDate(val)) : undefined;
...@@ -19702,7 +19786,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -19702,7 +19786,8 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
stringBasedInputType(ctrl); stringBasedInputType(ctrl);
ctrl.$$parserName = 'url'; 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); return ctrl.$isEmpty(value) || URL_REGEXP.test(value);
}; };
} }
...@@ -19714,7 +19799,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -19714,7 +19799,8 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
stringBasedInputType(ctrl); stringBasedInputType(ctrl);
ctrl.$$parserName = 'email'; 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); return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value);
}; };
} }
...@@ -19768,9 +19854,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt ...@@ -19768,9 +19854,11 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
element[0].checked = ctrl.$viewValue; 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) { ctrl.$isEmpty = function(value) {
return value !== trueValue; return value === false;
}; };
ctrl.$formatters.push(function(value) { ctrl.$formatters.push(function(value) {
...@@ -19802,7 +19890,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt ...@@ -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 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions. * patterns defined as scope expressions.
...@@ -19834,7 +19923,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt ...@@ -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 * @param {number=} ngMinlength Sets `minlength` validation error key if the value is shorter than
* minlength. * minlength.
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than * @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 * @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 * RegExp pattern expression. Expected value is `/regexp/` for inline patterns or `regexp` for
* patterns defined as scope expressions. * patterns defined as scope expressions.
...@@ -20050,13 +20140,18 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`. ...@@ -20050,13 +20140,18 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`.
* *
* @description * @description
* *
* `NgModelController` provides API for the `ng-model` directive. The controller contains * `NgModelController` provides API for the {@link ngModel `ngModel`} directive.
* services for data-binding, validation, CSS updates, and value formatting and parsing. It * The controller contains services for data-binding, validation, CSS updates, and value formatting
* purposefully does not contain any logic which deals with DOM rendering or listening to * and parsing. It purposefully does not contain any logic which deals with DOM rendering or
* DOM events. Such DOM related logic should be provided by other directives which make use of * listening to DOM events.
* `NgModelController` for data-binding. * 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 * This example shows how to use `NgModelController` with a custom control to achieve
* data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`)
* collaborate together to achieve the desired result. * collaborate together to achieve the desired result.
...@@ -20153,6 +20248,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20153,6 +20248,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) { function($scope, $exceptionHandler, $attr, $element, $parse, $animate, $timeout, $rootScope, $q, $interpolate) {
this.$viewValue = Number.NaN; this.$viewValue = Number.NaN;
this.$modelValue = Number.NaN; this.$modelValue = Number.NaN;
this.$$rawModelValue = undefined; // stores the parsed modelValue / model set from scope regardless of validity.
this.$validators = {}; this.$validators = {};
this.$asyncValidators = {}; this.$asyncValidators = {};
this.$parsers = []; this.$parsers = [];
...@@ -20171,32 +20267,33 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20171,32 +20267,33 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
var parsedNgModel = $parse($attr.ngModel), var parsedNgModel = $parse($attr.ngModel),
parsedNgModelAssign = parsedNgModel.assign,
ngModelGet = parsedNgModel,
ngModelSet = parsedNgModelAssign,
pendingDebounce = null, pendingDebounce = null,
ctrl = this; 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) { this.$$setOptions = function(options) {
ctrl.$options = options; ctrl.$options = options;
if (options && options.getterSetter) {
if (!parsedNgModel.assign && (!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}", throw $ngModelMinErr('nonassign', "Expression '{0}' is non-assignable. Element: {1}",
$attr.ngModel, startingTag($element)); $attr.ngModel, startingTag($element));
} }
...@@ -20229,17 +20326,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20229,17 +20326,18 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @name ngModel.NgModelController#$isEmpty * @name ngModel.NgModelController#$isEmpty
* *
* @description * @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. * 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`. * 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 * 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` * default. The `checkboxInputType` directive does this because in its case a value of `false`
* implies empty. * implies empty.
* *
* @param {*} value Model value to check. * @param {*} value The value of the input to check for emptiness.
* @returns {boolean} True if `value` is empty. * @returns {boolean} True if `value` is "empty".
*/ */
this.$isEmpty = function(value) { this.$isEmpty = function(value) {
return isUndefined(value) || value === '' || value === null || value !== value; return isUndefined(value) || value === '' || value === null || value !== value;
...@@ -20290,9 +20388,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20290,9 +20388,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @description * @description
* Sets the control to its pristine state. * 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 * 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 * state (`ng-pristine` class). A model is considered to be pristine when the control
* from when first compiled within then form. * has not been changed from when first compiled.
*/ */
this.$setPristine = function() { this.$setPristine = function() {
ctrl.$dirty = false; ctrl.$dirty = false;
...@@ -20301,6 +20399,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20301,6 +20399,25 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
$animate.addClass($element, PRISTINE_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 * @ngdoc method
* @name ngModel.NgModelController#$setUntouched * @name ngModel.NgModelController#$setUntouched
...@@ -20308,8 +20425,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20308,8 +20425,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @description * @description
* Sets the control to its untouched state. * Sets the control to its untouched state.
* *
* This method can be called to remove the 'ng-touched' class and set the control to its * 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 * 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 * by default, however this function can be used to restore that state if the model has
* already been touched by the user. * already been touched by the user.
*/ */
...@@ -20326,10 +20443,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20326,10 +20443,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @description * @description
* Sets the control to its touched state. * Sets the control to its touched state.
* *
* This method can be called to remove the 'ng-untouched' class and set the control to its * 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 * 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) * first focused the control element and then shifted focus away from the control (blur event).
* from the input element.
*/ */
this.$setTouched = function() { this.$setTouched = function() {
ctrl.$touched = true; ctrl.$touched = true;
...@@ -20407,14 +20523,51 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20407,14 +20523,51 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* @name ngModel.NgModelController#$validate * @name ngModel.NgModelController#$validate
* *
* @description * @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() { this.$validate = function() {
// ignore $validate before model is initialized // ignore $validate before model is initialized
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
return; 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) { this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) {
...@@ -20533,11 +20686,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20533,11 +20686,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// change to dirty // change to dirty
if (ctrl.$pristine) { if (ctrl.$pristine) {
ctrl.$dirty = true; this.$setDirty();
ctrl.$pristine = false;
$animate.removeClass($element, PRISTINE_CLASS);
$animate.addClass($element, DIRTY_CLASS);
parentForm.$setDirty();
} }
this.$$parseAndValidate(); this.$$parseAndValidate();
}; };
...@@ -20558,10 +20707,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20558,10 +20707,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
} }
if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) { if (isNumber(ctrl.$modelValue) && isNaN(ctrl.$modelValue)) {
// ctrl.$modelValue has not been touched yet... // ctrl.$modelValue has not been touched yet...
ctrl.$modelValue = ngModelGet(); ctrl.$modelValue = ngModelGet($scope);
} }
var prevModelValue = ctrl.$modelValue; var prevModelValue = ctrl.$modelValue;
var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid; var allowInvalid = ctrl.$options && ctrl.$options.allowInvalid;
ctrl.$$rawModelValue = modelValue;
if (allowInvalid) { if (allowInvalid) {
ctrl.$modelValue = modelValue; ctrl.$modelValue = modelValue;
writeToModelIfNeeded(); writeToModelIfNeeded();
...@@ -20585,7 +20735,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20585,7 +20735,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
}; };
this.$$writeModelToScope = function() { this.$$writeModelToScope = function() {
ngModelSet(ctrl.$modelValue); ngModelSet($scope, ctrl.$modelValue);
forEach(ctrl.$viewChangeListeners, function(listener) { forEach(ctrl.$viewChangeListeners, function(listener) {
try { try {
listener(); listener();
...@@ -20681,12 +20831,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20681,12 +20831,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
// ng-change executes in apply phase // ng-change executes in apply phase
// 4. view should be changed back to 'a' // 4. view should be changed back to 'a'
$scope.$watch(function ngModelWatch() { $scope.$watch(function ngModelWatch() {
var modelValue = ngModelGet(); var modelValue = ngModelGet($scope);
// if scope model value and ngModel value are out of sync // if scope model value and ngModel value are out of sync
// TODO(perf): why not move this to the action fn? // TODO(perf): why not move this to the action fn?
if (modelValue !== ctrl.$modelValue) { if (modelValue !== ctrl.$modelValue) {
ctrl.$modelValue = modelValue; ctrl.$modelValue = ctrl.$$rawModelValue = modelValue;
var formatters = ctrl.$formatters, var formatters = ctrl.$formatters,
idx = formatters.length; idx = formatters.length;
...@@ -20871,7 +21021,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -20871,7 +21021,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
</file> </file>
* </example> * </example>
*/ */
var ngModelDirective = function() { var ngModelDirective = ['$rootScope', function($rootScope) {
return { return {
restrict: 'A', restrict: 'A',
require: ['ngModel', '^?form', '^?ngModelOptions'], require: ['ngModel', '^?form', '^?ngModelOptions'],
...@@ -20915,15 +21065,17 @@ var ngModelDirective = function() { ...@@ -20915,15 +21065,17 @@ var ngModelDirective = function() {
element.on('blur', function(ev) { element.on('blur', function(ev) {
if (modelCtrl.$touched) return; if (modelCtrl.$touched) return;
scope.$apply(function() { if ($rootScope.$$phase) {
modelCtrl.$setTouched(); scope.$evalAsync(modelCtrl.$setTouched);
}); } else {
scope.$apply(modelCtrl.$setTouched);
}
}); });
} }
}; };
} }
}; };
}; }];
/** /**
...@@ -21012,8 +21164,8 @@ var requiredDirective = function() { ...@@ -21012,8 +21164,8 @@ var requiredDirective = function() {
if (!ctrl) return; if (!ctrl) return;
attr.required = true; // force truthy in case we are on non input element attr.required = true; // force truthy in case we are on non input element
ctrl.$validators.required = function(value) { ctrl.$validators.required = function(modelValue, viewValue) {
return !attr.required || !ctrl.$isEmpty(value); return !attr.required || !ctrl.$isEmpty(viewValue);
}; };
attr.$observe('required', function() { attr.$observe('required', function() {
...@@ -21062,13 +21214,14 @@ var maxlengthDirective = function() { ...@@ -21062,13 +21214,14 @@ var maxlengthDirective = function() {
link: function(scope, elm, attr, ctrl) { link: function(scope, elm, attr, ctrl) {
if (!ctrl) return; if (!ctrl) return;
var maxlength = 0; var maxlength = -1;
attr.$observe('maxlength', function(value) { attr.$observe('maxlength', function(value) {
maxlength = int(value) || 0; var intVal = int(value);
maxlength = isNaN(intVal) ? -1 : intVal;
ctrl.$validate(); ctrl.$validate();
}); });
ctrl.$validators.maxlength = function(modelValue, viewValue) { ctrl.$validators.maxlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(modelValue) || viewValue.length <= maxlength; return (maxlength < 0) || ctrl.$isEmpty(modelValue) || (viewValue.length <= maxlength);
}; };
} }
}; };
...@@ -21087,7 +21240,7 @@ var minlengthDirective = function() { ...@@ -21087,7 +21240,7 @@ var minlengthDirective = function() {
ctrl.$validate(); ctrl.$validate();
}); });
ctrl.$validators.minlength = function(modelValue, viewValue) { ctrl.$validators.minlength = function(modelValue, viewValue) {
return ctrl.$isEmpty(modelValue) || viewValue.length >= minlength; return ctrl.$isEmpty(viewValue) || viewValue.length >= minlength;
}; };
} }
}; };
...@@ -21715,12 +21868,11 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate ...@@ -21715,12 +21868,11 @@ var ngBindTemplateDirective = ['$interpolate', '$compile', function($interpolate
* @name ngBindHtml * @name ngBindHtml
* *
* @description * @description
* Creates a binding that will innerHTML the result of evaluating the `expression` into the current * Evaluates the expression and inserts the resulting HTML into the element in a secure way. By default,
* element in a secure way. By default, the innerHTML-ed content will be sanitized using the {@link * the resulting HTML content will be sanitized using the {@link ngSanitize.$sanitize $sanitize} service.
* ngSanitize.$sanitize $sanitize} service. To utilize this functionality, ensure that `$sanitize` * To utilize this functionality, ensure that `$sanitize` is available, for example, by including {@link
* is available, for example, by including {@link ngSanitize} in your module's dependencies (not in * ngSanitize} in your module's dependencies (not in core Angular). In order to use {@link ngSanitize}
* core Angular). In order to use {@link ngSanitize} in your module's dependencies, you need to * in your module's dependencies, you need to include "angular-sanitize.js" in your application.
* include "angular-sanitize.js" in your application.
* *
* You may also bypass sanitization for values you know are safe. To do so, bind to * You may also bypass sanitization for values you know are safe. To do so, bind to
* an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example * an explicitly trusted value via {@link ng.$sce#trustAsHtml $sce.trustAsHtml}. See the example
...@@ -23784,7 +23936,9 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); ...@@ -23784,7 +23936,9 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
</example> </example>
*/ */
var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) { var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interpolate) {
var BRACE = /{}/g; var BRACE = /{}/g,
IS_WHEN = /^when(Minus)?(.+)$/;
return { return {
restrict: 'EA', restrict: 'EA',
link: function(scope, element, attr) { link: function(scope, element, attr) {
...@@ -23795,34 +23949,44 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp ...@@ -23795,34 +23949,44 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp
whensExpFns = {}, whensExpFns = {},
startSymbol = $interpolate.startSymbol(), startSymbol = $interpolate.startSymbol(),
endSymbol = $interpolate.endSymbol(), endSymbol = $interpolate.endSymbol(),
isWhen = /^when(Minus)?(.+)$/; braceReplacement = startSymbol + numberExp + '-' + offset + endSymbol,
watchRemover = angular.noop,
lastCount;
forEach(attr, function(expression, attributeName) { forEach(attr, function(expression, attributeName) {
if (isWhen.test(attributeName)) { var tmpMatch = IS_WHEN.exec(attributeName);
whens[lowercase(attributeName.replace('when', '').replace('Minus', '-'))] = if (tmpMatch) {
element.attr(attr.$attr[attributeName]); var whenKey = (tmpMatch[1] ? '-' : '') + lowercase(tmpMatch[2]);
whens[whenKey] = element.attr(attr.$attr[attributeName]);
} }
}); });
forEach(whens, function(expression, key) { forEach(whens, function(expression, key) {
whensExpFns[key] = whensExpFns[key] = $interpolate(expression.replace(BRACE, braceReplacement));
$interpolate(expression.replace(BRACE, startSymbol + numberExp + '-' +
offset + endSymbol));
}); });
scope.$watch(function ngPluralizeWatch() { scope.$watch(numberExp, function ngPluralizeWatchAction(newVal) {
var value = parseFloat(scope.$eval(numberExp)); var count = parseFloat(newVal);
var countIsNaN = isNaN(count);
if (!isNaN(value)) { if (!countIsNaN && !(count in whens)) {
//if explicit number rule such as 1, 2, 3... is defined, just use it. Otherwise, // If an explicit number rule such as 1, 2, 3... is defined, just use it.
//check it against pluralization rules in $locale service // Otherwise, check it against pluralization rules in $locale service.
if (!(value in whens)) value = $locale.pluralCat(value - offset); count = $locale.pluralCat(count - offset);
return whensExpFns[value](scope); }
} else {
return ''; // If both `count` and `lastCount` are NaN, we don't need to re-register a watch.
// In JS `NaN !== NaN`, so we have to exlicitly check.
if ((count !== lastCount) && !(countIsNaN && isNaN(lastCount))) {
watchRemover();
watchRemover = scope.$watch(whensExpFns[count], updateElementText);
lastCount = count;
} }
}, function ngPluralizeWatchAction(newVal) {
element.text(newVal);
}); });
function updateElementText(newText) {
element.text(newText || '');
}
} }
}; };
}]; }];
...@@ -24304,17 +24468,17 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; ...@@ -24304,17 +24468,17 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* *
* ### Overriding `.ng-hide` * ### Overriding `.ng-hide`
* *
* By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change * 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` * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS: * class in CSS:
* *
* ```css * ```css
* .ng-hide { * .ng-hide {
* /&#42; this is just another form of hiding an element &#42;/ * /&#42; this is just another form of hiding an element &#42;/
* display:block!important; * display: block!important;
* position:absolute; * position: absolute;
* top:-9999px; * top: -9999px;
* left:-9999px; * left: -9999px;
* } * }
* ``` * ```
* *
...@@ -24334,13 +24498,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; ...@@ -24334,13 +24498,13 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
* .my-element.ng-hide-add, .my-element.ng-hide-remove { * .my-element.ng-hide-add, .my-element.ng-hide-remove {
* /&#42; this is required as of 1.3x to properly * /&#42; this is required as of 1.3x to properly
* apply all styling in a show/hide animation &#42;/ * apply all styling in a show/hide animation &#42;/
* transition:0s linear all; * transition: 0s linear all;
* } * }
* *
* .my-element.ng-hide-add-active, * .my-element.ng-hide-add-active,
* .my-element.ng-hide-remove-active { * .my-element.ng-hide-remove-active {
* /&#42; the transition is defined in the active class &#42;/ * /&#42; the transition is defined in the active class &#42;/
* transition:1s linear all; * transition: 1s linear all;
* } * }
* *
* .my-element.ng-hide-add { ... } * .my-element.ng-hide-add { ... }
...@@ -24382,29 +24546,29 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate'; ...@@ -24382,29 +24546,29 @@ var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
</file> </file>
<file name="animations.css"> <file name="animations.css">
.animate-show { .animate-show {
line-height:20px; line-height: 20px;
opacity:1; opacity: 1;
padding:10px; padding: 10px;
border:1px solid black; border: 1px solid black;
background:white; background: white;
} }
.animate-show.ng-hide-add.ng-hide-add-active, .animate-show.ng-hide-add.ng-hide-add-active,
.animate-show.ng-hide-remove.ng-hide-remove-active { .animate-show.ng-hide-remove.ng-hide-remove-active {
-webkit-transition:all linear 0.5s; -webkit-transition: all linear 0.5s;
transition:all linear 0.5s; transition: all linear 0.5s;
} }
.animate-show.ng-hide { .animate-show.ng-hide {
line-height:0; line-height: 0;
opacity:0; opacity: 0;
padding:0 10px; padding: 0 10px;
} }
.check-element { .check-element {
padding:10px; padding: 10px;
border:1px solid black; border: 1px solid black;
background:white; background: white;
} }
</file> </file>
<file name="protractor.js" type="protractor"> <file name="protractor.js" type="protractor">
...@@ -24478,17 +24642,17 @@ var ngShowDirective = ['$animate', function($animate) { ...@@ -24478,17 +24642,17 @@ var ngShowDirective = ['$animate', function($animate) {
* *
* ### Overriding `.ng-hide` * ### Overriding `.ng-hide`
* *
* By default, the `.ng-hide` class will style the element with `display:none!important`. If you wish to change * 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` * the hide behavior with ngShow/ngHide then this can be achieved by restating the styles for the `.ng-hide`
* class in CSS: * class in CSS:
* *
* ```css * ```css
* .ng-hide { * .ng-hide {
* /&#42; this is just another form of hiding an element &#42;/ * /&#42; this is just another form of hiding an element &#42;/
* display:block!important; * display: block!important;
* position:absolute; * position: absolute;
* top:-9999px; * top: -9999px;
* left:-9999px; * left: -9999px;
* } * }
* ``` * ```
* *
...@@ -24505,7 +24669,7 @@ var ngShowDirective = ['$animate', function($animate) { ...@@ -24505,7 +24669,7 @@ var ngShowDirective = ['$animate', function($animate) {
* //a working example can be found at the bottom of this page * //a working example can be found at the bottom of this page
* // * //
* .my-element.ng-hide-add, .my-element.ng-hide-remove { * .my-element.ng-hide-add, .my-element.ng-hide-remove {
* transition:0.5s linear all; * transition: 0.5s linear all;
* } * }
* *
* .my-element.ng-hide-add { ... } * .my-element.ng-hide-add { ... }
...@@ -24547,25 +24711,25 @@ var ngShowDirective = ['$animate', function($animate) { ...@@ -24547,25 +24711,25 @@ var ngShowDirective = ['$animate', function($animate) {
</file> </file>
<file name="animations.css"> <file name="animations.css">
.animate-hide { .animate-hide {
-webkit-transition:all linear 0.5s; -webkit-transition: all linear 0.5s;
transition:all linear 0.5s; transition: all linear 0.5s;
line-height:20px; line-height: 20px;
opacity:1; opacity: 1;
padding:10px; padding: 10px;
border:1px solid black; border: 1px solid black;
background:white; background: white;
} }
.animate-hide.ng-hide { .animate-hide.ng-hide {
line-height:0; line-height: 0;
opacity:0; opacity: 0;
padding:0 10px; padding: 0 10px;
} }
.check-element { .check-element {
padding:10px; padding: 10px;
border:1px solid black; border: 1px solid black;
background:white; background: white;
} }
</file> </file>
<file name="protractor.js" type="protractor"> <file name="protractor.js" type="protractor">
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment