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..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';
@@ -101,6 +102,98 @@ 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;
+ 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=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;
+ 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..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,7 +176,165 @@ void main() {
model.validate();
expect(model.valid).toEqual(false);
expect(model.invalid).toEqual(true);
- }));
+ });
+
+ they('should perform a max number validation if a max attribute value is present',
+ ['range', 'number'],
+ (type) {
+
+ _.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);
+ });
+
+ they('should perform a max number validation if a ng-max attribute value is present and/or changed',
+ ['range', 'number'],
+ (type) {
+
+ _.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);
+ });
+
+ they('should perform a min number validation if a min attribute value is present',
+ ['range', 'number'],
+ (type) {
+
+ _.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);
+ });
+
+ they('should perform a min number validation if a ng-min attribute value is present and/or changed',
+ ['range', 'number'],
+ (type) {
+
+ _.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', () {