aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/content/guide/dev_guide.forms.ngdoc12
-rw-r--r--src/AngularPublic.js1
-rw-r--r--src/ng/directive/input.js121
-rw-r--r--src/ngScenario/Scenario.js2
-rw-r--r--src/ngScenario/dsl.js3
-rw-r--r--test/BinderSpec.js14
-rw-r--r--test/ng/directive/formSpec.js15
-rw-r--r--test/ng/directive/inputSpec.js57
8 files changed, 76 insertions, 149 deletions
diff --git a/docs/content/guide/dev_guide.forms.ngdoc b/docs/content/guide/dev_guide.forms.ngdoc
index c643a613..0c3a7fc3 100644
--- a/docs/content/guide/dev_guide.forms.ngdoc
+++ b/docs/content/guide/dev_guide.forms.ngdoc
@@ -20,7 +20,7 @@ In addition it provides {@link api/angular.module.ng.$compileProvider.directive.
<doc:source>
<div ng-controller="Controller">
<form novalidate class="simple-form">
- Name: <input type="text" ng-model="user.name" ng-model-instant /><br />
+ Name: <input type="text" ng-model="user.name" /><br />
E-mail: <input type="email" ng-model="user.email" /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
@@ -50,11 +50,7 @@ In addition it provides {@link api/angular.module.ng.$compileProvider.directive.
</doc:example>
-Note that:
-
- * the {@link api/angular.module.ng.$compileProvider.directive.ng-model-instant ng-model-instant} causes the `user.name` to be updated immediately.
-
- * `novalidate` is used to disable browser's native form validation.
+Note that `novalidate` is used to disable browser's native form validation.
@@ -76,7 +72,7 @@ This ensures that the user is not distracted with an error until after interacti
<div ng-controller="Controller">
<form novalidate class="css-form">
Name:
- <input type="text" ng-model="user.name" ng-model-instant required /><br />
+ <input type="text" ng-model="user.name" required /><br />
E-mail: <input type="email" ng-model="user.email" required /><br />
Gender: <input type="radio" ng-model="user.gender" value="male" />male
<input type="radio" ng-model="user.gender" value="female" />female<br />
@@ -147,7 +143,7 @@ This allows us to extend the above example with these features:
<input type="checkbox" ng-model="user.agree" name="userAgree" required />
I agree: <input ng-show="user.agree" type="text" ng-model="user.agreeSign"
- ng-model-instant required /><br />
+ required /><br />
<div ng-show="!user.agree || !user.agreeSign">Please agree and sign.</div>
<button ng-click="reset()" disabled="{{isUnchanged(user)}}">RESET</button>
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index 8597de9c..1326bf8c 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -98,7 +98,6 @@ function publishExternalAPI(angular){
ngModel: ngModelDirective,
ngList: ngListDirective,
ngChange: ngChangeDirective,
- ngModelInstant: ngModelInstantDirective,
required: requiredDirective,
ngRequired: requiredDirective,
ngValue: ngValueDirective
diff --git a/src/ng/directive/input.js b/src/ng/directive/input.js
index 3d53f7ca..aab12e34 100644
--- a/src/ng/directive/input.js
+++ b/src/ng/directive/input.js
@@ -367,12 +367,43 @@ function isEmpty(value) {
}
-function textInputType(scope, element, attr, ctrl) {
- element.bind('blur', function() {
- scope.$apply(function() {
- ctrl.$setViewValue(trim(element.val()));
+function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+
+ var listener = function() {
+ var value = trim(element.val());
+
+ if (ctrl.$viewValue !== value) {
+ scope.$apply(function() {
+ ctrl.$setViewValue(value);
+ });
+ }
+ };
+
+ // if the browser does support "input" event, we are fine
+ if ($sniffer.hasEvent('input')) {
+ element.bind('input', listener);
+ } else {
+ var timeout;
+
+ element.bind('keydown', function(event) {
+ var key = event.keyCode;
+
+ // ignore
+ // command modifiers arrows
+ if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
+
+ if (!timeout) {
+ timeout = $browser.defer(function() {
+ listener();
+ timeout = null;
+ });
+ }
});
- });
+
+ // if user paste into input using mouse, we need "change" event to catch it
+ element.bind('change', listener);
+ }
+
ctrl.$render = function() {
element.val(isEmpty(ctrl.$viewValue) ? '' : ctrl.$viewValue);
@@ -448,8 +479,8 @@ function textInputType(scope, element, attr, ctrl) {
}
};
-function numberInputType(scope, element, attr, ctrl) {
- textInputType(scope, element, attr, ctrl);
+function numberInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
ctrl.$parsers.push(function(value) {
var empty = isEmpty(value);
@@ -510,8 +541,8 @@ function numberInputType(scope, element, attr, ctrl) {
});
}
-function urlInputType(scope, element, attr, ctrl) {
- textInputType(scope, element, attr, ctrl);
+function urlInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
var urlValidator = function(value) {
if (isEmpty(value) || URL_REGEXP.test(value)) {
@@ -527,8 +558,8 @@ function urlInputType(scope, element, attr, ctrl) {
ctrl.$parsers.push(urlValidator);
}
-function emailInputType(scope, element, attr, ctrl) {
- textInputType(scope, element, attr, ctrl);
+function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {
+ textInputType(scope, element, attr, ctrl, $sniffer, $browser);
var emailValidator = function(value) {
if (isEmpty(value) || EMAIL_REGEXP.test(value)) {
@@ -709,13 +740,14 @@ function checkboxInputType(scope, element, attr, ctrl) {
</doc:scenario>
</doc:example>
*/
-var inputDirective = [function() {
+var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {
return {
restrict: 'E',
require: '?ngModel',
link: function(scope, element, attr, ctrl) {
if (ctrl) {
- (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl);
+ (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,
+ $browser);
}
}
};
@@ -1004,69 +1036,6 @@ var ngChangeDirective = valueFn({
});
-/**
- * @ngdoc directive
- * @name angular.module.ng.$compileProvider.directive.ng-model-instant
- *
- * @element input
- *
- * @description
- * By default, Angular udpates the model only on `blur` event - when the input looses focus.
- * If you want to update after every key stroke, use `ng-model-instant`.
- *
- * @example
- * <doc:example>
- * <doc:source>
- * First name: <input type="text" ng-model="firstName" /><br />
- * Last name: <input type="text" ng-model="lastName" ng-model-instant /><br />
- *
- * First name ({{firstName}}) is only updated on `blur` event, but the last name ({{lastName}})
- * is updated immediately, because of using `ng-model-instant`.
- * </doc:source>
- * <doc:scenario>
- * it('should update first name on blur', function() {
- * input('firstName').enter('santa', 'blur');
- * expect(binding('firstName')).toEqual('santa');
- * });
- *
- * it('should update last name immediately', function() {
- * input('lastName').enter('santa', 'keydown');
- * expect(binding('lastName')).toEqual('santa');
- * });
- * </doc:scenario>
- * </doc:example>
- */
-var ngModelInstantDirective = ['$browser', function($browser) {
- return {
- require: 'ngModel',
- link: function(scope, element, attr, ctrl) {
- var handler = function() {
- scope.$apply(function() {
- ctrl.$setViewValue(trim(element.val()));
- });
- };
-
- var timeout;
- element.bind('keydown', function(event) {
- var key = event.keyCode;
-
- // command modifiers arrows
- if (key === 91 || (15 < key && key < 19) || (37 <= key && key <= 40)) return;
-
- if (!timeout) {
- timeout = $browser.defer(function() {
- handler();
- timeout = null;
- });
- }
- });
-
- element.bind('change input', handler);
- }
- };
-}];
-
-
var requiredDirective = [function() {
return {
require: '?ngModel',
diff --git a/src/ngScenario/Scenario.js b/src/ngScenario/Scenario.js
index cd3c335f..7335d5de 100644
--- a/src/ngScenario/Scenario.js
+++ b/src/ngScenario/Scenario.js
@@ -327,7 +327,7 @@ function browserTrigger(element, type, keys) {
(function(fn){
var parentTrigger = fn.trigger;
fn.trigger = function(type) {
- if (/(click|change|keydown|blur)/.test(type)) {
+ if (/(click|change|keydown|blur|input)/.test(type)) {
var processDefaults = [];
this.each(function(index, node) {
processDefaults.push(browserTrigger(node, type));
diff --git a/src/ngScenario/dsl.js b/src/ngScenario/dsl.js
index 8a1bccb1..7f399394 100644
--- a/src/ngScenario/dsl.js
+++ b/src/ngScenario/dsl.js
@@ -198,12 +198,13 @@ angular.scenario.dsl('binding', function() {
*/
angular.scenario.dsl('input', function() {
var chain = {};
+ var supportInputEvent = 'oninput' in document.createElement('div');
chain.enter = function(value, event) {
return this.addFutureAction("input '" + this.name + "' enter '" + value + "'", function($window, $document, done) {
var input = $document.elements('[ng\\:model="$1"]', this.name).filter(':input');
input.val(value);
- input.trigger(event || 'blur');
+ input.trigger(event || supportInputEvent && 'input' || 'change');
done();
});
};
diff --git a/test/BinderSpec.js b/test/BinderSpec.js
index 306cbc43..6d5dd91e 100644
--- a/test/BinderSpec.js
+++ b/test/BinderSpec.js
@@ -142,20 +142,6 @@ describe('Binder', function() {
expect(html.indexOf('action="foo();"')).toBeGreaterThan(0);
});
- it('RepeaterAdd', inject(function($rootScope, $compile) {
- element = $compile('<div><input type="text" ng-model="item.x" ng-repeat="item in items"></div>')($rootScope);
- $rootScope.items = [{x:'a'}, {x:'b'}];
- $rootScope.$apply();
- var first = childNode(element, 1);
- var second = childNode(element, 2);
- expect(first.val()).toEqual('a');
- expect(second.val()).toEqual('b');
-
- first.val('ABC');
- browserTrigger(first, 'blur');
- expect($rootScope.items[0].x).toEqual('ABC');
- }));
-
it('ItShouldRemoveExtraChildrenWhenIteratingOverHash', inject(function($rootScope, $compile) {
element = $compile('<div><div ng-repeat="i in items">{{i}}</div></div>')($rootScope);
var items = {};
diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js
index 5c34b5ad..491a31db 100644
--- a/test/ng/directive/formSpec.js
+++ b/test/ng/directive/formSpec.js
@@ -1,7 +1,7 @@
'use strict';
describe('form', function() {
- var doc, control, scope, $compile;
+ var doc, control, scope, $compile, changeInputValue;
beforeEach(module(function($compileProvider) {
$compileProvider.directive('storeModelCtrl', function() {
@@ -14,9 +14,14 @@ describe('form', function() {
});
}));
- beforeEach(inject(function($injector) {
+ beforeEach(inject(function($injector, $sniffer) {
$compile = $injector.get('$compile');
scope = $injector.get('$rootScope');
+
+ changeInputValue = function(elm, value) {
+ elm.val(value);
+ browserTrigger(elm, $sniffer.hasEvent('input') ? 'input' : 'change');
+ };
}));
afterEach(function() {
@@ -126,10 +131,8 @@ describe('form', function() {
var inputA = doc.find('input').eq(0),
inputB = doc.find('input').eq(1);
- inputA.val('val1');
- browserTrigger(inputA, 'blur');
- inputB.val('val2');
- browserTrigger(inputB, 'blur');
+ changeInputValue(inputA, 'val1');
+ changeInputValue(inputB, 'val2');
expect(scope.firstName).toBe('val1');
expect(scope.lastName).toBe('val2');
diff --git a/test/ng/directive/inputSpec.js b/test/ng/directive/inputSpec.js
index 734cb34d..395f37c3 100644
--- a/test/ng/directive/inputSpec.js
+++ b/test/ng/directive/inputSpec.js
@@ -236,7 +236,7 @@ describe('NgModelController', function() {
describe('ng-model', function() {
it('should set css classes (ng-valid, ng-invalid, ng-pristine, ng-dirty)',
- inject(function($compile, $rootScope) {
+ inject(function($compile, $rootScope, $sniffer) {
var element = $compile('<input type="email" ng-model="value" />')($rootScope);
$rootScope.$digest();
@@ -254,14 +254,14 @@ describe('ng-model', function() {
expect(element.hasClass('ng-invalid-email')).toBe(true);
element.val('invalid-again');
- browserTrigger(element, 'blur');
+ browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change');
expect(element).toBeInvalid();
expect(element).toBeDirty();
expect(element.hasClass('ng-valid-email')).toBe(false);
expect(element.hasClass('ng-invalid-email')).toBe(true);
element.val('vojta@google.com');
- browserTrigger(element, 'blur');
+ browserTrigger(element, $sniffer.hasEvent('input') ? 'input' : 'change');
expect(element).toBeValid();
expect(element).toBeDirty();
expect(element.hasClass('ng-valid-email')).toBe(true);
@@ -282,7 +282,7 @@ describe('ng-model', function() {
describe('input', function() {
- var formElm, inputElm, scope, $compile;
+ var formElm, inputElm, scope, $compile, changeInputValueTo;
function compileInput(inputHtml) {
formElm = jqLite('<form name="form">' + inputHtml + '</form>');
@@ -290,14 +290,14 @@ describe('input', function() {
$compile(formElm)(scope);
}
- function changeInputValueTo(value) {
- inputElm.val(value);
- browserTrigger(inputElm, 'blur');
- }
-
- beforeEach(inject(function($injector) {
+ beforeEach(inject(function($injector, $sniffer) {
$compile = $injector.get('$compile');
scope = $injector.get('$rootScope');
+
+ changeInputValueTo = function(value) {
+ inputElm.val(value);
+ browserTrigger(inputElm, $sniffer.hasEvent('input') ? 'input' : 'change');
+ };
}));
afterEach(function() {
@@ -379,7 +379,7 @@ describe('input', function() {
it('should ignore input without ng-model attr', function() {
compileInput('<input type="text" name="whatever" required />');
- browserTrigger(inputElm, 'blur');
+ changeInputValueTo('');
expect(inputElm.hasClass('ng-valid')).toBe(false);
expect(inputElm.hasClass('ng-invalid')).toBe(false);
expect(inputElm.hasClass('ng-pristine')).toBe(false);
@@ -715,7 +715,7 @@ describe('input', function() {
expect(inputElm[1].checked).toBe(true);
expect(inputElm[2].checked).toBe(false);
- browserTrigger(inputElm[2]);
+ browserTrigger(inputElm[2], 'click');
expect(scope.color).toBe('blue');
});
@@ -735,7 +735,7 @@ describe('input', function() {
expect(inputElm[0].checked).toBe(true);
expect(inputElm[1].checked).toBe(false);
- browserTrigger(inputElm[1]);
+ browserTrigger(inputElm[1], 'click');
expect(scope.value).toBe('red');
scope.$apply(function() {
@@ -753,7 +753,7 @@ describe('input', function() {
it('should ignore checkbox without ng-model attr', function() {
compileInput('<input type="checkbox" name="whatever" required />');
- browserTrigger(inputElm, 'blur');
+ changeInputValueTo('');
expect(inputElm.hasClass('ng-valid')).toBe(false);
expect(inputElm.hasClass('ng-invalid')).toBe(false);
expect(inputElm.hasClass('ng-pristine')).toBe(false);
@@ -851,7 +851,7 @@ describe('input', function() {
compileInput('<textarea name="whatever" required></textarea>');
inputElm = formElm.find('textarea');
- browserTrigger(inputElm, 'blur');
+ changeInputValueTo('');
expect(inputElm.hasClass('ng-valid')).toBe(false);
expect(inputElm.hasClass('ng-invalid')).toBe(false);
expect(inputElm.hasClass('ng-pristine')).toBe(false);
@@ -1053,33 +1053,6 @@ describe('input', function() {
});
- describe('ng-model-instant', function() {
-
- it('should bind keydown, change, input events', inject(function($browser) {
- compileInput('<input type="text" ng-model="value" ng-model-instant />');
-
- inputElm.val('value1');
- browserTrigger(inputElm, 'keydown');
-
- // should be async (because of keydown)
- expect(scope.value).toBeUndefined();
-
- $browser.defer.flush();
- expect(scope.value).toBe('value1');
-
- inputElm.val('value2');
- browserTrigger(inputElm, 'change');
- expect(scope.value).toBe('value2');
-
- if (msie < 9) return;
-
- inputElm.val('value3');
- browserTrigger(inputElm, 'input');
- expect(scope.value).toBe('value3');
- }));
- });
-
-
describe('ng-value', function() {
it('should evaluate and set constant expressions', function() {