@@ -25549,8 +27350,8 @@ var inputType = {
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object, then this is used directly.
* If the expression evaluates to a string, then it will be converted to a RegExp
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
@@ -25647,8 +27448,8 @@ var inputType = {
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object, then this is used directly.
* If the expression evaluates to a string, then it will be converted to a RegExp
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
@@ -25746,8 +27547,8 @@ var inputType = {
* @param {string=} pattern Similar to `ngPattern` except that the attribute value is the actual string
* that contains the regular expression body that will be converted to a regular expression
* as in the ngPattern directive.
- * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object, then this is used directly.
* If the expression evaluates to a string, then it will be converted to a RegExp
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
@@ -25966,7 +27767,7 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
if (!$sniffer.android) {
var composing = false;
- element.on('compositionstart', function(data) {
+ element.on('compositionstart', function() {
composing = true;
});
@@ -25976,6 +27777,8 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
});
}
+ var timeout;
+
var listener = function(ev) {
if (timeout) {
$browser.defer.cancel(timeout);
@@ -26005,8 +27808,6 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
if ($sniffer.hasEvent('input')) {
element.on('input', listener);
} else {
- var timeout;
-
var deferListener = function(ev, input, origValue) {
if (!timeout) {
timeout = $browser.defer(function() {
@@ -26038,6 +27839,26 @@ function baseInputType(scope, element, attr, ctrl, $sniffer, $browser) {
// or form autocomplete on newer browser, we need "change" event to catch it
element.on('change', listener);
+ // Some native input types (date-family) have the ability to change validity without
+ // firing any input/change events.
+ // For these event types, when native validators are present and the browser supports the type,
+ // check for validity changes on various DOM events.
+ if (PARTIAL_VALIDATION_TYPES[type] && ctrl.$$hasNativeValidators && type === attr.type) {
+ element.on(PARTIAL_VALIDATION_EVENTS, function(ev) {
+ if (!timeout) {
+ var validity = this[VALIDITY_STATE_PROPERTY];
+ var origBadInput = validity.badInput;
+ var origTypeMismatch = validity.typeMismatch;
+ timeout = $browser.defer(function() {
+ timeout = null;
+ if (validity.badInput !== origBadInput || validity.typeMismatch !== origTypeMismatch) {
+ listener(ev);
+ }
+ });
+ }
+ });
+ }
+
ctrl.$render = function() {
// Workaround for Firefox validation #12102.
var value = ctrl.$isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue;
@@ -26207,11 +28028,7 @@ function badInputChecker(scope, element, attr, ctrl) {
if (nativeValidation) {
ctrl.$parsers.push(function(value) {
var validity = element.prop(VALIDITY_STATE_PROPERTY) || {};
- // Detect bug in FF35 for input[email] (https://bugzilla.mozilla.org/show_bug.cgi?id=1064430):
- // - also sets validity.badInput (should only be validity.typeMismatch).
- // - see http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#e-mail-state-(type=email)
- // - can ignore this case as we can still read out the erroneous email...
- return validity.badInput && !validity.typeMismatch ? undefined : value;
+ return validity.badInput || validity.typeMismatch ? undefined : value;
});
}
}
@@ -26383,8 +28200,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
* 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 ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * does not match a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object, then this is used directly.
* If the expression evaluates to a string, then it will be converted to a RegExp
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
@@ -26422,8 +28239,8 @@ function checkboxInputType(scope, element, attr, ctrl, $sniffer, $browser, $filt
* @param {number=} ngMaxlength Sets `maxlength` validation error key if the value is longer than
* 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 ngModel value does not match
- * a RegExp found by evaluating the Angular expression given in the attribute value.
+ * @param {string=} ngPattern Sets `pattern` validation error key if the ngModel {@link ngModel.NgModelController#$viewValue $viewValue}
+ * value does not match a RegExp found by evaluating the Angular expression given in the attribute value.
* If the expression evaluates to a RegExp object, then this is used directly.
* If the expression evaluates to a string, then it will be converted to a RegExp
* after wrapping it in `^` and `$` characters. For instance, `"abc"` will be converted to
@@ -26992,7 +28809,11 @@ function classDirective(name, selector) {
updateClasses(oldClasses, newClasses);
}
}
- oldVal = shallowCopy(newVal);
+ if (isArray(newVal)) {
+ oldVal = newVal.map(function(v) { return shallowCopy(v); });
+ } else {
+ oldVal = shallowCopy(newVal);
+ }
}
}
};
@@ -27062,9 +28883,10 @@ function classDirective(name, selector) {
* new classes added.
*
* @animations
- * **add** - happens just before the class is applied to the elements
- *
- * **remove** - happens just before the class is removed from the element
+ * | Animation | Occurs |
+ * |----------------------------------|-------------------------------------|
+ * | {@link ng.$animate#addClass addClass} | just before the class is applied to the element |
+ * | {@link ng.$animate#removeClass removeClass} | just before the class is removed from the element |
*
* @element ANY
* @param {expression} ngClass {@link guide/expression Expression} to eval. The result
@@ -27649,7 +29471,7 @@ var ngControllerDirective = [function() {
*
* * no-inline-style: this stops Angular from injecting CSS styles into the DOM
*
- * * no-unsafe-eval: this stops Angular from optimising $parse with unsafe eval of strings
+ * * no-unsafe-eval: this stops Angular from optimizing $parse with unsafe eval of strings
*
* You can use these values in the following combinations:
*
@@ -27666,7 +29488,7 @@ var ngControllerDirective = [function() {
* inline styles. E.g. ``.
*
* * Specifying only `no-inline-style` tells Angular that we must not inject styles, but that we can
- * run eval - no automcatic check for unsafe eval will occur. E.g. ``
+ * run eval - no automatic check for unsafe eval will occur. E.g. ``
*
* * Specifying both `no-unsafe-eval` and `no-inline-style` tells Angular that we must not inject
* styles nor use eval, which is the same as an empty: ng-csp.
@@ -28323,8 +30145,10 @@ forEach(
* and `leave` effects.
*
* @animations
- * enter - happens just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container
- * leave - happens just before the `ngIf` contents are removed from the DOM
+ * | Animation | Occurs |
+ * |----------------------------------|-------------------------------------|
+ * | {@link ng.$animate#enter enter} | just after the `ngIf` contents change and a new DOM element is created and injected into the `ngIf` container |
+ * | {@link ng.$animate#leave leave} | just before the `ngIf` contents are removed from the DOM |
*
* @element ANY
* @scope
@@ -28365,7 +30189,7 @@ forEach(
*/
-var ngIfDirective = ['$animate', function($animate) {
+var ngIfDirective = ['$animate', '$compile', function($animate, $compile) {
return {
multiElement: true,
transclude: 'element',
@@ -28381,7 +30205,7 @@ var ngIfDirective = ['$animate', function($animate) {
if (!childScope) {
$transclude(function(clone, newScope) {
childScope = newScope;
- clone[clone.length++] = document.createComment(' end ngIf: ' + $attr.ngIf + ' ');
+ clone[clone.length++] = $compile.$$createComment('end ngIf', $attr.ngIf);
// Note: We only need the first/last node of the cloned nodes.
// However, we need to keep the reference to the jqlite wrapper as it might be changed later
// by a directive with templateUrl when its template arrives.
@@ -28436,8 +30260,10 @@ var ngIfDirective = ['$animate', function($animate) {
* access on some browsers.
*
* @animations
- * enter - animation is used to bring new content into the browser.
- * leave - animation is used to animate existing content away.
+ * | Animation | Occurs |
+ * |----------------------------------|-------------------------------------|
+ * | {@link ng.$animate#enter enter} | when the expression changes, on the new include |
+ * | {@link ng.$animate#leave leave} | when the expression changes, on the old include |
*
* The enter and leave animation occur concurrently.
*
@@ -28447,7 +30273,13 @@ var ngIfDirective = ['$animate', function($animate) {
* @param {string} ngInclude|src angular expression evaluating to URL. If the source is a string constant,
* make sure you wrap it in **single** quotes, e.g. `src="'myPartialTemplate.html'"`.
* @param {string=} onload Expression to evaluate when a new partial is loaded.
- *
+ *
+ * **Note:** When using onload on SVG elements in IE11, the browser will try to call
+ * a function with the name on the window element, which will usually throw a
+ * "function is undefined" error. To fix this, you can instead use `data-onload` or a
+ * different form that {@link guide/directive#normalization matches} `onload`.
+ *
+ *
* @param {string=} autoscroll Whether `ngInclude` should call {@link ng.$anchorScroll
* $anchorScroll} to scroll the viewport after the content is loaded.
*
@@ -28639,6 +30471,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
//set the 2nd param to true to ignore the template request error so that the inner
//contents and scope can be cleaned up.
$templateRequest(src, true).then(function(response) {
+ if (scope.$$destroyed) return;
+
if (thisChangeId !== changeCounter) return;
var newScope = scope.$new();
ctrl.template = response;
@@ -28660,6 +30494,8 @@ var ngIncludeDirective = ['$templateRequest', '$anchorScroll', '$animate',
currentScope.$emit('$includeContentLoaded', src);
scope.$eval(onloadExp);
}, function() {
+ if (scope.$$destroyed) return;
+
if (thisChangeId === changeCounter) {
cleanupLastIncludeContent();
scope.$emit('$includeContentError', src);
@@ -28688,12 +30524,12 @@ var ngIncludeFillContentDirective = ['$compile',
priority: -400,
require: 'ngInclude',
link: function(scope, $element, $attr, ctrl) {
- if (/SVG/.test($element[0].toString())) {
+ if (toString.call($element[0]).match(/SVG/)) {
// WebKit: https://bugs.webkit.org/show_bug.cgi?id=135698 --- SVG elements do not
// support innerHTML, so detect this here and try to generate the contents
// specially.
$element.empty();
- $compile(jqLiteBuildFragment(ctrl.template, document).childNodes)(scope,
+ $compile(jqLiteBuildFragment(ctrl.template, window.document).childNodes)(scope,
function namespaceAdaptedClone(clone) {
$element.append(clone);
}, {futureParentElement: $element});
@@ -28840,7 +30676,7 @@ var ngInitDirective = ngDirective({
*
*
*
- * ### Example - splitting on whitespace
+ * ### Example - splitting on newline
*
*
*
@@ -28917,7 +30753,9 @@ var VALID_CLASS = 'ng-valid',
DIRTY_CLASS = 'ng-dirty',
UNTOUCHED_CLASS = 'ng-untouched',
TOUCHED_CLASS = 'ng-touched',
- PENDING_CLASS = 'ng-pending';
+ PENDING_CLASS = 'ng-pending',
+ EMPTY_CLASS = 'ng-empty',
+ NOT_EMPTY_CLASS = 'ng-not-empty';
var ngModelMinErr = minErr('ngModel');
@@ -28925,7 +30763,9 @@ var ngModelMinErr = minErr('ngModel');
* @ngdoc type
* @name ngModel.NgModelController
*
- * @property {string} $viewValue Actual string value in the view.
+ * @property {*} $viewValue The actual value from the control's view. For `input` elements, this is a
+ * String. See {@link ngModel.NgModelController#$setViewValue} for information about when the $viewValue
+ * is set.
* @property {*} $modelValue The value in the model that the control is bound to.
* @property {Array.} $parsers Array of functions to execute, as a pipeline, whenever
the control reads value from the DOM. The functions are called in array order, each passing
@@ -29164,9 +31004,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
};
ngModelSet = function($scope, newValue) {
if (isFunction(parsedNgModel($scope))) {
- invokeModelSetter($scope, {$$$p: ctrl.$modelValue});
+ invokeModelSetter($scope, {$$$p: newValue});
} else {
- parsedNgModelAssign($scope, ctrl.$modelValue);
+ parsedNgModelAssign($scope, newValue);
}
};
} else if (!parsedNgModel.assign) {
@@ -29191,7 +31031,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* the `$viewValue` are different from last time.
*
* Since `ng-model` does not do a deep watch, `$render()` is only invoked if the values of
- * `$modelValue` and `$viewValue` are actually different from their previous value. If `$modelValue`
+ * `$modelValue` and `$viewValue` are actually different from their previous values. If `$modelValue`
* or `$viewValue` are objects (rather than a string or number) then `$render()` will not be
* invoked if you only change a property on the objects.
*/
@@ -29219,6 +31059,17 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
return isUndefined(value) || value === '' || value === null || value !== value;
};
+ this.$$updateEmptyClasses = function(value) {
+ if (ctrl.$isEmpty(value)) {
+ $animate.removeClass($element, NOT_EMPTY_CLASS);
+ $animate.addClass($element, EMPTY_CLASS);
+ } else {
+ $animate.removeClass($element, EMPTY_CLASS);
+ $animate.addClass($element, NOT_EMPTY_CLASS);
+ }
+ };
+
+
var currentValidationRunId = 0;
/**
@@ -29336,11 +31187,14 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* which may be caused by a pending debounced event or because the input is waiting for a some
* future event.
*
- * If you have an input that uses `ng-model-options` to set up debounced events or events such
- * as blur you can have a situation where there is a period when the `$viewValue`
- * is out of synch with the ngModel's `$modelValue`.
+ * If you have an input that uses `ng-model-options` to set up debounced updates or updates that
+ * depend on special events such as blur, you can have a situation where there is a period when
+ * the `$viewValue` is out of sync with the ngModel's `$modelValue`.
*
- * In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
+ * In this case, you can use `$rollbackViewValue()` to manually cancel the debounced / future update
+ * and reset the input to the last committed view value.
+ *
+ * It is also possible that you run into difficulties if you try to update the ngModel's `$modelValue`
* programmatically before these debounced/future events have resolved/occurred, because Angular's
* dirty checking mechanism is not able to tell whether the model has actually changed or not.
*
@@ -29353,39 +31207,63 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* angular.module('cancel-update-example', [])
*
* .controller('CancelUpdateController', ['$scope', function($scope) {
- * $scope.resetWithCancel = function(e) {
- * if (e.keyCode == 27) {
- * $scope.myForm.myInput1.$rollbackViewValue();
- * $scope.myValue = '';
- * }
- * };
- * $scope.resetWithoutCancel = function(e) {
+ * $scope.model = {};
+ *
+ * $scope.setEmpty = function(e, value, rollback) {
* if (e.keyCode == 27) {
- * $scope.myValue = '';
+ * e.preventDefault();
+ * if (rollback) {
+ * $scope.myForm[value].$rollbackViewValue();
+ * }
+ * $scope.model[value] = '';
* }
* };
* }]);
*
*
*
- *
Try typing something in each input. See that the model only updates when you
- * blur off the input.
- *
- *
Now see what happens if you start typing then press the Escape key
+ *
Both of these inputs are only updated if they are blurred. Hitting escape should
+ * empty them. Follow these steps and observe the difference:
+ *
+ * - Type something in the input. You will see that the model is not yet updated
+ * - Press the Escape key.
+ *
+ * - In the first example, nothing happens, because the model is already '', and no
+ * update is detected. If you blur the input, the model will be set to the current view.
+ *
+ * - In the second example, the pending update is cancelled, and the input is set back
+ * to the last committed view value (''). Blurring the input does nothing.
+ *
+ *
+ *
+ *
*
*
*
*
+
+ div {
+ display: table-cell;
+ }
+ div:nth-child(1) {
+ padding-right: 30px;
+ }
+
+
*
*/
this.$rollbackViewValue = function() {
@@ -29499,13 +31377,13 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
forEach(ctrl.$asyncValidators, function(validator, name) {
var promise = validator(modelValue, viewValue);
if (!isPromiseLike(promise)) {
- throw ngModelMinErr("$asyncValidators",
+ throw ngModelMinErr('nopromise',
"Expected asynchronous validator to return a promise but got '{0}' instead.", promise);
}
setValidity(name, undefined);
validatorPromises.push(promise.then(function() {
setValidity(name, true);
- }, function(error) {
+ }, function() {
allValid = false;
setValidity(name, false);
}));
@@ -29555,6 +31433,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
if (ctrl.$$lastCommittedViewValue === viewValue && (viewValue !== '' || !ctrl.$$hasNativeValidators)) {
return;
}
+ ctrl.$$updateEmptyClasses(viewValue);
ctrl.$$lastCommittedViewValue = viewValue;
// change to dirty
@@ -29653,7 +31532,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* However, custom controls might also pass objects to this method. In this case, we should make
* a copy of the object before passing it to `$setViewValue`. This is because `ngModel` does not
* perform a deep watch of objects, it only looks for a change of identity. If you only change
- * the property of the object then ngModel will not realise that the object has changed and
+ * the property of the object then ngModel will not realize that the object has changed and
* will not invoke the `$parsers` and `$validators` pipelines. For this reason, you should
* not change properties of the copy once it has been passed to `$setViewValue`.
* Otherwise you may cause the model value on the scope to change incorrectly.
@@ -29737,6 +31616,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
viewValue = formatters[idx](viewValue);
}
if (ctrl.$viewValue !== viewValue) {
+ ctrl.$$updateEmptyClasses(viewValue);
ctrl.$viewValue = ctrl.$$lastCommittedViewValue = viewValue;
ctrl.$render();
@@ -29767,7 +31647,8 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* require.
* - Providing validation behavior (i.e. required, number, email, url).
* - Keeping the state of the control (valid/invalid, dirty/pristine, touched/untouched, validation errors).
- * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`, `ng-untouched`) including animations.
+ * - Setting related css classes on the element (`ng-valid`, `ng-invalid`, `ng-dirty`, `ng-pristine`, `ng-touched`,
+ * `ng-untouched`, `ng-empty`, `ng-not-empty`) including animations.
* - Registering the control with its parent {@link ng.directive:form form}.
*
* Note: `ngModel` will try to bind to the property given by evaluating the expression on the
@@ -29795,6 +31676,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* - {@link ng.directive:select select}
* - {@link ng.directive:textarea textarea}
*
+ * # Complex Models (objects or collections)
+ *
+ * By default, `ngModel` watches the model by reference, not value. This is important to know when
+ * binding inputs to models that are objects (e.g. `Date`) or collections (e.g. arrays). If only properties of the
+ * object or collection change, `ngModel` will not be notified and so the input will not be re-rendered.
+ *
+ * The model must be assigned an entirely new object or collection before a re-rendering will occur.
+ *
+ * Some directives have options that will cause them to use a custom `$watchCollection` on the model expression
+ * - for example, `ngOptions` will do so when a `track by` clause is included in the comprehension expression or
+ * if the select is given the `multiple` attribute.
+ *
+ * The `$watchCollection()` method only does a shallow comparison, meaning that changing properties deeper than the
+ * first level of the object (or only changing the properties of an item in the collection if it's an array) will still
+ * not trigger a re-rendering of the model.
+ *
* # CSS classes
* The following CSS classes are added and removed on the associated input/select/textarea element
* depending on the validity of the model.
@@ -29808,13 +31705,16 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
* - `ng-touched`: the control has been blurred
* - `ng-untouched`: the control hasn't been blurred
* - `ng-pending`: any `$asyncValidators` are unfulfilled
+ * - `ng-empty`: the view does not contain a value or the value is deemed "empty", as defined
+ * by the {@link ngModel.NgModelController#$isEmpty} method
+ * - `ng-not-empty`: the view contains a non-empty value
*
* Keep in mind that ngAnimate can detect each of these classes when added and removed.
*
* ## Animation Hooks
*
* Animations within models are triggered when any of the associated CSS classes are added and removed
- * on the input element which is attached to the model. These classes are: `.ng-pristine`, `.ng-dirty`,
+ * on the input element which is attached to the model. These classes include: `.ng-pristine`, `.ng-dirty`,
* `.ng-invalid` and `.ng-valid` as well as any other validations that are performed on the model itself.
* The animations that are triggered within ngModel are similar to how they work in ngClass and
* animations can be hooked into using CSS transitions, keyframes as well as JS animations.
@@ -29957,7 +31857,7 @@ var ngModelDirective = ['$rootScope', function($rootScope) {
});
}
- element.on('blur', function(ev) {
+ element.on('blur', function() {
if (modelCtrl.$touched) return;
if ($rootScope.$$phase) {
@@ -30038,12 +31938,13 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
user.name =
+
user.data =