diff --git a/src/lib/angular/angular-animate.js b/src/lib/angular/angular-animate.js index 8cd592d..7bd0d7a 100644 --- a/src/lib/angular/angular-animate.js +++ b/src/lib/angular/angular-animate.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.13 + * @license AngularJS v1.3.15 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ diff --git a/src/lib/angular/angular-mocks.js b/src/lib/angular/angular-mocks.js index dbbe3c6..cbf36ff 100644 --- a/src/lib/angular/angular-mocks.js +++ b/src/lib/angular/angular-mocks.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.13 + * @license AngularJS v1.3.15 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -1809,6 +1809,77 @@ angular.mock.$RootElementProvider = function() { }; }; +/** + * @ngdoc service + * @name $controller + * @description + * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing + * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. + * + * + * ## Example + * + * ```js + * + * // Directive definition ... + * + * myMod.directive('myDirective', { + * controller: 'MyDirectiveController', + * bindToController: { + * name: '@' + * } + * }); + * + * + * // Controller definition ... + * + * myMod.controller('MyDirectiveController', ['log', function($log) { + * $log.info(this.name); + * })]; + * + * + * // In a test ... + * + * describe('myDirectiveController', function() { + * it('should write the bound name to the log', inject(function($controller, $log) { + * var ctrl = $controller('MyDirective', { /* no locals */ }, { name: 'Clark Kent' }); + * expect(ctrl.name).toEqual('Clark Kent'); + * expect($log.info.logs).toEqual(['Clark Kent']); + * }); + * }); + * + * ``` + * + * @param {Function|string} constructor If called with a function then it's considered to be the + * controller constructor function. Otherwise it's considered to be a string which is used + * to retrieve the controller constructor using the following steps: + * + * * check if a controller with given name is registered via `$controllerProvider` + * * check if evaluating the string on the current scope returns a constructor + * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global + * `window` object (not recommended) + * + * The string can use the `controller as property` syntax, where the controller instance is published + * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this + * to work correctly. + * + * @param {Object} locals Injection locals for Controller. + * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used + * to simulate the `bindToController` feature and simplify certain kinds of tests. + * @return {Object} Instance of given controller. + */ +angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { + return function(expression, locals, later, ident) { + if (later && typeof later === 'object') { + var create = $delegate(expression, locals, true, ident); + angular.extend(create.instance, later); + return create(); + } + return $delegate(expression, locals, later, ident); + }; +}]; + + /** * @ngdoc module * @name ngMock @@ -1837,6 +1908,7 @@ angular.module('ngMock', ['ng']).provider({ $provide.decorator('$$rAF', angular.mock.$RAFDecorator); $provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator); $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); + $provide.decorator('$controller', angular.mock.$ControllerDecorator); }]); /** diff --git a/src/lib/angular/angular-route.js b/src/lib/angular/angular-route.js index 6179e18..68150e7 100644 --- a/src/lib/angular/angular-route.js +++ b/src/lib/angular/angular-route.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.13 + * @license AngularJS v1.3.15 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ diff --git a/src/lib/angular/angular.js b/src/lib/angular/angular.js index 4f4f492..e488352 100644 --- a/src/lib/angular/angular.js +++ b/src/lib/angular/angular.js @@ -1,5 +1,5 @@ /** - * @license AngularJS v1.3.13 + * @license AngularJS v1.3.15 * (c) 2010-2014 Google, Inc. http://angularjs.org * License: MIT */ @@ -54,7 +54,7 @@ function minErr(module, ErrorConstructor) { return match; }); - message = message + '\nhttp://errors.angularjs.org/1.3.13/' + + message = message + '\nhttp://errors.angularjs.org/1.3.15/' + (module ? module + '/' : '') + code; for (i = 2; i < arguments.length; i++) { message = message + (i == 2 ? '?' : '&') + 'p' + (i - 2) + '=' + @@ -546,6 +546,12 @@ function isString(value) {return typeof value === 'string';} * @description * Determines if a reference is a `Number`. * + * This includes the "special" numbers `NaN`, `+Infinity` and `-Infinity`. + * + * If you wish to exclude these then you can use the native + * [`isFinite'](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/isFinite) + * method. + * * @param {*} value Reference to check. * @returns {boolean} True if `value` is a `Number`. */ @@ -914,10 +920,11 @@ function equals(o1, o2) { } else if (isDate(o1)) { if (!isDate(o2)) return false; return equals(o1.getTime(), o2.getTime()); - } else if (isRegExp(o1) && isRegExp(o2)) { - return o1.toString() == o2.toString(); + } else if (isRegExp(o1)) { + return isRegExp(o2) ? o1.toString() == o2.toString() : false; } else { - if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || isArray(o2)) return false; + if (isScope(o1) || isScope(o2) || isWindow(o1) || isWindow(o2) || + isArray(o2) || isDate(o2) || isRegExp(o2)) return false; keySet = {}; for (key in o1) { if (key.charAt(0) === '$' || isFunction(o1[key])) continue; @@ -2121,11 +2128,11 @@ function toDebugString(obj) { * - `codeName` – `{string}` – Code name of the release, such as "jiggling-armfat". */ var version = { - full: '1.3.13', // all of these placeholder strings will be replaced by grunt's + full: '1.3.15', // all of these placeholder strings will be replaced by grunt's major: 1, // package task minor: 3, - dot: 13, - codeName: 'meticulous-riffleshuffle' + dot: 15, + codeName: 'locality-filtration' }; @@ -2262,6 +2269,17 @@ function publishExternalAPI(angular) { ]); } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* global JQLitePrototype: true, addEventListenerFn: true, removeEventListenerFn: true, @@ -4685,6 +4703,7 @@ var $AnimateProvider = ['$provide', function($provide) { * @return {Promise} the animation callback promise */ leave: function(element, options) { + applyStyles(element, options); element.remove(); return asyncPromise(); }, @@ -5089,11 +5108,19 @@ function Browser(window, document, $log, $sniffer) { fireUrlChange(); } + function getCurrentState() { + try { + return history.state; + } catch (e) { + // MSIE can reportedly throw when there is no state (UNCONFIRMED). + } + } + // This variable should be used *only* inside the cacheState function. var lastCachedState = null; function cacheState() { // This should be the only place in $browser where `history.state` is read. - cachedState = window.history.state; + cachedState = getCurrentState(); cachedState = isUndefined(cachedState) ? null : cachedState; // Prevent callbacks fo fire twice if both hashchange & popstate were fired. @@ -5690,7 +5717,7 @@ function $CacheFactoryProvider() { * the document, but it must be a descendent of the {@link ng.$rootElement $rootElement} (IE, * element with ng-app attribute), otherwise the template will be ignored. * - * Adding via the $templateCache service: + * Adding via the `$templateCache` service: * * ```js * var myApp = angular.module('myApp', []); @@ -5718,6 +5745,17 @@ function $TemplateCacheProvider() { }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + /* ! VARIABLE/FUNCTION NAMING CONVENTIONS THAT APPLY TO THIS FILE! * * DOM-related variables: @@ -5929,7 +5967,8 @@ function $TemplateCacheProvider() { * Require another directive and inject its controller as the fourth argument to the linking function. The * `require` takes a string name (or array of strings) of the directive(s) to pass in. If an array is used, the * injected argument will be an array in corresponding order. If no such directive can be - * found, or if the directive does not have a controller, then an error is raised. The name can be prefixed with: + * found, or if the directive does not have a controller, then an error is raised (unless no link function + * is specified, in which case error checking is skipped). The name can be prefixed with: * * * (no prefix) - Locate the required controller on the current element. Throw an error if not found. * * `?` - Attempt to locate the required controller or pass `null` to the `link` fn if not found. @@ -10479,7 +10518,15 @@ function $LocaleProvider() { mediumDate: 'MMM d, y', shortDate: 'M/d/yy', mediumTime: 'h:mm:ss a', - shortTime: 'h:mm a' + shortTime: 'h:mm a', + ERANAMES: [ + "Before Christ", + "Anno Domini" + ], + ERAS: [ + "BC", + "AD" + ] }, pluralCat: function(num) { @@ -11487,6 +11534,7 @@ function $LocationProvider() { + @@ -11617,6 +11665,17 @@ function $LogProvider() { }]; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + var $parseMinErr = minErr('$parse'); // Sandboxing Angular Expressions @@ -13552,9 +13611,27 @@ function $RootScopeProvider() { return TTL; }; + function createChildScopeClass(parent) { + function ChildScope() { + this.$$watchers = this.$$nextSibling = + this.$$childHead = this.$$childTail = null; + this.$$listeners = {}; + this.$$listenerCount = {}; + this.$$watchersCount = 0; + this.$id = nextUid(); + this.$$ChildScope = null; + } + ChildScope.prototype = parent; + return ChildScope; + } + this.$get = ['$injector', '$exceptionHandler', '$parse', '$browser', function($injector, $exceptionHandler, $parse, $browser) { + function destroyChildScope($event) { + $event.currentScope.$$destroyed = true; + } + /** * @ngdoc type * @name $rootScope.Scope @@ -13677,15 +13754,7 @@ function $RootScopeProvider() { // Only create a child scope class if somebody asks for one, // but cache it to allow the VM to optimize lookups. if (!this.$$ChildScope) { - this.$$ChildScope = function ChildScope() { - this.$$watchers = this.$$nextSibling = - this.$$childHead = this.$$childTail = null; - this.$$listeners = {}; - this.$$listenerCount = {}; - this.$id = nextUid(); - this.$$ChildScope = null; - }; - this.$$ChildScope.prototype = this; + this.$$ChildScope = createChildScopeClass(this); } child = new this.$$ChildScope(); } @@ -13703,13 +13772,9 @@ function $RootScopeProvider() { // prototypically. In all other cases, this property needs to be set // when the parent scope is destroyed. // The listener needs to be added after the parent is set - if (isolate || parent != this) child.$on('$destroy', destroyChild); + if (isolate || parent != this) child.$on('$destroy', destroyChildScope); return child; - - function destroyChild() { - child.$$destroyed = true; - } }, /** @@ -14870,6 +14935,17 @@ function $$SanitizeUriProvider() { }; } +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Any commits to this file should be reviewed with security in mind. * + * Changes to this file can potentially create security vulnerabilities. * + * An approval from 2 Core members with history of modifying * + * this file is required. * + * * + * Does the change somehow allow for arbitrary javascript to be executed? * + * Or allows for someone to change the prototype of built-in objects? * + * Or gives undesired access to variables likes document or window? * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + var $sceMinErr = minErr('$sce'); var SCE_CONTEXTS = { @@ -16045,7 +16121,7 @@ function $TemplateRequestProvider() { }; return $http.get(tpl, httpOptions) - .finally(function() { + ['finally'](function() { handleRequestFn.totalPendingRequests--; }) .then(function(response) { @@ -16741,8 +16817,8 @@ function createPredicateFn(expression, comparator, matchAgainstAnyProp) { } function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatchWholeObject) { - var actualType = typeof actual; - var expectedType = typeof expected; + var actualType = (actual !== null) ? typeof actual : 'null'; + var expectedType = (expected !== null) ? typeof expected : 'null'; if ((expectedType === 'string') && (expected.charAt(0) === '!')) { return !deepCompare(actual, expected.substring(1), comparator, matchAgainstAnyProp); @@ -16767,7 +16843,7 @@ function deepCompare(actual, expected, comparator, matchAgainstAnyProp, dontMatc } else if (expectedType === 'object') { for (key in expected) { var expectedVal = expected[key]; - if (isFunction(expectedVal)) { + if (isFunction(expectedVal) || isUndefined(expectedVal)) { continue; } @@ -17081,6 +17157,14 @@ function ampmGetter(date, formats) { return date.getHours() < 12 ? formats.AMPMS[0] : formats.AMPMS[1]; } +function eraGetter(date, formats) { + return date.getFullYear() <= 0 ? formats.ERAS[0] : formats.ERAS[1]; +} + +function longEraGetter(date, formats) { + return date.getFullYear() <= 0 ? formats.ERANAMES[0] : formats.ERANAMES[1]; +} + var DATE_FORMATS = { yyyy: dateGetter('FullYear', 4), yy: dateGetter('FullYear', 2, 0, true), @@ -17107,10 +17191,14 @@ var DATE_FORMATS = { a: ampmGetter, Z: timeZoneGetter, ww: weekGetter(2), - w: weekGetter(1) + w: weekGetter(1), + G: eraGetter, + GG: eraGetter, + GGG: eraGetter, + GGGG: longEraGetter }; -var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|w+))(.*)/, +var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, NUMBER_STRING = /^\-?\d+$/; /** @@ -17147,6 +17235,8 @@ var DATE_FORMATS_SPLIT = /((?:[^yMdHhmsaZEw']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|d * * `'Z'`: 4 digit (+sign) representation of the timezone offset (-1200-+1200) * * `'ww'`: Week of year, padded (00-53). Week 01 is the week with the first Thursday of the year * * `'w'`: Week of year (0-53). Week 1 is the week with the first Thursday of the year + * * `'G'`, `'GG'`, `'GGG'`: The abbreviated form of the era string (e.g. 'AD') + * * `'GGGG'`: The long form of the era string (e.g. 'Anno Domini') * * `format` string can also be one of the following predefined * {@link guide/i18n localizable formats}: @@ -17480,6 +17570,43 @@ function limitToFilter() { * @param {boolean=} reverse Reverse the order of the array. * @returns {Array} Sorted copy of the source array. * + * + * @example + * The example below demonstrates a simple ngRepeat, where the data is sorted + * by age in descending order (predicate is set to `'-age'`). + * `reverse` is not set, which means it defaults to `false`. + + + +
+ + + + + + + + + + + +
NamePhone NumberAge
{{friend.name}}{{friend.phone}}{{friend.age}}
+
+
+
+ * + * The predicate and reverse parameters can be controlled dynamically through scope properties, + * as shown in the next example. * @example @@ -17854,20 +17981,24 @@ var htmlAnchorDirective = valueFn({ * * @description * - * We shouldn't do this, because it will make the button enabled on Chrome/Firefox but not on IE8 and older IEs: + * This directive sets the `disabled` attribute on the element if the + * {@link guide/expression expression} inside `ngDisabled` evaluates to truthy. + * + * A special directive is necessary because we cannot use interpolation inside the `disabled` + * attribute. The following example would make the button enabled on Chrome/Firefox + * but not on older IEs: + * * ```html - *
- * + * + *
+ * *
* ``` * - * The HTML specification does not require browsers to preserve the values of boolean attributes - * such as disabled. (Their presence means true and their absence means false.) + * This is because the HTML specification does not require browsers to preserve the values of + * boolean attributes such as `disabled` (Their presence means true and their absence means false.) * If we put an Angular interpolation expression into such an attribute then the * binding information would be lost when the browser removes the attribute. - * The `ngDisabled` directive solves this problem for the `disabled` attribute. - * This complementary directive is not removed by the browser and so provides - * a permanent reliable place to store the binding information. * * @example @@ -17886,7 +18017,7 @@ var htmlAnchorDirective = valueFn({ * * @element INPUT * @param {expression} ngDisabled If the {@link guide/expression expression} is truthy, - * then special attribute "disabled" will be set on the element + * then the `disabled` attribute will be set on the element */ @@ -18434,7 +18565,7 @@ function FormController(element, attrs, $scope, $animate, $interpolate) { * * # Alias: {@link ng.directive:ngForm `ngForm`} * - * In Angular forms can be nested. This means that the outer form is valid when all of the child + * In Angular, forms can be nested. This means that the outer form is valid when all of the child * forms are valid as well. However, browsers do not allow nesting of `
` elements, so * Angular provides the {@link ng.directive:ngForm `ngForm`} directive which behaves identically to * `` but can be nested. This allows you to have nested forms, which is very useful when @@ -18572,10 +18703,12 @@ var formDirectiveFactory = function(isNgForm) { name: 'form', restrict: isNgForm ? 'EAC' : 'E', controller: FormController, - compile: function ngFormCompile(formElement) { + compile: function ngFormCompile(formElement, attr) { // Setup initial state of the control formElement.addClass(PRISTINE_CLASS).addClass(VALID_CLASS); + var nameAttr = attr.name ? 'name' : (isNgForm && attr.ngForm ? 'ngForm' : false); + return { pre: function ngFormPreLink(scope, formElement, attr, controller) { // if `action` attr is not present on the form, prevent the default action (submission) @@ -18606,23 +18739,21 @@ var formDirectiveFactory = function(isNgForm) { }); } - var parentFormCtrl = controller.$$parentForm, - alias = controller.$name; + var parentFormCtrl = controller.$$parentForm; - if (alias) { - setter(scope, null, alias, controller, alias); - attr.$observe(attr.name ? 'name' : 'ngForm', function(newValue) { - if (alias === newValue) return; - setter(scope, null, alias, undefined, alias); - alias = newValue; - setter(scope, null, alias, controller, alias); - parentFormCtrl.$$renameControl(controller, alias); + if (nameAttr) { + setter(scope, null, controller.$name, controller, controller.$name); + attr.$observe(nameAttr, function(newValue) { + if (controller.$name === newValue) return; + setter(scope, null, controller.$name, undefined, controller.$name); + parentFormCtrl.$$renameControl(controller, newValue); + setter(scope, null, controller.$name, controller, controller.$name); }); } formElement.on('$destroy', function() { parentFormCtrl.$removeControl(controller); - if (alias) { - setter(scope, null, alias, undefined, alias); + if (nameAttr) { + setter(scope, null, attr[nameAttr], undefined, controller.$name); } extend(controller, nullFormCtrl); //stop propagating child destruction handlers upwards }); @@ -19888,7 +20019,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { return value; }); - if (attr.min || attr.ngMin) { + if (isDefined(attr.min) || attr.ngMin) { var minVal; ctrl.$validators.min = function(value) { return ctrl.$isEmpty(value) || isUndefined(minVal) || value >= minVal; @@ -19904,7 +20035,7 @@ function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) { }); } - if (attr.max || attr.ngMax) { + if (isDefined(attr.max) || attr.ngMax) { var maxVal; ctrl.$validators.max = function(value) { return ctrl.$isEmpty(value) || isUndefined(maxVal) || value <= maxVal; @@ -22595,8 +22726,8 @@ is set to `true`. The parse error is stored in `ngModel.$error.parse`. * data-binding. Notice how different directives (`contenteditable`, `ng-model`, and `required`) * collaborate together to achieve the desired result. * - * Note that `contenteditable` is an HTML5 attribute, which tells the browser to let the element - * contents be edited in place by the user. This will not work on older browsers. + * `contenteditable` is an HTML5 attribute, which tells the browser to let the element + * contents be edited in place by the user. * * We are using the {@link ng.service:$sce $sce} service here and include the {@link ngSanitize $sanitize} * module to automatically remove "bad" content like inline event listener (e.g. ``). @@ -22710,6 +22841,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ngModelGet = parsedNgModel, ngModelSet = parsedNgModelAssign, pendingDebounce = null, + parserValid, ctrl = this; this.$$setOptions = function(options) { @@ -22982,16 +23114,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // 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) { + ctrl.$$runValidators(modelValue, viewValue, function(allValid) { // If there was no change in validity, don't update the model // This prevents changing an invalid modelValue to undefined if (!allowInvalid && prevValid !== allValid) { @@ -23009,12 +23137,12 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ }; - this.$$runValidators = function(parseValid, modelValue, viewValue, doneCallback) { + this.$$runValidators = function(modelValue, viewValue, doneCallback) { currentValidationRunId++; var localValidationRunId = currentValidationRunId; // check parser error - if (!processParseErrors(parseValid)) { + if (!processParseErrors()) { validationDone(false); return; } @@ -23024,21 +23152,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ } processAsyncValidators(); - function processParseErrors(parseValid) { + function processParseErrors() { var errorKey = ctrl.$$parserName || 'parse'; - if (parseValid === undefined) { + if (parserValid === undefined) { setValidity(errorKey, null); } else { - setValidity(errorKey, parseValid); - if (!parseValid) { + if (!parserValid) { forEach(ctrl.$validators, function(v, name) { setValidity(name, null); }); forEach(ctrl.$asyncValidators, function(v, name) { setValidity(name, null); }); - return false; } + // Set the parse error last, to prevent unsetting it, should a $validators key == parserName + setValidity(errorKey, parserValid); + return parserValid; } return true; } @@ -23133,7 +23262,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ this.$$parseAndValidate = function() { var viewValue = ctrl.$$lastCommittedViewValue; var modelValue = viewValue; - var parserValid = isUndefined(modelValue) ? undefined : true; + parserValid = isUndefined(modelValue) ? undefined : true; if (parserValid) { for (var i = 0; i < ctrl.$parsers.length; i++) { @@ -23159,7 +23288,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // Pass the $$lastCommittedViewValue here, because the cached viewValue might be out of date. // This can happen if e.g. $setViewValue is called from inside a parser - ctrl.$$runValidators(parserValid, modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { + ctrl.$$runValidators(modelValue, ctrl.$$lastCommittedViewValue, function(allValid) { if (!allowInvalid) { // Note: Don't check ctrl.$valid here, as we could have // external validators (e.g. calculated on the server), @@ -23280,6 +23409,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ // TODO(perf): why not move this to the action fn? if (modelValue !== ctrl.$modelValue) { ctrl.$modelValue = ctrl.$$rawModelValue = modelValue; + parserValid = undefined; var formatters = ctrl.$formatters, idx = formatters.length; @@ -23292,7 +23422,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$ ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue; ctrl.$render(); - ctrl.$$runValidators(undefined, modelValue, viewValue, noop); + ctrl.$$runValidators(modelValue, viewValue, noop); } } @@ -24108,6 +24238,55 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp * when keys are deleted and reinstated. * * + * # Tracking and Duplicates + * + * When the contents of the collection change, `ngRepeat` makes the corresponding changes to the DOM: + * + * * When an item is added, a new instance of the template is added to the DOM. + * * When an item is removed, its template instance is removed from the DOM. + * * When items are reordered, their respective templates are reordered in the DOM. + * + * By default, `ngRepeat` does not allow duplicate items in arrays. This is because when + * there are duplicates, it is not possible to maintain a one-to-one mapping between collection + * items and DOM elements. + * + * If you do need to repeat duplicate items, you can substitute the default tracking behavior + * with your own using the `track by` expression. + * + * For example, you may track items by the index of each item in the collection, using the + * special scope property `$index`: + * ```html + *
+ * {{n}} + *
+ * ``` + * + * You may use arbitrary expressions in `track by`, including references to custom functions + * on the scope: + * ```html + *
+ * {{n}} + *
+ * ``` + * + * If you are working with objects that have an identifier property, you can track + * by the identifier instead of the whole object. Should you reload your data later, `ngRepeat` + * will not have to rebuild the DOM elements for items it has already rendered, even if the + * JavaScript objects in the collection have been substituted for new ones: + * ```html + *
+ * {{model.name}} + *
+ * ``` + * + * When no `track by` expression is provided, it is equivalent to tracking by the built-in + * `$id` function, which tracks items by their identity: + * ```html + *
+ * {{obj.prop}} + *
+ * ``` + * * # Special repeat start and end points * To repeat a series of elements instead of just one parent element, ngRepeat (as well as other ng directives) supports extending * the range of the repeater by defining explicit start and end points by using **ng-repeat-start** and **ng-repeat-end** respectively. @@ -24175,12 +24354,12 @@ var ngPluralizeDirective = ['$locale', '$interpolate', function($locale, $interp * * For example: `(name, age) in {'adam':10, 'amalie':12}`. * - * * `variable in expression track by tracking_expression` – You can also provide an optional tracking function - * which can be used to associate the objects in the collection with the DOM elements. If no tracking function - * is specified the ng-repeat associates elements by identity in the collection. It is an error to have - * more than one tracking function to resolve to the same key. (This would mean that two distinct objects are - * mapped to the same DOM element, which is not possible.) Filters should be applied to the expression, - * before specifying a tracking expression. + * * `variable in expression track by tracking_expression` – You can also provide an optional tracking expression + * which can be used to associate the objects in the collection with the DOM elements. If no tracking expression + * is specified, ng-repeat associates elements by identity. It is an error to have + * more than one tracking expression value resolve to the same key. (This would mean that two distinct objects are + * mapped to the same DOM element, which is not possible.) If filters are used in the expression, they should be + * applied before the tracking expression. * * For example: `item in items` is equivalent to `item in items track by $id(item)`. This implies that the DOM elements * will be associated by item identity in the array.