Skip to content

Commit 1e97e0a

Browse files
committed
feat(input): Allow custom events and timeouts to trigger model updates
By default, any change on the content will trigger an immediate model update and form validation. Now you can override this behavior using the `ng-update-model-on` attribute to bind only to a comma-delimited list of events. I.e. `ng-update-model-on="blur"` will update and validate only after the control loses focus. If you want to keep the default behavior and just add new events that may trigger the model update and validation, add `default` as one of the specified events. I.e. `ng-update-model-on="default,mousedown"` Also, a `ng-update-model-debounce` attribute will allow defering the actual model update some time after the last trigger event takes place (debouncing). This feature is not available in radio buttons. I.e. `ng-update-model-debounce="500"` Custom debouncing timeouts can be set for each event if you use an object in `ng-update-model-on`. I.e. `ng-update-model-on="{default: 500, blur: 0}"` You can specify both attributes in any tag so they became the default settings for any child control, although they can be overriden. Closes angular#1285
1 parent 16d8d13 commit 1e97e0a

File tree

8 files changed

+509
-1304
lines changed

8 files changed

+509
-1304
lines changed

angularFiles.js

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ angularFiles = {
6363
'src/ng/directive/ngStyle.js',
6464
'src/ng/directive/ngSwitch.js',
6565
'src/ng/directive/ngTransclude.js',
66+
'src/ng/directive/ngUpdateModel.js',
6667
'src/ng/directive/script.js',
6768
'src/ng/directive/select.js',
6869
'src/ng/directive/style.js'

docs/content/guide/forms.ngdoc

+40
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,46 @@ This allows us to extend the above example with these features:
180180
</example>
181181

182182

183+
# Non-immediate (debounced) or custom triggered model updates
184+
185+
By default, any change on the content will trigger a model update and form validation. You can override this behavior using the `ng-update-model-on`
186+
attribute to bind only to a comma-delimited list of events. I.e. `ng-update-model-on="blur"` will update and validate only after the control loses
187+
focus.
188+
189+
If you want to keep the default behavior and just add new events that may trigger the model update
190+
and validation, add "default" as one of the specified events. I.e. `ng-update-model-on="default,mousedown"`
191+
192+
You can delay the model update/validation using `ng-update-model-debounce`. I.e. `ng-update-model-debounce="500"` will wait for half a second since
193+
the last content change before triggering the model update and form validation. This debouncing feature is not available on radio buttons.
194+
195+
Custom debouncing timeouts can be set for each event for each event if you use an object in `ng-update-model-on`.
196+
I.e. `ng-update-model-on="{default: 500, blur: 0}"`
197+
198+
Using the object notation allows any valid Angular expression to be used inside, including data and function calls from the scope.
199+
200+
If those attributes are added to an element, they will be applied to all the child elements and controls that inherit from it unless they are
201+
overriden.
202+
203+
The following example shows how to override immediate updates. Changes on the inputs within the form will update the model
204+
only when the control loses focus (blur event).
205+
206+
<doc:example>
207+
<doc:source>
208+
<div ng-controller="ControllerUpdateOn">
209+
<form name="form" class="css-form" ng-update-model-on="blur">
210+
Name:
211+
<input type="text" ng-model="user.name" name="uName" /><br />
212+
</form>
213+
<pre>model = {{user | json}}</pre>
214+
</div>
215+
<script>
216+
function ControllerUpdateOn($scope) {
217+
$scope.user = {};
218+
}
219+
</script>
220+
</doc:source>
221+
</doc:example>
222+
183223

184224
# Custom Validation
185225

src/AngularPublic.js

+4
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@
4848
ngValueDirective,
4949
ngAttributeAliasDirectives,
5050
ngEventDirectives,
51+
ngUpdateModelOnDirective,
52+
ngUpdateModelDebounceDirective,
5153
5254
$AnchorScrollProvider,
5355
$AnimateProvider,
@@ -183,6 +185,8 @@ function publishExternalAPI(angular){
183185
ngChange: ngChangeDirective,
184186
required: requiredDirective,
185187
ngRequired: requiredDirective,
188+
ngUpdateModelOn: ngUpdateModelOnDirective,
189+
ngUpdateModelDebounce: ngUpdateModelDebounceDirective,
186190
ngValue: ngValueDirective
187191
}).
188192
directive({

src/ng/directive/input.js

+190-585
Large diffs are not rendered by default.

src/ng/directive/ngUpdateModel.js

+97
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
'use strict';
2+
3+
/**
4+
* @ngdoc directive
5+
* @name ng.directive:ngUpdateModelOn
6+
* @restrict A
7+
*
8+
* @description
9+
* The `ngUpdateModelOn` directive changes default behavior of model updates. You can customize
10+
* which events will be bound to the `input` elements so that the model update will
11+
* only be triggered when they occur.
12+
*
13+
* This option will be applicable to those `input` elements that descend from the
14+
* element containing the directive. So, if you use `ngUpdateModelOn` on a `form`
15+
* element, the default behavior will be used on the `input` elements within.
16+
*
17+
* See {@link guide/forms this link} for more information about debouncing and custom
18+
* events.
19+
*
20+
* @element ANY
21+
* @param {string} ngUpdateModelOn Allows specifying an event or a comma-delimited list of events
22+
* that will trigger a model update. If it is not set, it defaults to any inmediate change. If
23+
* the list contains "default", the original behavior is also kept. You can also specify an
24+
* object in which the key is the event and the value the particular debouncing timeout to be
25+
* applied to it.
26+
*/
27+
28+
var SIMPLEOBJECT_TEST = /^\s*?\{(.*)\}\s*?$/;
29+
30+
var NgUpdateModelOnController = ['$attrs', '$scope',
31+
function UpdateModelOnController($attrs, $scope) {
32+
33+
var attr = $attrs['ngUpdateModelOn'];
34+
var updateModelOnValue;
35+
var updateModelDebounceValue;
36+
37+
if (SIMPLEOBJECT_TEST.test(attr)) {
38+
updateModelDebounceValue = $scope.$eval(attr);
39+
var keys = [];
40+
for(var k in updateModelDebounceValue) {
41+
keys.push(k);
42+
}
43+
updateModelOnValue = keys.join(',');
44+
}
45+
else {
46+
updateModelOnValue = attr;
47+
}
48+
49+
this.$getEventList = function() {
50+
return updateModelOnValue;
51+
};
52+
53+
this.$getDebounceTimeout = function() {
54+
return updateModelDebounceValue;
55+
};
56+
}];
57+
58+
var ngUpdateModelOnDirective = [function() {
59+
return {
60+
restrict: 'A',
61+
controller: NgUpdateModelOnController
62+
};
63+
}];
64+
65+
66+
/**
67+
* @ngdoc directive
68+
* @name ng.directive:ngUpdateModelDebounce
69+
* @restrict A
70+
*
71+
* @description
72+
* The `ngUpdateModelDebounce` directive allows specifying a debounced timeout to model updates so they
73+
* are not triggerer instantly but after the timer has expired.
74+
*
75+
* If you need to specify different timeouts for each event, you can use
76+
* {@link ng.directive:ngUpdateModelOn ngUpdateModelOn} directive which the object notation.
77+
*
78+
* @element ANY
79+
* @param {integer} ngUpdateModelDebounce Time in milliseconds to wait since the last registered
80+
* content change before triggering a model update.
81+
*/
82+
var NgUpdateModelDebounceController = ['$attrs',
83+
function UpdateModelDebounceController($attrs) {
84+
85+
var updateModelDefaultTimeoutValue = $attrs['ngUpdateModelDebounce'];
86+
87+
this.$getDefaultTimeout = function() {
88+
return updateModelDefaultTimeoutValue;
89+
};
90+
}];
91+
92+
var ngUpdateModelDebounceDirective = [function() {
93+
return {
94+
restrict: 'A',
95+
controller: NgUpdateModelDebounceController
96+
};
97+
}];

src/ng/timeout.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ function $TimeoutProvider() {
2626
*
2727
* You can also use `$timeout` to debounce the call of a function using the returned promise
2828
* as the fourth parameter in the next call. See the following example:
29-
*
30-
* <pre>
29+
*
30+
* ```js
3131
* var debounce;
3232
* var doRealSave = function() {
3333
* // Save model to DB
@@ -36,13 +36,13 @@ function $TimeoutProvider() {
3636
* // debounce call for 2 seconds
3737
* debounce = $timeout(doRealSave, 2000, false, debounce);
3838
* }
39-
* </pre>
39+
* ```
4040
*
4141
* And in the form:
4242
*
43-
* <pre>
43+
* ```html
4444
* <input type="text" ng-model="name" ng-change="save()">
45-
* </pre>
45+
* ```
4646
*
4747
* @param {function()} fn A function, whose execution should be delayed.
4848
* @param {number=} [delay=0] Delay in milliseconds.

0 commit comments

Comments
 (0)