diff --git a/lib/directive/module.dart b/lib/directive/module.dart
index e92ab8ceb..102e3406a 100644
--- a/lib/directive/module.dart
+++ b/lib/directive/module.dart
@@ -52,6 +52,7 @@ class NgDirectiveModule extends Module {
value(NgShalowRepeatDirective, null);
value(NgShowDirective, null);
value(InputTextLikeDirective, null);
+ value(InputNumberLikeDirective, null);
value(InputRadioDirective, null);
value(InputCheckboxDirective, null);
value(InputSelectDirective, null);
diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart
index b68250769..9e9d4c5c0 100644
--- a/lib/directive/ng_model.dart
+++ b/lib/directive/ng_model.dart
@@ -130,7 +130,7 @@ class InputCheckboxDirective {
/**
* Usage:
*
- *
+ *
*
*
* This creates a two-way binding between any string-based input element
@@ -145,7 +145,6 @@ class InputCheckboxDirective {
@NgDirective(selector: 'input[type=password][ng-model]')
@NgDirective(selector: 'input[type=url][ng-model]')
@NgDirective(selector: 'input[type=email][ng-model]')
-@NgDirective(selector: 'input[type=number][ng-model]')
@NgDirective(selector: 'input[type=search][ng-model]')
class InputTextLikeDirective {
final dom.Element inputElement;
@@ -185,6 +184,44 @@ class InputTextLikeDirective {
}
}
+/**
+ * Usage:
+ *
+ *
+ *
+ * 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).
+ *
+ */
+@NgDirective(selector: 'input[type=number][ng-model]')
+@NgDirective(selector: 'input[type=range][ng-model]')
+class InputNumberLikeDirective {
+ final dom.InputElement inputElement;
+ final NgModel ngModel;
+ final Scope scope;
+
+ InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.scope) {
+ ngModel.render = (value) {
+ inputElement.value = value == null ? '' : value.toString();
+ };
+ inputElement
+ ..onChange.listen(relaxFnArgs(processValue))
+ ..onInput.listen(relaxFnArgs(processValue));
+ }
+
+ processValue() {
+ var value = num.parse(inputElement.value, (_) => null);
+ if (value != ngModel.viewValue) {
+ ngModel.dirty = true;
+ scope.$apply(() => ngModel.viewValue = value);
+ }
+ ngModel.validate();
+ }
+}
+
class _UidCounter {
static final int CHAR_0 = "0".codeUnitAt(0);
static final int CHAR_9 = "9".codeUnitAt(0);
diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart
index 3a71e8f15..8445713a2 100644
--- a/test/directive/ng_model_spec.dart
+++ b/test/directive/ng_model_spec.dart
@@ -59,12 +59,12 @@ describe('ng-model', () {
inputElement.value = '12';
_.triggerEvent(inputElement, 'change');
- expect(_.rootScope.model).toEqual('12');
+ expect(_.rootScope.model).toEqual(12);
inputElement.value = '14';
- var input = probe.directive(InputTextLikeDirective);
+ var input = probe.directive(InputNumberLikeDirective);
input.processValue();
- expect(_.rootScope.model).toEqual('14');
+ expect(_.rootScope.model).toEqual(14);
}));
it('should update input type=number to blank when model is null', inject(() {
@@ -75,7 +75,7 @@ describe('ng-model', () {
inputElement.value = '12';
_.triggerEvent(inputElement, 'change');
- expect(_.rootScope.model).toEqual('12');
+ expect(_.rootScope.model).toEqual(12);
_.rootScope.model = null;
_.rootScope.$apply();
@@ -109,6 +109,87 @@ describe('ng-model', () {
}));
});
+ describe('type="number" like', () {
+ it('should update input value from model', inject(() {
+ _.compile('');
+ _.rootScope.$digest();
+
+ _.rootScope.$apply('model = 42');
+ expect((_.rootElement as dom.InputElement).value).toEqual('42');
+ }));
+
+ it('should update input value from model for range inputs', inject(() {
+ _.compile('');
+ _.rootScope.$digest();
+
+ _.rootScope.$apply('model = 42');
+ expect((_.rootElement as dom.InputElement).value).toEqual('42');
+ }));
+
+ it('should update model from the input value', inject(() {
+ _.compile('');
+ Probe probe = _.rootScope.p;
+ var ngModel = probe.directive(NgModel);
+ InputElement inputElement = probe.element;
+
+ inputElement.value = '42';
+ _.triggerEvent(inputElement, 'change');
+ expect(_.rootScope.model).toEqual(42);
+
+ inputElement.value = '43';
+ var input = probe.directive(InputNumberLikeDirective);
+ input.processValue();
+ expect(_.rootScope.model).toEqual(43);
+ }));
+
+ it('should update model to null from a blank input value', inject(() {
+ _.compile('');
+ Probe probe = _.rootScope.p;
+ var ngModel = probe.directive(NgModel);
+ InputElement inputElement = probe.element;
+
+ inputElement.value = '';
+ _.triggerEvent(inputElement, 'change');
+ expect(_.rootScope.model).toBeNull();
+ }));
+
+ it('should update model from the input value for range inputs', inject(() {
+ _.compile('');
+ Probe probe = _.rootScope.p;
+ var ngModel = probe.directive(NgModel);
+ InputElement inputElement = probe.element;
+
+ inputElement.value = '42';
+ _.triggerEvent(inputElement, 'change');
+ expect(_.rootScope.model).toEqual(42);
+
+ inputElement.value = '43';
+ var input = probe.directive(InputNumberLikeDirective);
+ input.processValue();
+ expect(_.rootScope.model).toEqual(43);
+ }));
+
+ it('should update model to a native default value from a blank range input value', inject(() {
+ _.compile('');
+ Probe probe = _.rootScope.p;
+ var ngModel = probe.directive(NgModel);
+ InputElement inputElement = probe.element;
+
+ inputElement.value = '';
+ _.triggerEvent(inputElement, 'change');
+ expect(_.rootScope.model).toBeDefined();
+ }));
+
+ it('should render null as blank', inject(() {
+ _.compile('');
+ _.rootScope.$digest();
+
+ _.rootScope.$apply('model = null');
+ expect((_.rootElement as dom.InputElement).value).toEqual('');
+ }));
+
+ });
+
describe('type="password"', () {
it('should update input value from model', inject(() {
_.compile('');