Commit 5a53ef13 authored by Pascal Hartig's avatar Pascal Hartig

Angular: Update to 1.2.19

parent 36e2ffa2
...@@ -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.2.18", "angular": "1.2.19",
"todomvc-common": "~0.1.4" "todomvc-common": "~0.1.4"
}, },
"devDependencies": { "devDependencies": {
"angular-mocks": "1.2.18", "angular-mocks": "1.2.19",
"angular-route": "1.2.18" "angular-route": "1.2.19"
} }
} }
/** /**
* @license AngularJS v1.2.18 * @license AngularJS v1.2.19
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
......
/** /**
* @license AngularJS v1.2.18 * @license AngularJS v1.2.19
* (c) 2010-2014 Google, Inc. http://angularjs.org * (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT * License: MIT
*/ */
...@@ -68,7 +68,7 @@ function minErr(module) { ...@@ -68,7 +68,7 @@ function minErr(module) {
return match; return match;
}); });
message = message + '\nhttp://errors.angularjs.org/1.2.18/' + message = message + '\nhttp://errors.angularjs.org/1.2.19/' +
(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) + '=' +
...@@ -92,6 +92,7 @@ function minErr(module) { ...@@ -92,6 +92,7 @@ function minErr(module) {
-angularModule, -angularModule,
-nodeName_, -nodeName_,
-uid, -uid,
-VALIDITY_STATE_PROPERTY,
-lowercase, -lowercase,
-uppercase, -uppercase,
...@@ -181,6 +182,10 @@ function minErr(module) { ...@@ -181,6 +182,10 @@ function minErr(module) {
* <div doc-module-components="ng"></div> * <div doc-module-components="ng"></div>
*/ */
// The name of a form control's ValidityState property.
// This is used so that it's possible for internal tests to create mock ValidityStates.
var VALIDITY_STATE_PROPERTY = 'validity';
/** /**
* @ngdoc function * @ngdoc function
* @name angular.lowercase * @name angular.lowercase
...@@ -1483,7 +1488,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) { ...@@ -1483,7 +1488,7 @@ function assertArgFn(arg, name, acceptArrayAnnotation) {
} }
assertArg(isFunction(arg), name, 'not a function, got ' + assertArg(isFunction(arg), name, 'not a function, got ' +
(arg && typeof arg == 'object' ? arg.constructor.name || 'Object' : typeof arg)); (arg && typeof arg === 'object' ? arg.constructor.name || 'Object' : typeof arg));
return arg; return arg;
} }
...@@ -1953,11 +1958,11 @@ function setupModuleLoader(window) { ...@@ -1953,11 +1958,11 @@ function setupModuleLoader(window) {
* - `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.2.18', // all of these placeholder strings will be replaced by grunt's full: '1.2.19', // all of these placeholder strings will be replaced by grunt's
major: 1, // package task major: 1, // package task
minor: 2, minor: 2,
dot: 18, dot: 19,
codeName: 'ear-extendability' codeName: 'precognitive-flashbacks'
}; };
...@@ -2179,8 +2184,9 @@ function publishExternalAPI(angular){ ...@@ -2179,8 +2184,9 @@ function publishExternalAPI(angular){
* @returns {Object} jQuery object. * @returns {Object} jQuery object.
*/ */
JQLite.expando = 'ng339';
var jqCache = JQLite.cache = {}, var jqCache = JQLite.cache = {},
jqName = JQLite.expando = 'ng' + new Date().getTime(),
jqId = 1, jqId = 1,
addEventListenerFn = (window.document.addEventListener addEventListenerFn = (window.document.addEventListener
? function(element, type, fn) {element.addEventListener(type, fn, false);} ? function(element, type, fn) {element.addEventListener(type, fn, false);}
...@@ -2390,7 +2396,7 @@ function jqLiteOff(element, type, fn, unsupported) { ...@@ -2390,7 +2396,7 @@ function jqLiteOff(element, type, fn, unsupported) {
} }
function jqLiteRemoveData(element, name) { function jqLiteRemoveData(element, name) {
var expandoId = element[jqName], var expandoId = element.ng339,
expandoStore = jqCache[expandoId]; expandoStore = jqCache[expandoId];
if (expandoStore) { if (expandoStore) {
...@@ -2404,17 +2410,17 @@ function jqLiteRemoveData(element, name) { ...@@ -2404,17 +2410,17 @@ function jqLiteRemoveData(element, name) {
jqLiteOff(element); jqLiteOff(element);
} }
delete jqCache[expandoId]; delete jqCache[expandoId];
element[jqName] = undefined; // ie does not allow deletion of attributes on elements. element.ng339 = undefined; // don't delete DOM expandos. IE and Chrome don't like it
} }
} }
function jqLiteExpandoStore(element, key, value) { function jqLiteExpandoStore(element, key, value) {
var expandoId = element[jqName], var expandoId = element.ng339,
expandoStore = jqCache[expandoId || -1]; expandoStore = jqCache[expandoId || -1];
if (isDefined(value)) { if (isDefined(value)) {
if (!expandoStore) { if (!expandoStore) {
element[jqName] = expandoId = jqNextId(); element.ng339 = expandoId = jqNextId();
expandoStore = jqCache[expandoId] = {}; expandoStore = jqCache[expandoId] = {};
} }
expandoStore[key] = value; expandoStore[key] = value;
...@@ -3081,16 +3087,16 @@ forEach({ ...@@ -3081,16 +3087,16 @@ forEach({
* @returns {string} hash string such that the same input will have the same hash string. * @returns {string} hash string such that the same input will have the same hash string.
* The resulting string key is in 'type:hashKey' format. * The resulting string key is in 'type:hashKey' format.
*/ */
function hashKey(obj) { function hashKey(obj, nextUidFn) {
var objType = typeof obj, var objType = typeof obj,
key; key;
if (objType == 'object' && obj !== null) { if (objType == 'function' || (objType == 'object' && obj !== null)) {
if (typeof (key = obj.$$hashKey) == 'function') { if (typeof (key = obj.$$hashKey) == 'function') {
// must invoke on object to keep the right this // must invoke on object to keep the right this
key = obj.$$hashKey(); key = obj.$$hashKey();
} else if (key === undefined) { } else if (key === undefined) {
key = obj.$$hashKey = nextUid(); key = obj.$$hashKey = (nextUidFn || nextUid)();
} }
} else { } else {
key = obj; key = obj;
...@@ -3102,7 +3108,13 @@ function hashKey(obj) { ...@@ -3102,7 +3108,13 @@ function hashKey(obj) {
/** /**
* HashMap which can use objects as keys * HashMap which can use objects as keys
*/ */
function HashMap(array){ function HashMap(array, isolatedUid) {
if (isolatedUid) {
var uid = 0;
this.nextUid = function() {
return ++uid;
};
}
forEach(array, this.put, this); forEach(array, this.put, this);
} }
HashMap.prototype = { HashMap.prototype = {
...@@ -3112,7 +3124,7 @@ HashMap.prototype = { ...@@ -3112,7 +3124,7 @@ HashMap.prototype = {
* @param value value to store can be any type * @param value value to store can be any type
*/ */
put: function(key, value) { put: function(key, value) {
this[hashKey(key)] = value; this[hashKey(key, this.nextUid)] = value;
}, },
/** /**
...@@ -3120,7 +3132,7 @@ HashMap.prototype = { ...@@ -3120,7 +3132,7 @@ HashMap.prototype = {
* @returns {Object} the value for the key * @returns {Object} the value for the key
*/ */
get: function(key) { get: function(key) {
return this[hashKey(key)]; return this[hashKey(key, this.nextUid)];
}, },
/** /**
...@@ -3128,7 +3140,7 @@ HashMap.prototype = { ...@@ -3128,7 +3140,7 @@ HashMap.prototype = {
* @param key * @param key
*/ */
remove: function(key) { remove: function(key) {
var value = this[key = hashKey(key)]; var value = this[key = hashKey(key, this.nextUid)];
delete this[key]; delete this[key];
return value; return value;
} }
...@@ -3206,7 +3218,7 @@ function annotate(fn) { ...@@ -3206,7 +3218,7 @@ function annotate(fn) {
argDecl, argDecl,
last; last;
if (typeof fn == 'function') { if (typeof fn === 'function') {
if (!($inject = fn.$inject)) { if (!($inject = fn.$inject)) {
$inject = []; $inject = [];
if (fn.length) { if (fn.length) {
...@@ -3419,7 +3431,7 @@ function annotate(fn) { ...@@ -3419,7 +3431,7 @@ function annotate(fn) {
/** /**
* @ngdoc object * @ngdoc service
* @name $provide * @name $provide
* *
* @description * @description
...@@ -3725,7 +3737,7 @@ function createInjector(modulesToLoad) { ...@@ -3725,7 +3737,7 @@ function createInjector(modulesToLoad) {
var INSTANTIATING = {}, var INSTANTIATING = {},
providerSuffix = 'Provider', providerSuffix = 'Provider',
path = [], path = [],
loadedModules = new HashMap(), loadedModules = new HashMap([], true),
providerCache = { providerCache = {
$provide: { $provide: {
provider: supportObject(provider), provider: supportObject(provider),
...@@ -3896,8 +3908,7 @@ function createInjector(modulesToLoad) { ...@@ -3896,8 +3908,7 @@ function createInjector(modulesToLoad) {
: getService(key) : getService(key)
); );
} }
if (!fn.$inject) { if (isArray(fn)) {
// this means that we must be an array.
fn = fn[length]; fn = fn[length];
} }
...@@ -6078,7 +6089,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { ...@@ -6078,7 +6089,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective);
// iterate over the attributes // iterate over the attributes
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
var attrStartName = false; var attrStartName = false;
var attrEndName = false; var attrEndName = false;
...@@ -6086,9 +6097,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { ...@@ -6086,9 +6097,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
attr = nAttrs[j]; attr = nAttrs[j];
if (!msie || msie >= 8 || attr.specified) { if (!msie || msie >= 8 || attr.specified) {
name = attr.name; name = attr.name;
value = trim(attr.value);
// support ngAttr attribute binding // support ngAttr attribute binding
ngAttrName = directiveNormalize(name); ngAttrName = directiveNormalize(name);
if (NG_ATTR_BINDING.test(ngAttrName)) { if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) {
name = snake_case(ngAttrName.substr(6), '-'); name = snake_case(ngAttrName.substr(6), '-');
} }
...@@ -6101,10 +6114,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { ...@@ -6101,10 +6114,12 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
nName = directiveNormalize(name.toLowerCase()); nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name; attrsMap[nName] = name;
attrs[nName] = value = trim(attr.value); if (isNgAttr || !attrs.hasOwnProperty(nName)) {
attrs[nName] = value;
if (getBooleanAttrName(node, nName)) { if (getBooleanAttrName(node, nName)) {
attrs[nName] = true; // presence means true attrs[nName] = true; // presence means true
} }
}
addAttrInterpolateDirective(node, directives, value, nName); addAttrInterpolateDirective(node, directives, value, nName);
addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName,
attrEndName); attrEndName);
...@@ -7201,7 +7216,7 @@ function $ControllerProvider() { ...@@ -7201,7 +7216,7 @@ function $ControllerProvider() {
instance = $injector.instantiate(expression, locals); instance = $injector.instantiate(expression, locals);
if (identifier) { if (identifier) {
if (!(locals && typeof locals.$scope == 'object')) { if (!(locals && typeof locals.$scope === 'object')) {
throw minErr('$controller')('noscp', throw minErr('$controller')('noscp',
"Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.", "Cannot export controller '{0}' as '{1}'! No $scope object provided via `locals`.",
constructor || expression.name, identifier); constructor || expression.name, identifier);
...@@ -8403,7 +8418,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc ...@@ -8403,7 +8418,8 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
// Safari respectively. // Safari respectively.
if (xhr && xhr.readyState == 4) { if (xhr && xhr.readyState == 4) {
var responseHeaders = null, var responseHeaders = null,
response = null; response = null,
statusText = '';
if(status !== ABORTED) { if(status !== ABORTED) {
responseHeaders = xhr.getAllResponseHeaders(); responseHeaders = xhr.getAllResponseHeaders();
...@@ -8413,11 +8429,17 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc ...@@ -8413,11 +8429,17 @@ function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDoc
response = ('response' in xhr) ? xhr.response : xhr.responseText; response = ('response' in xhr) ? xhr.response : xhr.responseText;
} }
// Accessing statusText on an aborted xhr object will
// throw an 'c00c023f error' in IE9 and lower, don't touch it.
if (!(status === ABORTED && msie < 10)) {
statusText = xhr.statusText;
}
completeRequest(callback, completeRequest(callback,
status || xhr.status, status || xhr.status,
response, response,
responseHeaders, responseHeaders,
xhr.statusText || ''); statusText);
} }
}; };
...@@ -8951,7 +8973,7 @@ function $IntervalProvider() { ...@@ -8951,7 +8973,7 @@ function $IntervalProvider() {
interval.cancel = function(promise) { interval.cancel = function(promise) {
if (promise && promise.$$intervalId in intervals) { if (promise && promise.$$intervalId in intervals) {
intervals[promise.$$intervalId].reject('canceled'); intervals[promise.$$intervalId].reject('canceled');
clearInterval(promise.$$intervalId); $window.clearInterval(promise.$$intervalId);
delete intervals[promise.$$intervalId]; delete intervals[promise.$$intervalId];
return true; return true;
} }
...@@ -9585,7 +9607,7 @@ function $LocationProvider(){ ...@@ -9585,7 +9607,7 @@ function $LocationProvider(){
html5Mode = false; html5Mode = false;
/** /**
* @ngdoc property * @ngdoc method
* @name $locationProvider#hashPrefix * @name $locationProvider#hashPrefix
* @description * @description
* @param {string=} prefix Prefix for hash part (containing path and search) * @param {string=} prefix Prefix for hash part (containing path and search)
...@@ -9601,7 +9623,7 @@ function $LocationProvider(){ ...@@ -9601,7 +9623,7 @@ function $LocationProvider(){
}; };
/** /**
* @ngdoc property * @ngdoc method
* @name $locationProvider#html5Mode * @name $locationProvider#html5Mode
* @description * @description
* @param {boolean=} mode Use HTML5 strategy if available. * @param {boolean=} mode Use HTML5 strategy if available.
...@@ -9959,14 +9981,7 @@ var promiseWarning; ...@@ -9959,14 +9981,7 @@ var promiseWarning;
// //
// As an example, consider the following Angular expression: // As an example, consider the following Angular expression:
// //
// {}.toString.constructor(alert("evil JS code")) // {}.toString.constructor('alert("evil JS code")')
//
// We want to prevent this type of access. For the sake of performance, during the lexing phase we
// disallow any "dotted" access to any member named "constructor".
//
// For reflective calls (a[b]) we check that the value of the lookup is not the Function constructor
// while evaluating the expression, which is a stronger but more expensive test. Since reflective
// calls are expensive anyway, this is not such a big deal compared to static dereferencing.
// //
// This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits // This sandboxing technique is not perfect and doesn't aim to be. The goal is to prevent exploits
// against the expression language, but not to prevent exploits that were enabled by exposing // against the expression language, but not to prevent exploits that were enabled by exposing
...@@ -9974,17 +9989,19 @@ var promiseWarning; ...@@ -9974,17 +9989,19 @@ var promiseWarning;
// practice and therefore we are not even trying to protect against interaction with an object // practice and therefore we are not even trying to protect against interaction with an object
// explicitly exposed in this way. // explicitly exposed in this way.
// //
// A developer could foil the name check by aliasing the Function constructor under a different
// name on the scope.
//
// In general, it is not possible to access a Window object from an angular expression unless a // In general, it is not possible to access a Window object from an angular expression unless a
// window or some DOM object that has a reference to window is published onto a Scope. // window or some DOM object that has a reference to window is published onto a Scope.
// Similarly we prevent invocations of function known to be dangerous, as well as assignments to
// native objects.
function ensureSafeMemberName(name, fullExpression) { function ensureSafeMemberName(name, fullExpression) {
if (name === "constructor") { if (name === "__defineGetter__" || name === "__defineSetter__"
|| name === "__lookupGetter__" || name === "__lookupSetter__"
|| name === "__proto__") {
throw $parseMinErr('isecfld', throw $parseMinErr('isecfld',
'Referencing "constructor" field in Angular expressions is disallowed! Expression: {0}', 'Attempting to access a disallowed field in Angular expressions! '
fullExpression); +'Expression: {0}', fullExpression);
} }
return name; return name;
} }
...@@ -10006,11 +10023,34 @@ function ensureSafeObject(obj, fullExpression) { ...@@ -10006,11 +10023,34 @@ function ensureSafeObject(obj, fullExpression) {
throw $parseMinErr('isecdom', throw $parseMinErr('isecdom',
'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}', 'Referencing DOM nodes in Angular expressions is disallowed! Expression: {0}',
fullExpression); fullExpression);
} else if (// block Object so that we can't get hold of dangerous Object.* methods
obj === Object) {
throw $parseMinErr('isecobj',
'Referencing Object in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} }
} }
return obj; return obj;
} }
var CALL = Function.prototype.call;
var APPLY = Function.prototype.apply;
var BIND = Function.prototype.bind;
function ensureSafeFunction(obj, fullExpression) {
if (obj) {
if (obj.constructor === obj) {
throw $parseMinErr('isecfn',
'Referencing Function in Angular expressions is disallowed! Expression: {0}',
fullExpression);
} else if (obj === CALL || obj === APPLY || (BIND && obj === BIND)) {
throw $parseMinErr('isecff',
'Referencing call, apply or bind in Angular expressions is disallowed! Expression: {0}',
fullExpression);
}
}
}
var OPERATORS = { var OPERATORS = {
/* jshint bitwise : false */ /* jshint bitwise : false */
'null':function(){return null;}, 'null':function(){return null;},
...@@ -10645,6 +10685,7 @@ Parser.prototype = { ...@@ -10645,6 +10685,7 @@ Parser.prototype = {
i = indexFn(self, locals), i = indexFn(self, locals),
v, p; v, p;
ensureSafeMemberName(i, parser.text);
if (!o) return undefined; if (!o) return undefined;
v = ensureSafeObject(o[i], parser.text); v = ensureSafeObject(o[i], parser.text);
if (v && v.then && parser.options.unwrapPromises) { if (v && v.then && parser.options.unwrapPromises) {
...@@ -10687,7 +10728,7 @@ Parser.prototype = { ...@@ -10687,7 +10728,7 @@ Parser.prototype = {
var fnPtr = fn(scope, locals, context) || noop; var fnPtr = fn(scope, locals, context) || noop;
ensureSafeObject(context, parser.text); ensureSafeObject(context, parser.text);
ensureSafeObject(fnPtr, parser.text); ensureSafeFunction(fnPtr, parser.text);
// IE stupidity! (IE doesn't have apply for some native functions) // IE stupidity! (IE doesn't have apply for some native functions)
var v = fnPtr.apply var v = fnPtr.apply
...@@ -10796,6 +10837,8 @@ function setter(obj, path, setValue, fullExp, options) { ...@@ -10796,6 +10837,8 @@ function setter(obj, path, setValue, fullExp, options) {
} }
} }
key = ensureSafeMemberName(element.shift(), fullExp); key = ensureSafeMemberName(element.shift(), fullExp);
ensureSafeObject(obj, fullExp);
ensureSafeObject(obj[key], fullExp);
obj[key] = setValue; obj[key] = setValue;
return setValue; return setValue;
} }
...@@ -11215,9 +11258,6 @@ function $ParseProvider() { ...@@ -11215,9 +11258,6 @@ function $ParseProvider() {
* var deferred = $q.defer(); * var deferred = $q.defer();
* *
* setTimeout(function() { * setTimeout(function() {
* // since this fn executes async in a future turn of the event loop, we need to wrap
* // our code into an $apply call so that the model changes are properly observed.
* scope.$apply(function() {
* deferred.notify('About to greet ' + name + '.'); * deferred.notify('About to greet ' + name + '.');
* *
* if (okToGreet(name)) { * if (okToGreet(name)) {
...@@ -11225,7 +11265,6 @@ function $ParseProvider() { ...@@ -11225,7 +11265,6 @@ function $ParseProvider() {
* } else { * } else {
* deferred.reject('Greeting ' + name + ' is not allowed.'); * deferred.reject('Greeting ' + name + ' is not allowed.');
* } * }
* });
* }, 1000); * }, 1000);
* *
* return deferred.promise; * return deferred.promise;
...@@ -12387,7 +12426,7 @@ function $RootScopeProvider(){ ...@@ -12387,7 +12426,7 @@ function $RootScopeProvider(){
if ((value = watch.get(current)) !== (last = watch.last) && if ((value = watch.get(current)) !== (last = watch.last) &&
!(watch.eq !(watch.eq
? equals(value, last) ? equals(value, last)
: (typeof value == 'number' && typeof last == 'number' : (typeof value === 'number' && typeof last === 'number'
&& isNaN(value) && isNaN(last)))) { && isNaN(value) && isNaN(last)))) {
dirty = true; dirty = true;
lastDirtyWatch = watch; lastDirtyWatch = watch;
...@@ -13732,7 +13771,7 @@ function $SceProvider() { ...@@ -13732,7 +13771,7 @@ function $SceProvider() {
/** /**
* @ngdoc method * @ngdoc method
* @name $sce#parse * @name $sce#parseAs
* *
* @description * @description
* Converts Angular {@link guide/expression expression} into a function. This is like {@link * Converts Angular {@link guide/expression expression} into a function. This is like {@link
...@@ -14688,7 +14727,7 @@ function filterFilter() { ...@@ -14688,7 +14727,7 @@ function filterFilter() {
// jshint +W086 // jshint +W086
for (var key in expression) { for (var key in expression) {
(function(path) { (function(path) {
if (typeof expression[path] == 'undefined') return; if (typeof expression[path] === 'undefined') return;
predicates.push(function(value) { predicates.push(function(value) {
return search(path == '$' ? value : (value && value[path]), expression[path]); return search(path == '$' ? value : (value && value[path]), expression[path]);
}); });
...@@ -14843,6 +14882,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { ...@@ -14843,6 +14882,7 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
var match = numStr.match(/([\d\.]+)e(-?)(\d+)/); var match = numStr.match(/([\d\.]+)e(-?)(\d+)/);
if (match && match[2] == '-' && match[3] > fractionSize + 1) { if (match && match[2] == '-' && match[3] > fractionSize + 1) {
numStr = '0'; numStr = '0';
number = 0;
} else { } else {
formatedText = numStr; formatedText = numStr;
hasExponent = true; hasExponent = true;
...@@ -14857,8 +14897,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) { ...@@ -14857,8 +14897,11 @@ function formatNumber(number, pattern, groupSep, decimalSep, fractionSize) {
fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac); fractionSize = Math.min(Math.max(pattern.minFrac, fractionLen), pattern.maxFrac);
} }
var pow = Math.pow(10, fractionSize + 1); // safely round numbers in JS without hitting imprecisions of floating-point arithmetics
number = Math.floor(number * pow + 5) / pow; // inspired by:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
number = +(Math.round(+(number.toString() + 'e' + fractionSize)).toString() + 'e' + -fractionSize);
var fraction = ('' + number).split(DECIMAL_SEP); var fraction = ('' + number).split(DECIMAL_SEP);
var whole = fraction[0]; var whole = fraction[0];
fraction = fraction[1] || ''; fraction = fraction[1] || '';
...@@ -16360,7 +16403,7 @@ var ngFormDirective = formDirectiveFactory(true); ...@@ -16360,7 +16403,7 @@ var ngFormDirective = formDirectiveFactory(true);
*/ */
var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/; var URL_REGEXP = /^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/;
var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i; var EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/i;
var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/; var NUMBER_REGEXP = /^\s*(\-|\+)?(\d+|(\d*(\.\d*)))\s*$/;
var inputType = { var inputType = {
...@@ -16786,15 +16829,29 @@ function validate(ctrl, validatorName, validity, value){ ...@@ -16786,15 +16829,29 @@ function validate(ctrl, validatorName, validity, value){
return validity ? value : undefined; return validity ? value : undefined;
} }
function testFlags(validity, flags) {
var i, flag;
if (flags) {
for (i=0; i<flags.length; ++i) {
flag = flags[i];
if (validity[flag]) {
return true;
}
}
}
return false;
}
function addNativeHtml5Validators(ctrl, validatorName, element) { // Pass validity so that behaviour can be mocked easier.
var validity = element.prop('validity'); function addNativeHtml5Validators(ctrl, validatorName, badFlags, ignoreFlags, validity) {
if (isObject(validity)) { if (isObject(validity)) {
ctrl.$$hasNativeValidators = true;
var validator = function(value) { var validator = function(value) {
// Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can // Don't overwrite previous validation, don't consider valueMissing to apply (ng-required can
// perform the required validation) // perform the required validation)
if (!ctrl.$error[validatorName] && (validity.badInput || validity.customError || if (!ctrl.$error[validatorName] &&
validity.typeMismatch) && !validity.valueMissing) { !testFlags(validity, ignoreFlags) &&
testFlags(validity, badFlags)) {
ctrl.$setValidity(validatorName, false); ctrl.$setValidity(validatorName, false);
return; return;
} }
...@@ -16805,8 +16862,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) { ...@@ -16805,8 +16862,9 @@ function addNativeHtml5Validators(ctrl, validatorName, element) {
} }
function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
var validity = element.prop('validity'); var validity = element.prop(VALIDITY_STATE_PROPERTY);
var placeholder = element[0].placeholder, noevent = {}; var placeholder = element[0].placeholder, noevent = {};
ctrl.$$validityState = validity;
// In composition mode, users are still inputing intermediate text buffer, // In composition mode, users are still inputing intermediate text buffer,
// hold the listener until composition is done. // hold the listener until composition is done.
...@@ -16844,11 +16902,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -16844,11 +16902,11 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
value = trim(value); value = trim(value);
} }
if (ctrl.$viewValue !== value || // If a control is suffering from bad input, browsers discard its value, so it may be
// If the value is still empty/falsy, and there is no `required` error, run validators // necessary to revalidate even if the control's value is the same empty value twice in
// again. This enables HTML5 constraint validation errors to affect Angular validation // a row.
// even when the first character entered causes an error. var revalidate = validity && ctrl.$$hasNativeValidators;
(validity && value === '' && !validity.valueMissing)) { if (ctrl.$viewValue !== value || (value === '' && revalidate)) {
if (scope.$$phase) { if (scope.$$phase) {
ctrl.$setViewValue(value); ctrl.$setViewValue(value);
} else { } else {
...@@ -16954,6 +17012,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -16954,6 +17012,8 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
} }
} }
var numberBadFlags = ['badInput'];
function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
textInputType(scope, element, attr, ctrl, $sniffer, $browser); textInputType(scope, element, attr, ctrl, $sniffer, $browser);
...@@ -16968,7 +17028,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { ...@@ -16968,7 +17028,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
} }
}); });
addNativeHtml5Validators(ctrl, 'number', element); addNativeHtml5Validators(ctrl, 'number', numberBadFlags, null, ctrl.$$validityState);
ctrl.$formatters.push(function(value) { ctrl.$formatters.push(function(value) {
return ctrl.$isEmpty(value) ? '' : '' + value; return ctrl.$isEmpty(value) ? '' : '' + value;
...@@ -17450,7 +17510,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ...@@ -17450,7 +17510,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* This method should be called by validators - i.e. the parser or formatter functions. * This method should be called by validators - i.e. the parser or formatter functions.
* *
* @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign * @param {string} validationErrorKey Name of the validator. the `validationErrorKey` will assign
* to `$error[validationErrorKey]=isValid` so that it is available for data-binding. * to `$error[validationErrorKey]=!isValid` so that it is available for data-binding.
* The `validationErrorKey` should be in camelCase and will get converted into dash-case * The `validationErrorKey` should be in camelCase and will get converted into dash-case
* for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error` * for class name. Example: `myError` will result in `ng-valid-my-error` and `ng-invalid-my-error`
* class and can be bound to as `{{someForm.someControl.$error.myError}}` . * class and can be bound to as `{{someForm.someControl.$error.myError}}` .
...@@ -19834,7 +19894,7 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 }); ...@@ -19834,7 +19894,7 @@ var ngNonBindableDirective = ngDirective({ terminal: true, priority: 1000 });
* When one person, perhaps John, views the document, "John is viewing" will be shown. * When one person, perhaps John, views the document, "John is viewing" will be shown.
* When three people view the document, no explicit number rule is found, so * When three people view the document, no explicit number rule is found, so
* an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category. * an offset of 2 is taken off 3, and Angular uses 1 to decide the plural category.
* In this case, plural category 'one' is matched and "John, Marry and one other person are viewing" * In this case, plural category 'one' is matched and "John, Mary and one other person are viewing"
* is shown. * is shown.
* *
* Note that when you specify offsets, you must provide explicit number rules for * Note that when you specify offsets, you must provide explicit number rules for
...@@ -20716,7 +20776,7 @@ var ngHideDirective = ['$animate', function($animate) { ...@@ -20716,7 +20776,7 @@ var ngHideDirective = ['$animate', function($animate) {
<file name="protractor.js" type="protractor"> <file name="protractor.js" type="protractor">
var colorSpan = element(by.css('span')); var colorSpan = element(by.css('span'));
iit('should check ng-style', function() { it('should check ng-style', function() {
expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)'); expect(colorSpan.getCssValue('color')).toBe('rgba(0, 0, 0, 1)');
element(by.css('input[value=\'set color\']')).click(); element(by.css('input[value=\'set color\']')).click();
expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)'); expect(colorSpan.getCssValue('color')).toBe('rgba(255, 0, 0, 1)');
...@@ -21609,7 +21669,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) { ...@@ -21609,7 +21669,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
// rather then the element. // rather then the element.
(element = optionTemplate.clone()) (element = optionTemplate.clone())
.val(option.id) .val(option.id)
.attr('selected', option.selected) .prop('selected', option.selected)
.text(option.label); .text(option.label);
} }
......
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