From 5fd2354042f96f4db5d76dadfbe6c1d744bef9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 5 Mar 2014 17:34:23 -0500 Subject: [PATCH 1/2] feat(NgModelValidator): provide support for min and max validations on number input fields --- lib/directive/module.dart | 2 + lib/directive/ng_model_validators.dart | 84 ++++++++++ test/directive/ng_model_validators_spec.dart | 154 +++++++++++++++++++ 3 files changed, 240 insertions(+) diff --git a/lib/directive/module.dart b/lib/directive/module.dart index 6bd55d707..ef8599276 100644 --- a/lib/directive/module.dart +++ b/lib/directive/module.dart @@ -82,6 +82,8 @@ class NgDirectiveModule extends Module { value(NgModelUrlValidator, null); value(NgModelEmailValidator, null); value(NgModelNumberValidator, null); + value(NgModelMaxNumberValidator, null); + value(NgModelMinNumberValidator, null); value(NgModelPatternValidator, null); value(NgModelMinLengthValidator, null); value(NgModelMaxLengthValidator, null); diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index c816efccf..303dd5869 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -101,6 +101,90 @@ class NgModelNumberValidator implements NgValidatable { } } +/** + * Validates the model to see if the numeric value than or equal to the max value. + */ +@NgDirective(selector: 'input[type=number][ng-model][max]') +@NgDirective( + selector: 'input[type=number][ng-model][ng-max]', + map: const {'ng-max': '=>max'}) +class NgModelMaxNumberValidator implements NgValidatable { + + double _max; + String get name => 'max'; + + NgModelMaxNumberValidator(NgModel ngModel) { + ngModel.addValidator(this); + } + + @NgAttr('max') + get max => _max; + set max(value) { + try { + num parsedValue = double.parse(value); + _max = parsedValue.isNaN ? _max : parsedValue; + } catch(e) {}; + } + + bool isValid(value) { + if (value == null || max == null) return true; + + try { + num parsedValue = double.parse(value.toString()); + if (!parsedValue.isNaN) { + return parsedValue <= max; + } + } catch(exception, stackTrace) {} + + //this validator doesn't care if the type conversation fails or the value + //is not a number (NaN) because NgModelNumberValidator will handle the + //number-based validation either way. + return true; + } +} + +/** + * Validates the model to see if the numeric value is greater than or equal to the min value. + */ +@NgDirective(selector: 'input[type=number][ng-model][min]') +@NgDirective( + selector: 'input[type=number][ng-model][ng-min]', + map: const {'ng-min': '=>min'}) +class NgModelMinNumberValidator implements NgValidatable { + + double _min; + String get name => 'min'; + + NgModelMinNumberValidator(NgModel ngModel) { + ngModel.addValidator(this); + } + + @NgAttr('min') + get min => _min; + set min(value) { + try { + num parsedValue = double.parse(value); + _min = parsedValue.isNaN ? _min : parsedValue; + } catch(e) {}; + } + + bool isValid(value) { + if (value == null || min == null) return true; + + try { + num parsedValue = double.parse(value.toString()); + if (!parsedValue.isNaN) { + return parsedValue >= min; + } + } catch(exception, stackTrace) {} + + //this validator doesn't care if the type conversation fails or the value + //is not a number (NaN) because NgModelNumberValidator will handle the + //number-based validation either way. + return true; + } +} + /** * Validates the model to see if its contents match the given pattern present on either the * HTML pattern or ng-pattern attributes present on the input element. diff --git a/test/directive/ng_model_validators_spec.dart b/test/directive/ng_model_validators_spec.dart index a549d0d6b..6b5e1eb9c 100644 --- a/test/directive/ng_model_validators_spec.dart +++ b/test/directive/ng_model_validators_spec.dart @@ -163,6 +163,160 @@ void main() { expect(model.valid).toEqual(false); expect(model.invalid).toEqual(true); })); + + it('should perform a max number validation if a max attribute value is present', + inject((RootScope scope) { + + _.compile(''); + Probe probe = _.rootScope.context['i']; + var model = probe.directive(NgModel); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "8"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('max')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "99"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('max')).toBe(true); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "a"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('max')).toBe(false); + expect(model.hasError('number')).toBe(true); + })); + + it('should perform a max number validation if a ng-max attribute value is present and/or changed', + inject((RootScope scope) { + + _.compile(''); + Probe probe = _.rootScope.context['i']; + var model = probe.directive(NgModel); + + //should be valid even when no number is present + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('max')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "20"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('max')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['maxVal'] = "19"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('max')).toBe(true); + + _.rootScope.apply(() { + _.rootScope.context['maxVal'] = "22"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('max')).toBe(false); + })); + + it('should perform a min number validation if a min attribute value is present', + inject((RootScope scope) { + + _.compile(''); + Probe probe = _.rootScope.context['i']; + var model = probe.directive(NgModel); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "8"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('min')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "-20"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('min')).toBe(true); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "x"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('min')).toBe(false); + expect(model.hasError('number')).toBe(true); + })); + + it('should perform a min number validation if a ng-min attribute value is present and/or changed', + inject((RootScope scope) { + + _.compile(''); + Probe probe = _.rootScope.context['i']; + var model = probe.directive(NgModel); + + //should be valid even when no number is present + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('min')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "5"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('min')).toBe(false); + + _.rootScope.apply(() { + _.rootScope.context['minVal'] = "5.5"; + }); + + model.validate(); + expect(model.valid).toEqual(false); + expect(model.invalid).toEqual(true); + expect(model.hasError('min')).toBe(true); + + _.rootScope.apply(() { + _.rootScope.context['val'] = "5.6"; + }); + + model.validate(); + expect(model.valid).toEqual(true); + expect(model.invalid).toEqual(false); + expect(model.hasError('min')).toBe(false); + })); }); describe('pattern', () { From 1cbcef4a74520dad0489ac851da93e1db2768ed1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Wed, 5 Mar 2014 18:54:31 -0500 Subject: [PATCH 2/2] feat(NgModelValidator): perform number validations on range input elements --- lib/directive/ng_model_validators.dart | 9 +++ test/directive/ng_model_validators_spec.dart | 58 +++++++++++++------- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index 303dd5869..9e4947263 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -79,6 +79,7 @@ class NgModelEmailValidator implements NgValidatable { * Validates the model to see if its contents match a valid number. */ @NgDirective(selector: 'input[type=number][ng-model]') +@NgDirective(selector: 'input[type=range][ng-model]') class NgModelNumberValidator implements NgValidatable { String get name => 'number'; @@ -105,9 +106,13 @@ class NgModelNumberValidator implements NgValidatable { * Validates the model to see if the numeric value than or equal to the max value. */ @NgDirective(selector: 'input[type=number][ng-model][max]') +@NgDirective(selector: 'input[type=range][ng-model][max]') @NgDirective( selector: 'input[type=number][ng-model][ng-max]', map: const {'ng-max': '=>max'}) +@NgDirective( + selector: 'input[type=range][ng-model][ng-max]', + map: const {'ng-max': '=>max'}) class NgModelMaxNumberValidator implements NgValidatable { double _max; @@ -147,9 +152,13 @@ class NgModelMaxNumberValidator implements NgValidatable { * Validates the model to see if the numeric value is greater than or equal to the min value. */ @NgDirective(selector: 'input[type=number][ng-model][min]') +@NgDirective(selector: 'input[type=range][ng-model][min]') @NgDirective( selector: 'input[type=number][ng-model][ng-min]', map: const {'ng-min': '=>min'}) +@NgDirective( + selector: 'input[type=range][ng-model][ng-min]', + map: const {'ng-min': '=>min'}) class NgModelMinNumberValidator implements NgValidatable { double _min; diff --git a/test/directive/ng_model_validators_spec.dart b/test/directive/ng_model_validators_spec.dart index 6b5e1eb9c..c745b0f33 100644 --- a/test/directive/ng_model_validators_spec.dart +++ b/test/directive/ng_model_validators_spec.dart @@ -3,6 +3,17 @@ library ng_model_validators; import '../_specs.dart'; void main() { + they(should, tokens, callback, [exclusive=false]) { + tokens.forEach((token) { + describe(token, () { + (exclusive ? iit : it)(should, () => callback(token)); + }); + }); + } + + tthey(should, tokens, callback) => + they(should, tokens, callback, true); + describe('ngModel validators', () { TestBed _; @@ -128,9 +139,12 @@ void main() { })); }); - describe('[type="number"]', () { - it('should validate the input field given a valid or invalid number', inject((RootScope scope) { - _.compile(''); + describe('[type="number|range"]', () { + they('should validate the input field given a valid or invalid number', + ['range', 'number'], + (type) { + + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -162,12 +176,13 @@ void main() { model.validate(); expect(model.valid).toEqual(false); expect(model.invalid).toEqual(true); - })); + }); - it('should perform a max number validation if a max attribute value is present', - inject((RootScope scope) { + they('should perform a max number validation if a max attribute value is present', + ['range', 'number'], + (type) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -198,12 +213,13 @@ void main() { expect(model.invalid).toEqual(true); expect(model.hasError('max')).toBe(false); expect(model.hasError('number')).toBe(true); - })); + }); - it('should perform a max number validation if a ng-max attribute value is present and/or changed', - inject((RootScope scope) { + they('should perform a max number validation if a ng-max attribute value is present and/or changed', + ['range', 'number'], + (type) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -239,12 +255,13 @@ void main() { expect(model.valid).toEqual(true); expect(model.invalid).toEqual(false); expect(model.hasError('max')).toBe(false); - })); + }); - it('should perform a min number validation if a min attribute value is present', - inject((RootScope scope) { + they('should perform a min number validation if a min attribute value is present', + ['range', 'number'], + (type) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -275,12 +292,13 @@ void main() { expect(model.invalid).toEqual(true); expect(model.hasError('min')).toBe(false); expect(model.hasError('number')).toBe(true); - })); + }); - it('should perform a min number validation if a ng-min attribute value is present and/or changed', - inject((RootScope scope) { + they('should perform a min number validation if a ng-min attribute value is present and/or changed', + ['range', 'number'], + (type) { - _.compile(''); + _.compile(''); Probe probe = _.rootScope.context['i']; var model = probe.directive(NgModel); @@ -316,7 +334,7 @@ void main() { expect(model.valid).toEqual(true); expect(model.invalid).toEqual(false); expect(model.hasError('min')).toBe(false); - })); + }); }); describe('pattern', () {