@@ -16,7 +16,7 @@ var DATETIMELOCAL_REGEXP = /^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d)$/;
16
16
var WEEK_REGEXP = / ^ ( \d { 4 } ) - W ( \d \d ) $ / ;
17
17
var MONTH_REGEXP = / ^ ( \d { 4 } ) - ( \d \d ) $ / ;
18
18
var TIME_REGEXP = / ^ ( \d \d ) : ( \d \d ) $ / ;
19
- var DEFAULT_REGEXP = / ( \b | ^ ) d e f a u l t ( \b | $ ) / ;
19
+ var DEFAULT_REGEXP = / ( \s + | ^ ) d e f a u l t ( \s + | $ ) / ;
20
20
21
21
var inputType = {
22
22
@@ -934,51 +934,42 @@ function textInputType(scope, element, attr, ctrl, $sniffer, $browser) {
934
934
}
935
935
} ;
936
936
937
- // Allow adding/overriding bound events
938
- if ( ctrl . $options && ctrl . $options . updateOn ) {
939
- // bind to user-defined events
940
- element . on ( ctrl . $options . updateOn , listener ) ;
941
- }
942
-
943
- // setup default events if requested
944
- if ( ! ctrl . $options || ctrl . $options . updateOnDefault ) {
945
- // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
946
- // input event on backspace, delete or cut
947
- if ( $sniffer . hasEvent ( 'input' ) ) {
948
- element . on ( 'input' , listener ) ;
949
- } else {
950
- var timeout ;
951
-
952
- var deferListener = function ( ev ) {
953
- if ( ! timeout ) {
954
- timeout = $browser . defer ( function ( ) {
955
- listener ( ev ) ;
956
- timeout = null ;
957
- } ) ;
958
- }
959
- } ;
937
+ // if the browser does support "input" event, we are fine - except on IE9 which doesn't fire the
938
+ // input event on backspace, delete or cut
939
+ if ( $sniffer . hasEvent ( 'input' ) ) {
940
+ element . on ( 'input' , listener ) ;
941
+ } else {
942
+ var timeout ;
943
+
944
+ var deferListener = function ( ev ) {
945
+ if ( ! timeout ) {
946
+ timeout = $browser . defer ( function ( ) {
947
+ listener ( ev ) ;
948
+ timeout = null ;
949
+ } ) ;
950
+ }
951
+ } ;
960
952
961
- element . on ( 'keydown' , function ( event ) {
962
- var key = event . keyCode ;
953
+ element . on ( 'keydown' , function ( event ) {
954
+ var key = event . keyCode ;
963
955
964
- // ignore
965
- // command modifiers arrows
966
- if ( key === 91 || ( 15 < key && key < 19 ) || ( 37 <= key && key <= 40 ) ) return ;
956
+ // ignore
957
+ // command modifiers arrows
958
+ if ( key === 91 || ( 15 < key && key < 19 ) || ( 37 <= key && key <= 40 ) ) return ;
967
959
968
- deferListener ( event ) ;
969
- } ) ;
960
+ deferListener ( event ) ;
961
+ } ) ;
970
962
971
- // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
972
- if ( $sniffer . hasEvent ( 'paste' ) ) {
973
- element . on ( 'paste cut' , deferListener ) ;
974
- }
963
+ // if user modifies input value using context menu in IE, we need "paste" and "cut" events to catch it
964
+ if ( $sniffer . hasEvent ( 'paste' ) ) {
965
+ element . on ( 'paste cut' , deferListener ) ;
975
966
}
976
-
977
- // if user paste into input using mouse on older browser
978
- // or form autocomplete on newer browser, we need "change" event to catch it
979
- element . on ( 'change' , listener ) ;
980
967
}
981
968
969
+ // if user paste into input using mouse on older browser
970
+ // or form autocomplete on newer browser, we need "change" event to catch it
971
+ element . on ( 'change' , listener ) ;
972
+
982
973
ctrl . $render = function ( ) {
983
974
element . val ( ctrl . $isEmpty ( ctrl . $viewValue ) ? '' : ctrl . $viewValue ) ;
984
975
} ;
@@ -1221,15 +1212,7 @@ function radioInputType(scope, element, attr, ctrl) {
1221
1212
}
1222
1213
} ;
1223
1214
1224
- // Allow adding/overriding bound events
1225
- if ( ctrl . $options && ctrl . $options . updateOn ) {
1226
- // bind to user-defined events
1227
- element . on ( ctrl . $options . updateOn , listener ) ;
1228
- }
1229
-
1230
- if ( ! ctrl . $options || ctrl . $options . updateOnDefault ) {
1231
- element . on ( 'click' , listener ) ;
1232
- }
1215
+ element . on ( 'click' , listener ) ;
1233
1216
1234
1217
ctrl . $render = function ( ) {
1235
1218
var value = attr . value ;
@@ -1252,15 +1235,7 @@ function checkboxInputType(scope, element, attr, ctrl) {
1252
1235
} ) ;
1253
1236
} ;
1254
1237
1255
- // Allow adding/overriding bound events
1256
- if ( ctrl . $options && ctrl . $options . updateOn ) {
1257
- // bind to user-defined events
1258
- element . on ( ctrl . $options . updateOn , listener ) ;
1259
- }
1260
-
1261
- if ( ! ctrl . $options || ctrl . $options . updateOnDefault ) {
1262
- element . on ( 'click' , listener ) ;
1263
- }
1238
+ element . on ( 'click' , listener ) ;
1264
1239
1265
1240
ctrl . $render = function ( ) {
1266
1241
element [ 0 ] . checked = ctrl . $viewValue ;
@@ -1704,22 +1679,22 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1704
1679
1705
1680
/**
1706
1681
* @ngdoc method
1707
- * @name ngModel.NgModelController#$cancelUpdate
1682
+ * @name ngModel.NgModelController#$rollbackViewValue
1708
1683
*
1709
1684
* @description
1710
- * Cancel an update and reset the input element's value to prevent an update to the `$viewValue `,
1685
+ * Cancel an update and reset the input element's value to prevent an update to the `$modelValue `,
1711
1686
* which may be caused by a pending debounced event or because the input is waiting for a some
1712
1687
* future event.
1713
1688
*
1714
1689
* If you have an input that uses `ng-model-options` to set up debounced events or events such
1715
- * as blur you can have a situation where there is a period when the value of the input element
1716
- * is out of synch with the ngModel's `$viewValue `.
1690
+ * as blur you can have a situation where there is a period when the `$viewValue`
1691
+ * is out of synch with the ngModel's `$modelValue `.
1717
1692
*
1718
1693
* In this case, you can run into difficulties if you try to update the ngModel's `$modelValue`
1719
1694
* programmatically before these debounced/future events have resolved/occurred, because Angular's
1720
1695
* dirty checking mechanism is not able to tell whether the model has actually changed or not.
1721
1696
*
1722
- * The `$cancelUpdate ()` method should be called before programmatically changing the model of an
1697
+ * The `$rollbackViewValue ()` method should be called before programmatically changing the model of an
1723
1698
* input which may have such events pending. This is important in order to make sure that the
1724
1699
* input field will be updated with the new model value and any pending operations are cancelled.
1725
1700
*
@@ -1730,7 +1705,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1730
1705
* .controller('CancelUpdateCtrl', function($scope) {
1731
1706
* $scope.resetWithCancel = function (e) {
1732
1707
* if (e.keyCode == 27) {
1733
- * $scope.myForm.myInput1.$cancelUpdate ();
1708
+ * $scope.myForm.myInput1.$rollbackViewValue ();
1734
1709
* $scope.myValue = '';
1735
1710
* }
1736
1711
* };
@@ -1749,26 +1724,39 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1749
1724
* <p>Now see what happens if you start typing then press the Escape key</p>
1750
1725
*
1751
1726
* <form name="myForm" ng-model-options="{ updateOn: 'blur' }">
1752
- * <p>With $cancelUpdate ()</p>
1727
+ * <p>With $rollbackViewValue ()</p>
1753
1728
* <input name="myInput1" ng-model="myValue" ng-keydown="resetWithCancel($event)"><br/>
1754
1729
* myValue: "{{ myValue }}"
1755
1730
*
1756
- * <p>Without $cancelUpdate ()</p>
1731
+ * <p>Without $rollbackViewValue ()</p>
1757
1732
* <input name="myInput2" ng-model="myValue" ng-keydown="resetWithoutCancel($event)"><br/>
1758
1733
* myValue: "{{ myValue }}"
1759
1734
* </form>
1760
1735
* </div>
1761
1736
* </file>
1762
1737
* </example>
1763
1738
*/
1764
- this . $cancelUpdate = function ( ) {
1739
+ this . $rollbackViewValue = function ( ) {
1765
1740
$timeout . cancel ( pendingDebounce ) ;
1741
+ ctrl . $viewValue = ctrl . $$lastCommittedViewValue ;
1766
1742
ctrl . $render ( ) ;
1767
1743
} ;
1768
1744
1769
- // update the view value
1770
- this . $$realSetViewValue = function ( value ) {
1771
- ctrl . $viewValue = value ;
1745
+ /**
1746
+ * @ngdoc method
1747
+ * @name ngModel.NgModelController#$commitViewValue
1748
+ *
1749
+ * @description
1750
+ * Commit a pending update to the `$modelValue`.
1751
+ *
1752
+ * Updates may be pending by a debounced event or because the input is waiting for a some future
1753
+ * event defined in `ng-model-options`. this method is rarely needed as `NgModelController`
1754
+ * usually handles calling this in response to input events.
1755
+ */
1756
+ this . $commitViewValue = function ( ) {
1757
+ var value = ctrl . $viewValue ;
1758
+ ctrl . $$lastCommittedViewValue = value ;
1759
+ $timeout . cancel ( pendingDebounce ) ;
1772
1760
1773
1761
// change to dirty
1774
1762
if ( ctrl . $pristine ) {
@@ -1813,6 +1801,9 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1813
1801
*
1814
1802
* Lastly, all the registered change listeners, in the `$viewChangeListeners` list, are called.
1815
1803
*
1804
+ * In case the {@link ng.directive:ngModelOptions ngModelOptions} directive is used with `updateOn`
1805
+ * and the `default` trigger is not listed, all those actions will remain pending until one of the
1806
+ * `updateOn` events is triggered on the DOM element.
1816
1807
* All these actions will be debounced if the {@link ng.directive:ngModelOptions ngModelOptions}
1817
1808
* directive is used with a custom debounce for this particular event.
1818
1809
*
@@ -1822,6 +1813,13 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1822
1813
* @param {string } trigger Event that triggered the update.
1823
1814
*/
1824
1815
this . $setViewValue = function ( value , trigger ) {
1816
+ ctrl . $viewValue = value ;
1817
+ if ( ! ctrl . $options || ctrl . $options . updateOnDefault ) {
1818
+ ctrl . $$debounceViewValueCommit ( trigger ) ;
1819
+ }
1820
+ } ;
1821
+
1822
+ this . $$debounceViewValueCommit = function ( trigger ) {
1825
1823
var debounceDelay = 0 ,
1826
1824
options = ctrl . $options ,
1827
1825
debounce ;
@@ -1840,10 +1838,10 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1840
1838
$timeout . cancel ( pendingDebounce ) ;
1841
1839
if ( debounceDelay ) {
1842
1840
pendingDebounce = $timeout ( function ( ) {
1843
- ctrl . $$realSetViewValue ( value ) ;
1841
+ ctrl . $commitViewValue ( ) ;
1844
1842
} , debounceDelay ) ;
1845
1843
} else {
1846
- ctrl . $$realSetViewValue ( value ) ;
1844
+ ctrl . $commitViewValue ( ) ;
1847
1845
}
1848
1846
} ;
1849
1847
@@ -1863,7 +1861,7 @@ var NgModelController = ['$scope', '$exceptionHandler', '$attrs', '$element', '$
1863
1861
}
1864
1862
1865
1863
if ( ctrl . $viewValue !== value ) {
1866
- ctrl . $viewValue = value ;
1864
+ ctrl . $viewValue = ctrl . $$lastCommittedViewValue = value ;
1867
1865
ctrl . $render ( ) ;
1868
1866
}
1869
1867
}
@@ -2001,6 +1999,16 @@ var ngModelDirective = function() {
2001
1999
scope . $on ( '$destroy' , function ( ) {
2002
2000
formCtrl . $removeControl ( modelCtrl ) ;
2003
2001
} ) ;
2002
+ } ,
2003
+ post : function ( scope , element , attr , ctrls ) {
2004
+ var modelCtrl = ctrls [ 0 ] ;
2005
+ if ( modelCtrl . $options && modelCtrl . $options . updateOn ) {
2006
+ element . on ( modelCtrl . $options . updateOn , function ( ev ) {
2007
+ scope . $apply ( function ( ) {
2008
+ modelCtrl . $$debounceViewValueCommit ( ev && ev . type ) ;
2009
+ } ) ;
2010
+ } ) ;
2011
+ }
2004
2012
}
2005
2013
}
2006
2014
} ;
@@ -2279,14 +2287,18 @@ var ngValueDirective = function() {
2279
2287
*
2280
2288
* Given the nature of `ngModelOptions`, the value displayed inside input fields in the view might
2281
2289
* be different than the value in the actual model. This means that if you update the model you
2282
- * should also invoke {@link ngModel.NgModelController `$cancelUpdate `} on the relevant input field in
2290
+ * should also invoke {@link ngModel.NgModelController `$rollbackViewValue `} on the relevant input field in
2283
2291
* order to make sure it is synchronized with the model and that any debounced action is canceled.
2284
2292
*
2285
- * The easiest way to reference the control's {@link ngModel.NgModelController `$cancelUpdate `}
2293
+ * The easiest way to reference the control's {@link ngModel.NgModelController `$rollbackViewValue `}
2286
2294
* method is by making sure the input is placed inside a form that has a `name` attribute. This is
2287
2295
* important because `form` controllers are published to the related scope under the name in their
2288
2296
* `name` attribute.
2289
2297
*
2298
+ * Any pending changes will take place immediately when an enclosing form is submitted via the
2299
+ * `submit` event. Note that `ngClick` events will occur before the model is updated. Use `ngSubmit`
2300
+ * to have access to the updated model.
2301
+ *
2290
2302
* @param {Object } ngModelOptions options to apply to the current model. Valid keys are:
2291
2303
* - `updateOn`: string specifying which event should be the input bound to. You can set several
2292
2304
* events using an space delimited list. There is a special event called `default` that
@@ -2324,7 +2336,7 @@ var ngValueDirective = function() {
2324
2336
2325
2337
$scope.cancel = function (e) {
2326
2338
if (e.keyCode == 27) {
2327
- $scope.userForm.userName.$cancelUpdate ();
2339
+ $scope.userForm.userName.$rollbackViewValue ();
2328
2340
}
2329
2341
};
2330
2342
}
@@ -2342,7 +2354,7 @@ var ngValueDirective = function() {
2342
2354
expect(model.getText()).toEqual('say hello');
2343
2355
});
2344
2356
2345
- it('should $cancelUpdate when model changes', function() {
2357
+ it('should $rollbackViewValue when model changes', function() {
2346
2358
input.sendKeys(' hello');
2347
2359
expect(input.getAttribute('value')).toEqual('say hello');
2348
2360
input.sendKeys(protractor.Key.ESCAPE);
@@ -2364,7 +2376,7 @@ var ngValueDirective = function() {
2364
2376
<input type="text" name="userName"
2365
2377
ng-model="user.name"
2366
2378
ng-model-options="{ debounce: 1000 }" />
2367
- <button ng-click="userForm.userName.$cancelUpdate (); user.name=''">Clear</button><br />
2379
+ <button ng-click="userForm.userName.$rollbackViewValue (); user.name=''">Clear</button><br />
2368
2380
</form>
2369
2381
<pre>user.name = <span ng-bind="user.name"></span></pre>
2370
2382
</div>
@@ -2382,13 +2394,13 @@ var ngModelOptionsDirective = function() {
2382
2394
var that = this ;
2383
2395
this . $options = $scope . $eval ( $attrs . ngModelOptions ) ;
2384
2396
// Allow adding/overriding bound events
2385
- if ( this . $options . updateOn ) {
2397
+ if ( this . $options . updateOn !== undefined ) {
2386
2398
this . $options . updateOnDefault = false ;
2387
2399
// extract "default" pseudo-event from list of events that can trigger a model update
2388
- this . $options . updateOn = this . $options . updateOn . replace ( DEFAULT_REGEXP , function ( ) {
2400
+ this . $options . updateOn = trim ( this . $options . updateOn . replace ( DEFAULT_REGEXP , function ( ) {
2389
2401
that . $options . updateOnDefault = true ;
2390
2402
return ' ' ;
2391
- } ) ;
2403
+ } ) ) ;
2392
2404
} else {
2393
2405
this . $options . updateOnDefault = true ;
2394
2406
}
0 commit comments