From e51ed72c92391ec52b19fe20b9040275ec0f5415 Mon Sep 17 00:00:00 2001 From: Tero Parviainen Date: Sat, 8 Feb 2014 10:49:26 +0200 Subject: [PATCH] feat(ngModel): Treat the values of number and range inputs as numbers Add a InputNumberLikeDirective that binds the model as a numeric value, so that it can be bound to num types on scopes. Select the directive for number and range input types. --- lib/directive/module.dart | 1 + lib/directive/ng_model.dart | 41 ++++++++++- test/directive/ng_model_spec.dart | 112 +++++++++++++++++++++--------- 3 files changed, 121 insertions(+), 33 deletions(-) diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 0e6004392..c31f3ce1d 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 2eb46f672..8e15fcfc2 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -177,7 +177,7 @@ class InputCheckboxDirective { /** * Usage: * - * + * * * * This creates a two-way binding between any string-based input element @@ -192,7 +192,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; @@ -232,6 +231,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 ff1bd27e9..8f10b3c80 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -51,37 +51,6 @@ describe('ng-model', () { expect(_.rootScope.model).toEqual('def'); })); - it('should update model from the input value for type=number', inject(() { - _.compile(''); - Probe probe = _.rootScope.p; - var ngModel = probe.directive(NgModel); - InputElement inputElement = probe.element; - - inputElement.value = '12'; - _.triggerEvent(inputElement, 'change'); - expect(_.rootScope.model).toEqual('12'); - - inputElement.value = '14'; - var input = probe.directive(InputTextLikeDirective); - input.processValue(); - expect(_.rootScope.model).toEqual('14'); - })); - - it('should update input type=number to blank when model is null', inject(() { - _.compile(''); - Probe probe = _.rootScope.p; - var ngModel = probe.directive(NgModel); - InputElement inputElement = probe.element; - - inputElement.value = '12'; - _.triggerEvent(inputElement, 'change'); - expect(_.rootScope.model).toEqual('12'); - - _.rootScope.model = null; - _.rootScope.$apply(); - expect(inputElement.value).toEqual(''); - })); - it('should write to input only if value is different', inject(() { var scope = _.rootScope; var element = new dom.InputElement(); @@ -109,6 +78,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('');