diff options
author | Bernhard Posselt <dev@bernhard-posselt.com> | 2014-09-23 09:12:24 +0200 |
---|---|---|
committer | Bernhard Posselt <dev@bernhard-posselt.com> | 2014-09-23 09:46:18 +0200 |
commit | 1c872d3d501b2f9894510dac27b284409b555df1 (patch) | |
tree | b67f692dda9833a14df386bf1a8cd8f5cbdb9c70 /js/vendor/angular/angular.js | |
parent | 4dda6575be15e4f8dba0eca4ab6024705f7147a9 (diff) |
update angular
Diffstat (limited to 'js/vendor/angular/angular.js')
-rw-r--r-- | js/vendor/angular/angular.js | 475 |
1 files changed, 348 insertions, 127 deletions
diff --git a/js/vendor/angular/angular.js b/js/vendor/angular/angular.js index bdc97abb0..a3527e899 100644 --- a/js/vendor/angular/angular.js +++ b/js/vendor/angular/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.0-rc.1 + * @license AngularJS v1.3.0-rc.2 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -71,7 +71,7 @@ function minErr(module, ErrorConstructor) { return match; }); - message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.1/' + + message = message + '\nhttp://errors.angularjs.org/1.3.0-rc.2/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + @@ -1535,7 +1535,7 @@ function reloadWithDebugInfo() { window.location.reload(); } -/* +/** * @name angular.getTestability * @module ng * @description @@ -2122,11 +2122,11 @@ function setupModuleLoader(window) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.0-rc.1', // all of these placeholder strings will be replaced by grunt's + full: '1.3.0-rc.2', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, dot: 0, - codeName: 'backyard-atomicity' + codeName: 'tactile-perception' }; @@ -2957,7 +2957,6 @@ forEach({ function createEventHandler(element, events) { var eventHandler = function (event, type) { - // jQuery specific api event.isDefaultPrevented = function() { return event.defaultPrevented; @@ -2968,13 +2967,34 @@ function createEventHandler(element, events) { if (!eventFnsLength) return; + if (isUndefined(event.immediatePropagationStopped)) { + var originalStopImmediatePropagation = event.stopImmediatePropagation; + event.stopImmediatePropagation = function() { + event.immediatePropagationStopped = true; + + if (event.stopPropagation) { + event.stopPropagation(); + } + + if (originalStopImmediatePropagation) { + originalStopImmediatePropagation.call(event); + } + }; + } + + event.isImmediatePropagationStopped = function() { + return event.immediatePropagationStopped === true; + }; + // Copy event handlers in case event handlers array is modified during execution. if ((eventFnsLength > 1)) { eventFns = shallowCopy(eventFns); } for (var i = 0; i < eventFnsLength; i++) { - eventFns[i].call(element, event); + if (!event.isImmediatePropagationStopped()) { + eventFns[i].call(element, event); + } } }; @@ -3175,11 +3195,12 @@ forEach({ var eventFns = events && events[eventName]; if (eventFns) { - // Create a dummy event to pass to the handlers dummyEvent = { preventDefault: function() { this.defaultPrevented = true; }, isDefaultPrevented: function() { return this.defaultPrevented === true; }, + stopImmediatePropagation: function() { this.immediatePropagationStopped = true; }, + isImmediatePropagationStopped: function() { return this.immediatePropagationStopped === true; }, stopPropagation: noop, type: eventName, target: element @@ -3195,9 +3216,10 @@ forEach({ handlerArgs = extraParameters ? [dummyEvent].concat(extraParameters) : [dummyEvent]; forEach(eventFnsCopy, function(fn) { - fn.apply(element, handlerArgs); + if (!dummyEvent.isImmediatePropagationStopped()) { + fn.apply(element, handlerArgs); + } }); - } } }, function(fn, name){ @@ -5845,6 +5867,31 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { // 'on' and be composed of only English letters. var EVENT_HANDLER_ATTR_REGEXP = /^(on[a-z]+|formaction)$/; + function parseIsolateBindings(scope, directiveName) { + var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/; + + var bindings = {}; + + forEach(scope, function(definition, scopeName) { + var match = definition.match(LOCAL_REGEXP); + + if (!match) { + throw $compileMinErr('iscp', + "Invalid isolate scope definition for directive '{0}'." + + " Definition: {... {1}: '{2}' ...}", + directiveName, scopeName, definition); + } + + bindings[scopeName] = { + attrName: match[3] || scopeName, + mode: match[1], + optional: match[2] === '?' + }; + }); + + return bindings; + } + /** * @ngdoc method * @name $compileProvider#directive @@ -5882,6 +5929,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directive.name = directive.name || name; directive.require = directive.require || (directive.controller && directive.name); directive.restrict = directive.restrict || 'EA'; + if (isObject(directive.scope)) { + directive.$$isolateBindings = parseIsolateBindings(directive.scope, directive.name); + } directives.push(directive); } catch (e) { $exceptionHandler(e); @@ -6928,21 +6978,19 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { newIsolateScopeDirective.bindToController === true) { isolateBindingContext = isolateScopeController.instance; } - forEach(newIsolateScopeDirective.scope, function(definition, scopeName) { - var match = definition.match(LOCAL_REGEXP) || [], - attrName = match[3] || scopeName, - optional = (match[2] == '?'), - mode = match[1], // @, =, or & + + forEach(isolateScope.$$isolateBindings = newIsolateScopeDirective.$$isolateBindings, function(definition, scopeName) { + var attrName = definition.attrName, + optional = definition.optional, + mode = definition.mode, // @, =, or & lastValue, parentGet, parentSet, compare; - isolateScope.$$isolateBindings[scopeName] = mode + attrName; - switch (mode) { case '@': attrs.$observe(attrName, function(value) { - isolateScope[scopeName] = value; + isolateBindingContext[scopeName] = value; }); attrs.$$observers[attrName].$$scope = scope; if( attrs[attrName] ) { @@ -6970,7 +7018,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attrs[attrName], newIsolateScopeDirective.name); }; lastValue = isolateBindingContext[scopeName] = parentGet(scope); - var unwatch = scope.$watch($parse(attrs[attrName], function parentValueWatch(parentValue) { + var parentValueWatch = function parentValueWatch(parentValue) { if (!compare(parentValue, isolateBindingContext[scopeName])) { // we are out of sync and need to copy if (!compare(parentValue, lastValue)) { @@ -6982,7 +7030,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } return lastValue = parentValue; - }), null, parentGet.literal); + }; + parentValueWatch.$stateful = true; + var unwatch = scope.$watch($parse(attrs[attrName], parentValueWatch), null, parentGet.literal); isolateScope.$on('$destroy', unwatch); break; @@ -6992,12 +7042,6 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return parentGet(scope, locals); }; break; - - default: - throw $compileMinErr('iscp', - "Invalid isolate scope definition for directive '{0}'." + - " Definition: {... {1}: '{2}' ...}", - newIsolateScopeDirective.name, scopeName, definition); } }); } @@ -10749,7 +10793,6 @@ var OPERATORS = extend(createMap(), { '/':function(self, locals, a,b){return a(self, locals)/b(self, locals);}, '%':function(self, locals, a,b){return a(self, locals)%b(self, locals);}, '^':function(self, locals, a,b){return a(self, locals)^b(self, locals);}, - '=':noop, '===':function(self, locals, a, b){return a(self, locals)===b(self, locals);}, '!==':function(self, locals, a, b){return a(self, locals)!==b(self, locals);}, '==':function(self, locals, a,b){return a(self, locals)==b(self, locals);}, @@ -10761,9 +10804,11 @@ var OPERATORS = extend(createMap(), { '&&':function(self, locals, a,b){return a(self, locals)&&b(self, locals);}, '||':function(self, locals, a,b){return a(self, locals)||b(self, locals);}, '&':function(self, locals, a,b){return a(self, locals)&b(self, locals);}, -// '|':function(self, locals, a,b){return a|b;}, - '|':function(self, locals, a,b){return b(self, locals)(self, locals, a(self, locals));}, - '!':function(self, locals, a){return !a(self, locals);} + '!':function(self, locals, a){return !a(self, locals);}, + + //Tokenized as operators but parsed as assignment/filters + '=':true, + '|':true }); /* jshint bitwise: true */ var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; @@ -11012,6 +11057,10 @@ Lexer.prototype = { }; +function isConstant(exp) { + return exp.constant; +} + /** * @constructor */ @@ -11126,26 +11175,20 @@ Parser.prototype = { }, unaryFn: function(fn, right) { - return extend(function(self, locals) { + return extend(function $parseUnaryFn(self, locals) { return fn(self, locals, right); }, { - constant:right.constant - }); - }, - - ternaryFn: function(left, middle, right){ - return extend(function(self, locals){ - return left(self, locals) ? middle(self, locals) : right(self, locals); - }, { - constant: left.constant && middle.constant && right.constant + constant:right.constant, + inputs: [right] }); }, - binaryFn: function(left, fn, right) { - return extend(function(self, locals) { + binaryFn: function(left, fn, right, isBranching) { + return extend(function $parseBinaryFn(self, locals) { return fn(self, locals, left, right); }, { - constant:left.constant && right.constant + constant: left.constant && right.constant, + inputs: !isBranching && [left, right] }); }, @@ -11174,12 +11217,12 @@ Parser.prototype = { var left = this.expression(); var token; while ((token = this.expect('|'))) { - left = this.binaryFn(left, token.fn, this.filter()); + left = this.filter(left); } return left; }, - filter: function() { + filter: function(inputFn) { var token = this.expect(); var fn = this.$filter(token.text); var argsFn; @@ -11193,7 +11236,10 @@ Parser.prototype = { } } - return valueFn(function $parseFilter(self, locals, input) { + var inputs = [inputFn].concat(argsFn || []); + + return extend(function $parseFilter(self, locals) { + var input = inputFn(self, locals); if (args) { args[0] = input; @@ -11206,6 +11252,9 @@ Parser.prototype = { } return fn(input); + }, { + constant: !fn.$stateful && inputs.every(isConstant), + inputs: !fn.$stateful && inputs }); }, @@ -11223,9 +11272,11 @@ Parser.prototype = { this.text.substring(0, token.index) + '] can not be assigned to', token); } right = this.ternary(); - return function $parseAssignment(scope, locals) { + return extend(function $parseAssignment(scope, locals) { return left.assign(scope, right(scope, locals), locals); - }; + }, { + inputs: [left, right] + }); } return left; }, @@ -11237,20 +11288,27 @@ Parser.prototype = { if ((token = this.expect('?'))) { middle = this.assignment(); if ((token = this.expect(':'))) { - return this.ternaryFn(left, middle, this.assignment()); + var right = this.assignment(); + + return extend(function $parseTernary(self, locals){ + return left(self, locals) ? middle(self, locals) : right(self, locals); + }, { + constant: left.constant && middle.constant && right.constant + }); + } else { this.throwError('expected :', token); } - } else { - return left; } + + return left; }, logicalOR: function() { var left = this.logicalAND(); var token; while ((token = this.expect('||'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); + left = this.binaryFn(left, token.fn, this.logicalAND(), true); } return left; }, @@ -11259,7 +11317,7 @@ Parser.prototype = { var left = this.equality(); var token; if ((token = this.expect('&&'))) { - left = this.binaryFn(left, token.fn, this.logicalAND()); + left = this.binaryFn(left, token.fn, this.logicalAND(), true); } return left; }, @@ -11394,7 +11452,6 @@ Parser.prototype = { // This is used with json array declaration arrayDeclaration: function () { var elementFns = []; - var allConstant = true; if (this.peekToken().text !== ']') { do { if (this.peek(']')) { @@ -11403,9 +11460,6 @@ Parser.prototype = { } var elementFn = this.expression(); elementFns.push(elementFn); - if (!elementFn.constant) { - allConstant = false; - } } while (this.expect(',')); } this.consume(']'); @@ -11418,41 +11472,38 @@ Parser.prototype = { return array; }, { literal: true, - constant: allConstant + constant: elementFns.every(isConstant), + inputs: elementFns }); }, object: function () { - var keyValues = []; - var allConstant = true; + var keys = [], valueFns = []; if (this.peekToken().text !== '}') { do { if (this.peek('}')) { // Support trailing commas per ES5.1. break; } - var token = this.expect(), - key = token.string || token.text; + var token = this.expect(); + keys.push(token.string || token.text); this.consume(':'); var value = this.expression(); - keyValues.push({key: key, value: value}); - if (!value.constant) { - allConstant = false; - } + valueFns.push(value); } while (this.expect(',')); } this.consume('}'); return extend(function $parseObjectLiteral(self, locals) { var object = {}; - for (var i = 0, ii = keyValues.length; i < ii; i++) { - var keyValue = keyValues[i]; - object[keyValue.key] = keyValue.value(self, locals); + for (var i = 0, ii = valueFns.length; i < ii; i++) { + object[keys[i]] = valueFns[i](self, locals); } return object; }, { literal: true, - constant: allConstant + constant: valueFns.every(isConstant), + inputs: valueFns }); } }; @@ -11534,7 +11585,7 @@ function getterFn(path, options, fullExp) { if (pathKeysLength < 6) { fn = cspSafeGetterFn(pathKeys[0], pathKeys[1], pathKeys[2], pathKeys[3], pathKeys[4], fullExp); } else { - fn = function(scope, locals) { + fn = function cspSafeGetter(scope, locals) { var i = 0, val; do { val = cspSafeGetterFn(pathKeys[i++], pathKeys[i++], pathKeys[i++], pathKeys[i++], @@ -11563,14 +11614,14 @@ function getterFn(path, options, fullExp) { var evaledFnGetter = new Function('s', 'l', code); // s=scope, l=locals /* jshint +W054 */ evaledFnGetter.toString = valueFn(code); - evaledFnGetter.assign = function(self, value) { - return setter(self, path, value, path); - }; fn = evaledFnGetter; } fn.sharedGetter = true; + fn.assign = function(self, value) { + return setter(self, path, value, path); + }; getterFnCache[path] = fn; return fn; } @@ -11679,6 +11730,8 @@ function $ParseProvider() { parsedExpression = wrapSharedExpression(parsedExpression); parsedExpression.$$watchDelegate = parsedExpression.literal ? oneTimeLiteralWatchDelegate : oneTimeWatchDelegate; + } else if (parsedExpression.inputs) { + parsedExpression.$$watchDelegate = inputsWatchDelegate; } cache[cacheKey] = parsedExpression; @@ -11693,6 +11746,88 @@ function $ParseProvider() { } }; + function collectExpressionInputs(inputs, list) { + for (var i = 0, ii = inputs.length; i < ii; i++) { + var input = inputs[i]; + if (!input.constant) { + if (input.inputs) { + collectExpressionInputs(input.inputs, list); + } else if (list.indexOf(input) === -1) { // TODO(perf) can we do better? + list.push(input); + } + } + } + + return list; + } + + function expressionInputDirtyCheck(newValue, oldValueOfValue) { + + if (newValue == null || oldValueOfValue == null) { // null/undefined + return newValue === oldValueOfValue; + } + + if (typeof newValue === 'object') { + + // attempt to convert the value to a primitive type + // TODO(docs): add a note to docs that by implementing valueOf even objects and arrays can + // be cheaply dirty-checked + newValue = newValue.valueOf(); + + if (typeof newValue === 'object') { + // objects/arrays are not supported - deep-watching them would be too expensive + return false; + } + + // fall-through to the primitive equality check + } + + //Primitive or NaN + return newValue === oldValueOfValue || (newValue !== newValue && oldValueOfValue !== oldValueOfValue); + } + + function inputsWatchDelegate(scope, listener, objectEquality, parsedExpression) { + var inputExpressions = parsedExpression.$$inputs || + (parsedExpression.$$inputs = collectExpressionInputs(parsedExpression.inputs, [])); + + var lastResult; + + if (inputExpressions.length === 1) { + var oldInputValue = expressionInputDirtyCheck; // init to something unique so that equals check fails + inputExpressions = inputExpressions[0]; + return scope.$watch(function expressionInputWatch(scope) { + var newInputValue = inputExpressions(scope); + if (!expressionInputDirtyCheck(newInputValue, oldInputValue)) { + lastResult = parsedExpression(scope); + oldInputValue = newInputValue && newInputValue.valueOf(); + } + return lastResult; + }, listener, objectEquality); + } + + var oldInputValueOfValues = []; + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + oldInputValueOfValues[i] = expressionInputDirtyCheck; // init to something unique so that equals check fails + } + + return scope.$watch(function expressionInputsWatch(scope) { + var changed = false; + + for (var i = 0, ii = inputExpressions.length; i < ii; i++) { + var newInputValue = inputExpressions[i](scope); + if (changed || (changed = !expressionInputDirtyCheck(newInputValue, oldInputValueOfValues[i]))) { + oldInputValueOfValues[i] = newInputValue && newInputValue.valueOf(); + } + } + + if (changed) { + lastResult = parsedExpression(scope); + } + + return lastResult; + }, listener, objectEquality); + } + function oneTimeWatchDelegate(scope, listener, objectEquality, parsedExpression) { var unwatch, lastValue; return unwatch = scope.$watch(function oneTimeWatch(scope) { @@ -11758,7 +11893,18 @@ function $ParseProvider() { // initial value is defined (for bind-once) return isDefined(value) ? result : value; }; - fn.$$watchDelegate = parsedExpression.$$watchDelegate; + + // Propagate $$watchDelegates other then inputsWatchDelegate + if (parsedExpression.$$watchDelegate && + parsedExpression.$$watchDelegate !== inputsWatchDelegate) { + fn.$$watchDelegate = parsedExpression.$$watchDelegate; + } else if (!interceptorFn.$stateful) { + // If there is an interceptor, but no watchDelegate then treat the interceptor like + // we treat filters - it is assumed to be a pure function unless flagged with $stateful + fn.$$watchDelegate = inputsWatchDelegate; + fn.inputs = [parsedExpression]; + } + return fn; } }]; @@ -12496,7 +12642,7 @@ function $RootScopeProvider(){ this.$$postDigestQueue = []; this.$$listeners = {}; this.$$listenerCount = {}; - this.$$isolateBindings = {}; + this.$$isolateBindings = null; this.$$applyAsyncQueue = []; } @@ -12877,6 +13023,8 @@ function $RootScopeProvider(){ * de-registration function is executed, the internal watch operation is terminated. */ $watchCollection: function(obj, listener) { + $watchCollectionInterceptor.$stateful = true; + var self = this; // the current value, updated on each dirty-check run var newValue; @@ -16199,10 +16347,11 @@ var uppercaseFilter = valueFn(uppercase); * * @description * Creates a new array or string containing only a specified number of elements. The elements - * are taken from either the beginning or the end of the source array or string, as specified by - * the value and sign (positive or negative) of `limit`. + * are taken from either the beginning or the end of the source array, string or number, as specified by + * the value and sign (positive or negative) of `limit`. If a number is used as input, it is + * converted to a string. * - * @param {Array|string} input Source array or string to be limited. + * @param {Array|string|number} input Source array, string or number to be limited. * @param {string|number} limit The length of the returned array or string. If the `limit` number * is positive, `limit` number of items from the beginning of the source array/string are copied. * If the number is negative, `limit` number of items from the end of the source array/string @@ -16218,8 +16367,10 @@ var uppercaseFilter = valueFn(uppercase); .controller('ExampleController', ['$scope', function($scope) { $scope.numbers = [1,2,3,4,5,6,7,8,9]; $scope.letters = "abcdefghi"; + $scope.longNumber = 2345432342; $scope.numLimit = 3; $scope.letterLimit = 3; + $scope.longNumberLimit = 3; }]); </script> <div ng-controller="ExampleController"> @@ -16227,19 +16378,25 @@ var uppercaseFilter = valueFn(uppercase); <p>Output numbers: {{ numbers | limitTo:numLimit }}</p> Limit {{letters}} to: <input type="integer" ng-model="letterLimit"> <p>Output letters: {{ letters | limitTo:letterLimit }}</p> + Limit {{longNumber}} to: <input type="integer" ng-model="longNumberLimit"> + <p>Output long number: {{ longNumber | limitTo:longNumberLimit }}</p> </div> </file> <file name="protractor.js" type="protractor"> var numLimitInput = element(by.model('numLimit')); var letterLimitInput = element(by.model('letterLimit')); + var longNumberLimitInput = element(by.model('longNumberLimit')); var limitedNumbers = element(by.binding('numbers | limitTo:numLimit')); var limitedLetters = element(by.binding('letters | limitTo:letterLimit')); + var limitedLongNumber = element(by.binding('longNumber | limitTo:longNumberLimit')); it('should limit the number array to first three items', function() { expect(numLimitInput.getAttribute('value')).toBe('3'); expect(letterLimitInput.getAttribute('value')).toBe('3'); + expect(longNumberLimitInput.getAttribute('value')).toBe('3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3]'); expect(limitedLetters.getText()).toEqual('Output letters: abc'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 234'); }); it('should update the output when -3 is entered', function() { @@ -16247,8 +16404,11 @@ var uppercaseFilter = valueFn(uppercase); numLimitInput.sendKeys('-3'); letterLimitInput.clear(); letterLimitInput.sendKeys('-3'); + longNumberLimitInput.clear(); + longNumberLimitInput.sendKeys('-3'); expect(limitedNumbers.getText()).toEqual('Output numbers: [7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: ghi'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 342'); }); it('should not exceed the maximum size of input array', function() { @@ -16256,14 +16416,18 @@ var uppercaseFilter = valueFn(uppercase); numLimitInput.sendKeys('100'); letterLimitInput.clear(); letterLimitInput.sendKeys('100'); + longNumberLimitInput.clear(); + longNumberLimitInput.sendKeys('100'); expect(limitedNumbers.getText()).toEqual('Output numbers: [1,2,3,4,5,6,7,8,9]'); expect(limitedLetters.getText()).toEqual('Output letters: abcdefghi'); + expect(limitedLongNumber.getText()).toEqual('Output long number: 2345432342'); }); </file> </example> */ function limitToFilter(){ return function(input, limit) { + if (isNumber(input)) input = input.toString(); if (!isArray(input) && !isString(input)) return input; if (Math.abs(Number(limit)) === Infinity) { @@ -18436,11 +18600,11 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) { element.on('change', listener); ctrl.$render = function() { - element.val(ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue); + element.val(ctrl.$isEmpty(ctrl.$modelValue) ? '' : ctrl.$viewValue); }; } -function weekParser(isoWeek) { +function weekParser(isoWeek, existingDate) { if (isDate(isoWeek)) { return isoWeek; } @@ -18451,9 +18615,21 @@ function weekParser(isoWeek) { if (parts) { var year = +parts[1], week = +parts[2], + hours = 0, + minutes = 0, + seconds = 0, + milliseconds = 0, firstThurs = getFirstThursdayOfYear(year), addDays = (week - 1) * 7; - return new Date(year, 0, firstThurs.getDate() + addDays); + + if (existingDate) { + hours = existingDate.getHours(); + minutes = existingDate.getMinutes(); + seconds = existingDate.getSeconds(); + milliseconds = existingDate.getMilliseconds(); + } + + return new Date(year, 0, firstThurs.getDate() + addDays, hours, minutes, seconds, milliseconds); } } @@ -18461,7 +18637,7 @@ function weekParser(isoWeek) { } function createDateParser(regexp, mapping) { - return function(iso) { + return function(iso, date) { var parts, map; if (isDate(iso)) { @@ -18483,14 +18659,26 @@ function createDateParser(regexp, mapping) { if (parts) { parts.shift(); - map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0 }; + if (date) { + map = { + yyyy: date.getFullYear(), + MM: date.getMonth() + 1, + dd: date.getDate(), + HH: date.getHours(), + mm: date.getMinutes(), + ss: date.getSeconds(), + sss: date.getMilliseconds() + }; + } else { + map = { yyyy: 1970, MM: 1, dd: 1, HH: 0, mm: 0, ss: 0, sss: 0 }; + } forEach(parts, function(part, index) { if (index < mapping.length) { map[mapping[index]] = +part; } }); - return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0); + return new Date(map.yyyy, map.MM - 1, map.dd, map.HH, map.mm, map.ss || 0, map.sss || 0); } } @@ -18508,7 +18696,12 @@ function createDateInputType(type, regexp, parseDate, format) { ctrl.$parsers.push(function(value) { if (ctrl.$isEmpty(value)) return null; if (regexp.test(value)) { - var parsedDate = parseDate(value); + var previousDate = ctrl.$modelValue; + if (previousDate && timezone === 'UTC') { + var timezoneOffset = 60000 * previousDate.getTimezoneOffset(); + previousDate = new Date(previousDate.getTime() + timezoneOffset); + } + var parsedDate = parseDate(value, previousDate); if (timezone === 'UTC') { parsedDate.setMinutes(parsedDate.getMinutes() - parsedDate.getTimezoneOffset()); } @@ -18628,8 +18821,7 @@ function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'url'; - ctrl.$validators.url = function(modelValue, viewValue) { - var value = modelValue || viewValue; + ctrl.$validators.url = function(value) { return ctrl.$isEmpty(value) || URL_REGEXP.test(value); }; } @@ -18641,8 +18833,7 @@ function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) { stringBasedInputType(ctrl); ctrl.$$parserName = 'email'; - ctrl.$validators.email = function(modelValue, viewValue) { - var value = modelValue || viewValue; + ctrl.$validators.email = function(value) { return ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value); }; } @@ -18696,7 +18887,7 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt element[0].checked = ctrl.$viewValue; }; - // Override the standard `$isEmpty` because a value of `false` means empty in a checkbox. + // Override the standard `$isEmpty` because an empty checkbox is never equal to the trueValue ctrl.$isEmpty = function(value) { return value !== trueValue; }; @@ -19155,7 +19346,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ * default. The `checkboxInputType` directive does this because in its case a value of `false` * implies empty. * - * @param {*} value Reference to check. + * @param {*} value Model value to check. * @returns {boolean} True if `value` is empty. */ this.$isEmpty = function(value) { @@ -19342,9 +19533,11 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // check parser error if (!processParseErrors(parseValid)) { + validationDone(false); return; } if (!processSyncValidators()) { + validationDone(false); return; } processAsyncValidators(); @@ -19362,7 +19555,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ forEach(ctrl.$asyncValidators, function(v, name) { setValidity(name, null); }); - validationDone(); return false; } } @@ -19380,7 +19572,6 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ forEach(ctrl.$asyncValidators, function(v, name) { setValidity(name, null); }); - validationDone(); return false; } return true; @@ -19388,6 +19579,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ function processAsyncValidators() { var validatorPromises = []; + var allValid = true; forEach(ctrl.$asyncValidators, function(validator, name) { var promise = validator(modelValue, viewValue); if (!isPromiseLike(promise)) { @@ -19398,13 +19590,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ < |