diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart
index 239b2e68b..1ac6f9dce 100644
--- a/lib/directive/ng_model.dart
+++ b/lib/directive/ng_model.dart
@@ -211,12 +211,17 @@ class InputTextLikeDirective {
*
*
*
- * This creates a two-way binding between a number-based input element
- * so long as the ng-model attribute is present on the input element. Whenever
- * the value of the input element changes then the matching model property on the
- * scope will be updated as well as the other way around (when the scope property
- * is updated).
- *
+ * Model:
+ *
+ * num myModel;
+ *
+ * This creates a two-way binding between the input and the named model property
+ * (e.g., myModel in the example above). When processing the input, its value is
+ * read as a [num], via the [dom.InputElement.valueAsNumber] field. If the input
+ * text does not represent a number, then the model is appropriately set to
+ * [double.NAN]. Setting the model property to [null] will clear the input.
+ * Setting the model to [double.NAN] will have no effect (input will be left
+ * unchanged).
*/
@NgDirective(selector: 'input[type=number][ng-model]')
@NgDirective(selector: 'input[type=range][ng-model]')
@@ -225,9 +230,26 @@ class InputNumberLikeDirective {
final NgModel ngModel;
final Scope scope;
+ num get typedValue => inputElement.valueAsNumber;
+ void set typedValue(num value) {
+ // [chalin, 2014-02-16] This post
+ // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-January/024829.html
+ // suggests that setting `valueAsNumber` to null should clear the field, but
+ // it does not. [TODO: put BUG/ISSUE number here]. We implement a
+ // workaround by setting `value`. Clean-up once the bug is fixed.
+ if (value == null) {
+ inputElement.value = null;
+ } else {
+ inputElement.valueAsNumber = value;
+ }
+ }
+
InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.scope) {
ngModel.render = (value) {
- inputElement.value = value == null ? '' : value.toString();
+ if (value != typedValue
+ && (value == null || value is num && !value.isNaN)) {
+ typedValue = value;
+ }
};
inputElement
..onChange.listen(relaxFnArgs(processValue))
@@ -235,7 +257,7 @@ class InputNumberLikeDirective {
}
processValue() {
- var value = num.parse(inputElement.value, (_) => null);
+ num value = typedValue;
if (value != ngModel.viewValue) {
ngModel.dirty = true;
scope.$apply(() => ngModel.viewValue = value);
diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart
index 9240ad1c1..ce1ea6106 100644
--- a/test/directive/ng_model_spec.dart
+++ b/test/directive/ng_model_spec.dart
@@ -109,7 +109,65 @@ describe('ng-model', () {
}));
});
+ /* An input of type number can only have assigned to its [value] field
+ * a string that represents a valid number. Any attempts to assign
+ * any other string will have no effect on the [value] field.
+ *
+ * This function simulates typing the given string value into the input
+ * field regardless of whether it represents a valid number or not. It
+ * has the side-effect of setting the window focus to the input.
+ */
+ void setNumberInputValue(InputElement input, String value) {
+ input..focus()
+ ..dispatchEvent(new TextEvent('textInput', data: value));
+ }
+
describe('type="number" like', () {
+
+ it('should leave input unchanged when text does not represent a valid number', inject((Injector i) {
+ var modelFieldName = 'modelForNumFromInvalid1';
+ var element = _.compile('');
+ dom.querySelector('body').append(element);
+
+ // Subcase: text not representing a valid number.
+ var piX = '3.141x';
+ setNumberInputValue(element, piX);
+ // Because the text is not a valid number, the element value is empty.
+ expect(element.value).toEqual('');
+ // But the selection can confirm that the text is there:
+ element.selectionStart = 0;
+ element.selectionEnd = piX.length;
+ expect(dom.window.getSelection().toString()).toEqual(piX);
+ // When the input is invalid, the model is [double.NAN]:
+ _.triggerEvent(element, 'change');
+ expect(_.rootScope[modelFieldName].isNaN).toBeTruthy();
+
+ // Subcase: text representing a valid number (as if the user had erased
+ // the trailing 'x').
+ num pi = 3.14159;
+ setNumberInputValue(element, pi.toString());
+ _.triggerEvent(element, 'change');
+ expect(element.value).toEqual(pi.toString());
+ expect(_.rootScope[modelFieldName]).toEqual(pi);
+ }));
+
+ it('should not reformat user input to equivalent numeric representation', inject((Injector i) {
+ var modelFieldName = 'modelForNumFromInvalid2';
+ var element = _.compile('');
+ dom.querySelector('body').append(element);
+
+ var ten = '1e1';
+ setNumberInputValue(element, ten);
+ _.triggerEvent(element, 'change');
+ expect(_.rootScope[modelFieldName]).toEqual(10);
+ // Ensure that the input text is literally the same
+ element.selectionStart = 0;
+ // Set the selectionEnd to one beyond ten.length in
+ // case angular added some extra text.
+ element.selectionEnd = ten.length + 1;
+ expect(dom.window.getSelection().toString()).toEqual(ten);
+ }));
+
it('should update input value from model', inject(() {
_.compile('');
_.rootScope.$digest();
@@ -142,7 +200,7 @@ describe('ng-model', () {
expect(_.rootScope.model).toEqual(43);
}));
- it('should update model to null from a blank input value', inject(() {
+ it('should update model to NaN from a blank input value', inject(() {
_.compile('');
Probe probe = _.rootScope.p;
var ngModel = probe.directive(NgModel);
@@ -150,7 +208,7 @@ describe('ng-model', () {
inputElement.value = '';
_.triggerEvent(inputElement, 'change');
- expect(_.rootScope.model).toBeNull();
+ expect(_.rootScope.model.isNaN).toBeTruthy();
}));
it('should update model from the input value for range inputs', inject(() {