@@ -1065,9 +1065,41 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
1065
1065
* @name ngModelOptions
1066
1066
*
1067
1067
* @description
1068
- * Allows tuning how model updates are done. Using `ngModelOptions` you can specify a custom list of
1069
- * events that will trigger a model update and/or a debouncing delay so that the actual update only
1070
- * takes place when a timer expires; this timer will be reset after another change takes place.
1068
+ * This directive allows you to modify the behaviour of ngModel and input directives within your
1069
+ * application. You can specify an ngModelOptions directive on any element and the settings affect
1070
+ * the ngModel and input directives on all descendent elements.
1071
+ *
1072
+ * The ngModelOptions settings are found by evaluating the value of the ngModelOptions attribute as
1073
+ * an Angular expression. This expression should evaluate to an object, whose properties contain
1074
+ * the settings.
1075
+ *
1076
+ * If a setting is not specified as a property on the object for a particular ngModelOptions directive
1077
+ * then it will inherit that setting from the first ngModelOptions directive found by traversing up the
1078
+ * DOM tree.
1079
+ *
1080
+ * For example in the following fragment of HTML ...
1081
+ *
1082
+ *
1083
+ * ```html
1084
+ * <div ng-model-options="{ allowInvalid: true }">
1085
+ * <form ng-model-options="{ updateOn: \'blur\' }">
1086
+ * <input ng-model-options="{ updateOn: \'default\' }">
1087
+ * </form>
1088
+ * </div>
1089
+ * ```
1090
+ *
1091
+ * ... the `input` element will effective have the follow settings ...
1092
+ *
1093
+ * ```js
1094
+ * { allowInvalid: true, updateOn: 'default' }
1095
+ * ```
1096
+ *
1097
+ *
1098
+ * ## Triggering and debouncing model updates
1099
+ *
1100
+ * The `updateOn` and `debounce` properties allow you to specify a custom list of events that will
1101
+ * trigger a model update and/or a debouncing delay so that the actual update only takes place when
1102
+ * a timer expires; this timer will be reset after another change takes place.
1071
1103
*
1072
1104
* Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
1073
1105
* be different from the value in the actual model. This means that if you update the model you
@@ -1083,7 +1115,130 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
1083
1115
* `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
1084
1116
* to have access to the updated model.
1085
1117
*
1086
- * `ngModelOptions` has an effect on the element it's declared on and its descendants.
1118
+ * The following example shows how to override immediate updates. Changes on the inputs within the
1119
+ * form will update the model only when the control loses focus (blur event). If `escape` key is
1120
+ * pressed while the input field is focused, the value is reset to the value in the current model.
1121
+ *
1122
+ * <example name="ngModelOptions-directive-blur" module="optionsExample">
1123
+ * <file name="index.html">
1124
+ * <div ng-controller="ExampleController">
1125
+ * <form name="userForm">
1126
+ * Name:
1127
+ * <input type="text" name="userName"
1128
+ * ng-model="user.name"
1129
+ * ng-model-options="{ updateOn: 'blur' }"
1130
+ * ng-keyup="cancel($event)" /><br />
1131
+ *
1132
+ * Other data:
1133
+ * <input type="text" ng-model="user.data" /><br />
1134
+ * </form>
1135
+ * <pre>user.name = <span ng-bind="user.name"></span></pre>
1136
+ * </div>
1137
+ * </file>
1138
+ * <file name="app.js">
1139
+ * angular.module('optionsExample', [])
1140
+ * .controller('ExampleController', ['$scope', function($scope) {
1141
+ * $scope.user = { name: 'say', data: '' };
1142
+ *
1143
+ * $scope.cancel = function(e) {
1144
+ * if (e.keyCode == 27) {
1145
+ * $scope.userForm.userName.$rollbackViewValue();
1146
+ * }
1147
+ * };
1148
+ * }]);
1149
+ * </file>
1150
+ * <file name="protractor.js" type="protractor">
1151
+ * var model = element(by.binding('user.name'));
1152
+ * var input = element(by.model('user.name'));
1153
+ * var other = element(by.model('user.data'));
1154
+ *
1155
+ * it('should allow custom events', function() {
1156
+ * input.sendKeys(' hello');
1157
+ * input.click();
1158
+ * expect(model.getText()).toEqual('say');
1159
+ * other.click();
1160
+ * expect(model.getText()).toEqual('say hello');
1161
+ * });
1162
+ *
1163
+ * it('should $rollbackViewValue when model changes', function() {
1164
+ * input.sendKeys(' hello');
1165
+ * expect(input.getAttribute('value')).toEqual('say hello');
1166
+ * input.sendKeys(protractor.Key.ESCAPE);
1167
+ * expect(input.getAttribute('value')).toEqual('say');
1168
+ * other.click();
1169
+ * expect(model.getText()).toEqual('say');
1170
+ * });
1171
+ * </file>
1172
+ * </example>
1173
+ *
1174
+ * The next example shows how to debounce model changes. Model will be updated only 1 sec after last change.
1175
+ * If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
1176
+ *
1177
+ * <example name="ngModelOptions-directive-debounce" module="optionsExample">
1178
+ * <file name="index.html">
1179
+ * <div ng-controller="ExampleController">
1180
+ * <form name="userForm">
1181
+ * Name:
1182
+ * <input type="text" name="userName"
1183
+ * ng-model="user.name"
1184
+ * ng-model-options="{ debounce: 1000 }" />
1185
+ * <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
1186
+ * </form>
1187
+ * <pre>user.name = <span ng-bind="user.name"></span></pre>
1188
+ * </div>
1189
+ * </file>
1190
+ * <file name="app.js">
1191
+ * angular.module('optionsExample', [])
1192
+ * .controller('ExampleController', ['$scope', function($scope) {
1193
+ * $scope.user = { name: 'say' };
1194
+ * }]);
1195
+ * </file>
1196
+ * </example>
1197
+ *
1198
+ * ## Model updates and validation
1199
+ *
1200
+ * The default behaviour in `ngModel` is that the model value is set to `null` when the validation
1201
+ * determines that the value is invalid. By setting the `allowInvalid` property to true, the model
1202
+ * will still be updated even if the value is invalid.
1203
+ *
1204
+ *
1205
+ * ## Connecting to the scope
1206
+ *
1207
+ * By setting the `getterSetter` property to true you are telling ngModel that the `ngModel` expression
1208
+ * on the scope actually refers to a "getter/setter" function rather than the actual value itself.
1209
+ *
1210
+ * The following example shows how to bind to getter/setters:
1211
+ *
1212
+ * <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
1213
+ * <file name="index.html">
1214
+ * <div ng-controller="ExampleController">
1215
+ * <form name="userForm">
1216
+ * Name:
1217
+ * <input type="text" name="userName"
1218
+ * ng-model="user.name"
1219
+ * ng-model-options="{ getterSetter: true }" />
1220
+ * </form>
1221
+ * <pre>user.name = <span ng-bind="user.name()"></span></pre>
1222
+ * </div>
1223
+ * </file>
1224
+ * <file name="app.js">
1225
+ * angular.module('getterSetterExample', [])
1226
+ * .controller('ExampleController', ['$scope', function($scope) {
1227
+ * var _name = 'Brian';
1228
+ * $scope.user = {
1229
+ * name: function(newName) {
1230
+ * return angular.isDefined(newName) ? (_name = newName) : _name;
1231
+ * }
1232
+ * };
1233
+ * }]);
1234
+ * </file>
1235
+ * </example>
1236
+ *
1237
+ *
1238
+ * ## Specifying timezones
1239
+ *
1240
+ * You can specify the timezone that date/time input directives expect by providing its name in the
1241
+ * `timezone` property.
1087
1242
*
1088
1243
* @param {Object } ngModelOptions options to apply to the current model. Valid keys are:
1089
1244
* - `updateOn`: string specifying which event should the input be bound to. You can set several
@@ -1096,138 +1251,42 @@ var DEFAULT_REGEXP = /(\s+|^)default(\s+|$)/;
1096
1251
* - `allowInvalid`: boolean value which indicates that the model can be set with values that did
1097
1252
* not validate correctly instead of the default behavior of setting the model to undefined.
1098
1253
* - `getterSetter`: boolean value which determines whether or not to treat functions bound to
1099
- `ngModel` as getters/setters.
1254
+ * `ngModel` as getters/setters.
1100
1255
* - `timezone`: Defines the timezone to be used to read/write the `Date` instance in the model for
1101
1256
* `<input type="date">`, `<input type="time">`, ... . Right now, the only supported value is `'UTC'`,
1102
1257
* otherwise the default timezone of the browser will be used.
1103
- *
1104
- * @example
1105
-
1106
- The following example shows how to override immediate updates. Changes on the inputs within the
1107
- form will update the model only when the control loses focus (blur event). If `escape` key is
1108
- pressed while the input field is focused, the value is reset to the value in the current model.
1109
-
1110
- <example name="ngModelOptions-directive-blur" module="optionsExample">
1111
- <file name="index.html">
1112
- <div ng-controller="ExampleController">
1113
- <form name="userForm">
1114
- Name:
1115
- <input type="text" name="userName"
1116
- ng-model="user.name"
1117
- ng-model-options="{ updateOn: 'blur' }"
1118
- ng-keyup="cancel($event)" /><br />
1119
-
1120
- Other data:
1121
- <input type="text" ng-model="user.data" /><br />
1122
- </form>
1123
- <pre>user.name = <span ng-bind="user.name"></span></pre>
1124
- </div>
1125
- </file>
1126
- <file name="app.js">
1127
- angular.module('optionsExample', [])
1128
- .controller('ExampleController', ['$scope', function($scope) {
1129
- $scope.user = { name: 'say', data: '' };
1130
-
1131
- $scope.cancel = function(e) {
1132
- if (e.keyCode == 27) {
1133
- $scope.userForm.userName.$rollbackViewValue();
1134
- }
1135
- };
1136
- }]);
1137
- </file>
1138
- <file name="protractor.js" type="protractor">
1139
- var model = element(by.binding('user.name'));
1140
- var input = element(by.model('user.name'));
1141
- var other = element(by.model('user.data'));
1142
-
1143
- it('should allow custom events', function() {
1144
- input.sendKeys(' hello');
1145
- input.click();
1146
- expect(model.getText()).toEqual('say');
1147
- other.click();
1148
- expect(model.getText()).toEqual('say hello');
1149
- });
1150
-
1151
- it('should $rollbackViewValue when model changes', function() {
1152
- input.sendKeys(' hello');
1153
- expect(input.getAttribute('value')).toEqual('say hello');
1154
- input.sendKeys(protractor.Key.ESCAPE);
1155
- expect(input.getAttribute('value')).toEqual('say');
1156
- other.click();
1157
- expect(model.getText()).toEqual('say');
1158
- });
1159
- </file>
1160
- </example>
1161
-
1162
- This one shows how to debounce model changes. Model will be updated only 1 sec after last change.
1163
- If the `Clear` button is pressed, any debounced action is canceled and the value becomes empty.
1164
-
1165
- <example name="ngModelOptions-directive-debounce" module="optionsExample">
1166
- <file name="index.html">
1167
- <div ng-controller="ExampleController">
1168
- <form name="userForm">
1169
- Name:
1170
- <input type="text" name="userName"
1171
- ng-model="user.name"
1172
- ng-model-options="{ debounce: 1000 }" />
1173
- <button ng-click="userForm.userName.$rollbackViewValue(); user.name=''">Clear</button><br />
1174
- </form>
1175
- <pre>user.name = <span ng-bind="user.name"></span></pre>
1176
- </div>
1177
- </file>
1178
- <file name="app.js">
1179
- angular.module('optionsExample', [])
1180
- .controller('ExampleController', ['$scope', function($scope) {
1181
- $scope.user = { name: 'say' };
1182
- }]);
1183
- </file>
1184
- </example>
1185
-
1186
- This one shows how to bind to getter/setters:
1187
-
1188
- <example name="ngModelOptions-directive-getter-setter" module="getterSetterExample">
1189
- <file name="index.html">
1190
- <div ng-controller="ExampleController">
1191
- <form name="userForm">
1192
- Name:
1193
- <input type="text" name="userName"
1194
- ng-model="user.name"
1195
- ng-model-options="{ getterSetter: true }" />
1196
- </form>
1197
- <pre>user.name = <span ng-bind="user.name()"></span></pre>
1198
- </div>
1199
- </file>
1200
- <file name="app.js">
1201
- angular.module('getterSetterExample', [])
1202
- .controller('ExampleController', ['$scope', function($scope) {
1203
- var _name = 'Brian';
1204
- $scope.user = {
1205
- name: function(newName) {
1206
- return angular.isDefined(newName) ? (_name = newName) : _name;
1207
- }
1208
- };
1209
- }]);
1210
- </file>
1211
- </example>
1212
1258
*/
1213
1259
var ngModelOptionsDirective = function ( ) {
1214
1260
return {
1215
1261
restrict : 'A' ,
1216
- controller : [ '$scope' , '$attrs' , function ( $scope , $attrs ) {
1217
- var that = this ;
1218
- this . $options = copy ( $scope . $eval ( $attrs . ngModelOptions ) ) ;
1219
- // Allow adding/overriding bound events
1220
- if ( this . $options . updateOn !== undefined ) {
1221
- this . $options . updateOnDefault = false ;
1222
- // extract "default" pseudo-event from list of events that can trigger a model update
1223
- this . $options . updateOn = trim ( this . $options . updateOn . replace ( DEFAULT_REGEXP , function ( ) {
1224
- that . $options . updateOnDefault = true ;
1225
- return ' ' ;
1226
- } ) ) ;
1227
- } else {
1228
- this . $options . updateOnDefault = true ;
1262
+ // ngModelOptions needs to run before ngModel and input directives
1263
+ priority : 10 ,
1264
+ require : [ 'ngModelOptions' , '?^^ngModelOptions' ] ,
1265
+ controller : function ( ) { } ,
1266
+ link : {
1267
+ pre : function ngModelOptionsPreLinkFn ( scope , element , attrs , ctrls ) {
1268
+ var optionsCtrl = ctrls [ 0 ] ;
1269
+ var parentOptions = ctrls [ 1 ] ? ctrls [ 1 ] . $localOptions : { } ;
1270
+
1271
+ // Store the raw options taken from the attributes (after inheriting parent options)
1272
+ optionsCtrl . $localOptions = extend ( { } , scope . $eval ( attrs . ngModelOptions ) , parentOptions ) ;
1273
+
1274
+ // Make a copy and manipulate the options to make them ready to be consumed
1275
+ optionsCtrl . $options = copy ( optionsCtrl . $localOptions ) ;
1276
+
1277
+ // Allow adding/overriding bound events
1278
+ if ( optionsCtrl . $options . updateOn !== undefined ) {
1279
+ optionsCtrl . $options . updateOnDefault = false ;
1280
+ // extract "default" pseudo-event from list of events that can trigger a model update
1281
+ optionsCtrl . $options . updateOn = trim ( optionsCtrl . $options . updateOn . replace ( DEFAULT_REGEXP , function ( ) {
1282
+ optionsCtrl . $options . updateOnDefault = true ;
1283
+ return ' ' ;
1284
+ } ) ) ;
1285
+ } else {
1286
+ optionsCtrl . $options . updateOnDefault = true ;
1287
+ }
1229
1288
}
1230
- } ]
1289
+ }
1231
1290
} ;
1232
1291
} ;
1233
1292
0 commit comments