Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

Commit 7dc55fb

Browse files
matskomhevery
authored andcommitted
feat(NgModelValidator): provide support for min and max validations on number input fields
1 parent 8b989b6 commit 7dc55fb

File tree

3 files changed

+240
-0
lines changed

3 files changed

+240
-0
lines changed

lib/directive/module.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ class NgDirectiveModule extends Module {
8181
value(NgModelUrlValidator, null);
8282
value(NgModelEmailValidator, null);
8383
value(NgModelNumberValidator, null);
84+
value(NgModelMaxNumberValidator, null);
85+
value(NgModelMinNumberValidator, null);
8486
value(NgModelPatternValidator, null);
8587
value(NgModelMinLengthValidator, null);
8688
value(NgModelMaxLengthValidator, null);

lib/directive/ng_model_validators.dart

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,90 @@ class NgModelNumberValidator implements NgValidatable {
101101
}
102102
}
103103

104+
/**
105+
* Validates the model to see if the numeric value than or equal to the max value.
106+
*/
107+
@NgDirective(selector: 'input[type=number][ng-model][max]')
108+
@NgDirective(
109+
selector: 'input[type=number][ng-model][ng-max]',
110+
map: const {'ng-max': '=>max'})
111+
class NgModelMaxNumberValidator implements NgValidatable {
112+
113+
double _max;
114+
String get name => 'max';
115+
116+
NgModelMaxNumberValidator(NgModel ngModel) {
117+
ngModel.addValidator(this);
118+
}
119+
120+
@NgAttr('max')
121+
get max => _max;
122+
set max(value) {
123+
try {
124+
num parsedValue = double.parse(value);
125+
_max = parsedValue.isNaN ? _max : parsedValue;
126+
} catch(e) {};
127+
}
128+
129+
bool isValid(value) {
130+
if (value == null || max == null) return true;
131+
132+
try {
133+
num parsedValue = double.parse(value.toString());
134+
if (!parsedValue.isNaN) {
135+
return parsedValue <= max;
136+
}
137+
} catch(exception, stackTrace) {}
138+
139+
//this validator doesn't care if the type conversation fails or the value
140+
//is not a number (NaN) because NgModelNumberValidator will handle the
141+
//number-based validation either way.
142+
return true;
143+
}
144+
}
145+
146+
/**
147+
* Validates the model to see if the numeric value is greater than or equal to the min value.
148+
*/
149+
@NgDirective(selector: 'input[type=number][ng-model][min]')
150+
@NgDirective(
151+
selector: 'input[type=number][ng-model][ng-min]',
152+
map: const {'ng-min': '=>min'})
153+
class NgModelMinNumberValidator implements NgValidatable {
154+
155+
double _min;
156+
String get name => 'min';
157+
158+
NgModelMinNumberValidator(NgModel ngModel) {
159+
ngModel.addValidator(this);
160+
}
161+
162+
@NgAttr('min')
163+
get min => _min;
164+
set min(value) {
165+
try {
166+
num parsedValue = double.parse(value);
167+
_min = parsedValue.isNaN ? _min : parsedValue;
168+
} catch(e) {};
169+
}
170+
171+
bool isValid(value) {
172+
if (value == null || min == null) return true;
173+
174+
try {
175+
num parsedValue = double.parse(value.toString());
176+
if (!parsedValue.isNaN) {
177+
return parsedValue >= min;
178+
}
179+
} catch(exception, stackTrace) {}
180+
181+
//this validator doesn't care if the type conversation fails or the value
182+
//is not a number (NaN) because NgModelNumberValidator will handle the
183+
//number-based validation either way.
184+
return true;
185+
}
186+
}
187+
104188
/**
105189
* Validates the model to see if its contents match the given pattern present on either the
106190
* HTML pattern or ng-pattern attributes present on the input element.

test/directive/ng_model_validators_spec.dart

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,160 @@ void main() {
163163
expect(model.valid).toEqual(false);
164164
expect(model.invalid).toEqual(true);
165165
}));
166+
167+
it('should perform a max number validation if a max attribute value is present',
168+
inject((RootScope scope) {
169+
170+
_.compile('<input type="number" ng-model="val" max="10" probe="i" />');
171+
Probe probe = _.rootScope.context['i'];
172+
var model = probe.directive(NgModel);
173+
174+
_.rootScope.apply(() {
175+
_.rootScope.context['val'] = "8";
176+
});
177+
178+
model.validate();
179+
expect(model.valid).toEqual(true);
180+
expect(model.invalid).toEqual(false);
181+
expect(model.hasError('max')).toBe(false);
182+
183+
_.rootScope.apply(() {
184+
_.rootScope.context['val'] = "99";
185+
});
186+
187+
model.validate();
188+
expect(model.valid).toEqual(false);
189+
expect(model.invalid).toEqual(true);
190+
expect(model.hasError('max')).toBe(true);
191+
192+
_.rootScope.apply(() {
193+
_.rootScope.context['val'] = "a";
194+
});
195+
196+
model.validate();
197+
expect(model.valid).toEqual(false);
198+
expect(model.invalid).toEqual(true);
199+
expect(model.hasError('max')).toBe(false);
200+
expect(model.hasError('number')).toBe(true);
201+
}));
202+
203+
it('should perform a max number validation if a ng-max attribute value is present and/or changed',
204+
inject((RootScope scope) {
205+
206+
_.compile('<input type="number" ng-model="val" ng-max="maxVal" probe="i" />');
207+
Probe probe = _.rootScope.context['i'];
208+
var model = probe.directive(NgModel);
209+
210+
//should be valid even when no number is present
211+
model.validate();
212+
expect(model.valid).toEqual(true);
213+
expect(model.invalid).toEqual(false);
214+
expect(model.hasError('max')).toBe(false);
215+
216+
_.rootScope.apply(() {
217+
_.rootScope.context['val'] = "20";
218+
});
219+
220+
model.validate();
221+
expect(model.valid).toEqual(true);
222+
expect(model.invalid).toEqual(false);
223+
expect(model.hasError('max')).toBe(false);
224+
225+
_.rootScope.apply(() {
226+
_.rootScope.context['maxVal'] = "19";
227+
});
228+
229+
model.validate();
230+
expect(model.valid).toEqual(false);
231+
expect(model.invalid).toEqual(true);
232+
expect(model.hasError('max')).toBe(true);
233+
234+
_.rootScope.apply(() {
235+
_.rootScope.context['maxVal'] = "22";
236+
});
237+
238+
model.validate();
239+
expect(model.valid).toEqual(true);
240+
expect(model.invalid).toEqual(false);
241+
expect(model.hasError('max')).toBe(false);
242+
}));
243+
244+
it('should perform a min number validation if a min attribute value is present',
245+
inject((RootScope scope) {
246+
247+
_.compile('<input type="number" ng-model="val" min="-10" probe="i" />');
248+
Probe probe = _.rootScope.context['i'];
249+
var model = probe.directive(NgModel);
250+
251+
_.rootScope.apply(() {
252+
_.rootScope.context['val'] = "8";
253+
});
254+
255+
model.validate();
256+
expect(model.valid).toEqual(true);
257+
expect(model.invalid).toEqual(false);
258+
expect(model.hasError('min')).toBe(false);
259+
260+
_.rootScope.apply(() {
261+
_.rootScope.context['val'] = "-20";
262+
});
263+
264+
model.validate();
265+
expect(model.valid).toEqual(false);
266+
expect(model.invalid).toEqual(true);
267+
expect(model.hasError('min')).toBe(true);
268+
269+
_.rootScope.apply(() {
270+
_.rootScope.context['val'] = "x";
271+
});
272+
273+
model.validate();
274+
expect(model.valid).toEqual(false);
275+
expect(model.invalid).toEqual(true);
276+
expect(model.hasError('min')).toBe(false);
277+
expect(model.hasError('number')).toBe(true);
278+
}));
279+
280+
it('should perform a min number validation if a ng-min attribute value is present and/or changed',
281+
inject((RootScope scope) {
282+
283+
_.compile('<input type="number" ng-model="val" ng-min="minVal" probe="i" />');
284+
Probe probe = _.rootScope.context['i'];
285+
var model = probe.directive(NgModel);
286+
287+
//should be valid even when no number is present
288+
model.validate();
289+
expect(model.valid).toEqual(true);
290+
expect(model.invalid).toEqual(false);
291+
expect(model.hasError('min')).toBe(false);
292+
293+
_.rootScope.apply(() {
294+
_.rootScope.context['val'] = "5";
295+
});
296+
297+
model.validate();
298+
expect(model.valid).toEqual(true);
299+
expect(model.invalid).toEqual(false);
300+
expect(model.hasError('min')).toBe(false);
301+
302+
_.rootScope.apply(() {
303+
_.rootScope.context['minVal'] = "5.5";
304+
});
305+
306+
model.validate();
307+
expect(model.valid).toEqual(false);
308+
expect(model.invalid).toEqual(true);
309+
expect(model.hasError('min')).toBe(true);
310+
311+
_.rootScope.apply(() {
312+
_.rootScope.context['val'] = "5.6";
313+
});
314+
315+
model.validate();
316+
expect(model.valid).toEqual(true);
317+
expect(model.invalid).toEqual(false);
318+
expect(model.hasError('min')).toBe(false);
319+
}));
166320
});
167321

168322
describe('pattern', () {

0 commit comments

Comments
 (0)