Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 30 additions & 8 deletions lib/directive/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,17 @@ class InputTextLikeDirective {
*
* <input type="number|range" ng-model="myModel">
*
* 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]')
Expand All @@ -225,17 +230,34 @@ 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))
..onInput.listen(relaxFnArgs(processValue));
}

processValue() {
var value = num.parse(inputElement.value, (_) => null);
num value = typedValue;
if (value != ngModel.viewValue) {
ngModel.dirty = true;
scope.$apply(() => ngModel.viewValue = value);
Expand Down
62 changes: 60 additions & 2 deletions test/directive/ng_model_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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('<input type="number" ng-model="$modelFieldName">');
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('<input type="number" ng-model="$modelFieldName">');
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('<input type="number" ng-model="model">');
_.rootScope.$digest();
Expand Down Expand Up @@ -142,15 +200,15 @@ 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('<input type="number" ng-model="model" probe="p">');
Probe probe = _.rootScope.p;
var ngModel = probe.directive(NgModel);
InputElement inputElement = probe.element;

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(() {
Expand Down