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('');