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

Commit

Permalink
fix(forms): ensure models are validated when validator attributes change
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko authored and mhevery committed Mar 19, 2014
1 parent 1954e9e commit 0622f3a
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 28 deletions.
18 changes: 15 additions & 3 deletions lib/directive/ng_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class NgModel extends NgControl implements NgAttachAware {
String _exp;
final _validators = <NgValidator>[];
bool _alwaysProcessViewValue;
bool _toBeValidated = false;

NgModelConverter _converter;
Watch _removeWatch;
Expand Down Expand Up @@ -89,6 +90,16 @@ class NgModel extends NgControl implements NgAttachAware {
addInfoState(this, NgControl.NG_DIRTY);
}

void validateLater() {
if (_toBeValidated) return;
_toBeValidated = true;
_scope.rootScope.runAsync(() {
if (_toBeValidated) {
validate();
}
});
}

get converter => _converter;
set converter(NgModelConverter c) {
_converter = c;
Expand Down Expand Up @@ -136,7 +147,6 @@ class NgModel extends NgControl implements NgAttachAware {
_scope.rootScope.runAsync(() {
_modelValue = boundExpression();
_originalValue = modelValue;
validate();
processViewValue(_modelValue);
});
}
Expand Down Expand Up @@ -184,13 +194,15 @@ class NgModel extends NgControl implements NgAttachAware {
* Executes a validation on the form against each of the validation present on the model.
*/
void validate() {
_toBeValidated = false;
if (validators.isNotEmpty) {
validators.forEach((validator) {
validator.isValid(modelValue) == false
? this.addError(validator.name)
: this.removeError(validator.name);
});
}

invalid
? addInfo(NgControl.NG_INVALID)
: removeInfo(NgControl.NG_INVALID);
Expand All @@ -201,15 +213,15 @@ class NgModel extends NgControl implements NgAttachAware {
*/
void addValidator(NgValidator v) {
validators.add(v);
validate();
validateLater();
}

/**
* De-registers a validator from the model.
*/
void removeValidator(NgValidator v) {
validators.remove(v);
validate();
validateLater();
}
}

Expand Down
64 changes: 40 additions & 24 deletions lib/directive/ng_model_validators.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ abstract class NgValidator {
selector: '[ng-model][ng-required]',
map: const {'ng-required': '=>required'})
class NgModelRequiredValidator implements NgValidator {
bool _required = true;

final String name = 'ng-required';
bool _required = true;
final NgModel _ngModel;

NgModelRequiredValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelRequiredValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

bool isValid(modelValue) {
Expand All @@ -35,6 +36,7 @@ class NgModelRequiredValidator implements NgValidator {

set required(value) {
_required = value == null ? false : value;
_ngModel.validateLater();
}
}

Expand Down Expand Up @@ -81,6 +83,7 @@ class NgModelEmailValidator implements NgValidator {
@NgDirective(selector: 'input[type=number][ng-model]')
@NgDirective(selector: 'input[type=range][ng-model]')
class NgModelNumberValidator implements NgValidator {

final String name = 'ng-number';

NgModelNumberValidator(NgModel ngModel) {
Expand Down Expand Up @@ -115,11 +118,12 @@ class NgModelNumberValidator implements NgValidator {
map: const {'ng-max': '=>max'})
class NgModelMaxNumberValidator implements NgValidator {

double _max;
final String name = 'ng-max';
double _max;
final NgModel _ngModel;

NgModelMaxNumberValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelMaxNumberValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

@NgAttr('max')
Expand All @@ -128,6 +132,7 @@ class NgModelMaxNumberValidator implements NgValidator {
try {
num parsedValue = double.parse(value);
_max = parsedValue.isNaN ? _max : parsedValue;
_ngModel.validateLater();
} catch(e) {};
}

Expand Down Expand Up @@ -161,11 +166,12 @@ class NgModelMaxNumberValidator implements NgValidator {
map: const {'ng-min': '=>min'})
class NgModelMinNumberValidator implements NgValidator {

double _min;
final String name = 'ng-min';
double _min;
final NgModel _ngModel;

NgModelMinNumberValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelMinNumberValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

@NgAttr('min')
Expand All @@ -174,6 +180,7 @@ class NgModelMinNumberValidator implements NgValidator {
try {
num parsedValue = double.parse(value);
_min = parsedValue.isNaN ? _min : parsedValue;
_ngModel.validateLater();
} catch(e) {};
}

Expand Down Expand Up @@ -203,12 +210,13 @@ class NgModelMinNumberValidator implements NgValidator {
selector: '[ng-model][ng-pattern]',
map: const {'ng-pattern': '=>pattern'})
class NgModelPatternValidator implements NgValidator {
RegExp _pattern;

final String name = 'ng-pattern';
RegExp _pattern;
final NgModel _ngModel;

NgModelPatternValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelPatternValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

bool isValid(modelValue) {
Expand All @@ -218,8 +226,10 @@ class NgModelPatternValidator implements NgValidator {
}

@NgAttr('pattern')
set pattern(val) =>
_pattern = val != null && val.length > 0 ? new RegExp(val) : null;
void set pattern(val) {
_pattern = val != null && val.length > 0 ? new RegExp(val) : null;
_ngModel.validateLater();
}
}

/**
Expand All @@ -232,12 +242,13 @@ class NgModelPatternValidator implements NgValidator {
selector: '[ng-model][ng-minlength]',
map: const {'ng-minlength': '=>minlength'})
class NgModelMinLengthValidator implements NgValidator {
int _minlength;

final String name = 'ng-minlength';
int _minlength;
final NgModel _ngModel;

NgModelMinLengthValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelMinLengthValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

bool isValid(modelValue) {
Expand All @@ -247,8 +258,10 @@ class NgModelMinLengthValidator implements NgValidator {
}

@NgAttr('minlength')
set minlength(value) =>
_minlength = value == null ? 0 : int.parse(value.toString());
void set minlength(value) {
_minlength = value == null ? 0 : int.parse(value.toString());
_ngModel.validateLater();
}
}

/**
Expand All @@ -261,18 +274,21 @@ class NgModelMinLengthValidator implements NgValidator {
selector: '[ng-model][ng-maxlength]',
map: const {'ng-maxlength': '=>maxlength'})
class NgModelMaxLengthValidator implements NgValidator {
int _maxlength = 0;

final String name = 'ng-maxlength';
int _maxlength = 0;
final NgModel _ngModel;

NgModelMaxLengthValidator(NgModel ngModel) {
ngModel.addValidator(this);
NgModelMaxLengthValidator(NgModel this._ngModel) {
_ngModel.addValidator(this);
}

bool isValid(modelValue) =>
_maxlength == 0 || (modelValue == null ? 0 : modelValue.length) <= _maxlength;

@NgAttr('maxlength')
set maxlength(value) =>
_maxlength = value == null ? 0 : int.parse(value.toString());
void set maxlength(value) {
_maxlength = value == null ? 0 : int.parse(value.toString());
_ngModel.validateLater();
}
}
24 changes: 24 additions & 0 deletions test/directive/ng_form_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ void main() {
_.compile('<form name="superForm">' +
' <input type="text" ng-model="myModel" probe="i" required />' +
'</form>');
scope.apply();

NgForm form = _.rootScope.context['superForm'];
Probe probe = _.rootScope.context['i'];
Expand Down Expand Up @@ -600,6 +601,29 @@ void main() {
expect(form.classes.contains('ng-required-invalid')).toBe(false);
});

it('should re-validate itself when validators are toggled on and off',
(TestBed _, Scope scope) {

scope.context['required'] = true;
_.compile('<form name="myForm">' +
'<input type="text" ng-model="model" ng-required="required" probe="i">' +
'</form>');
scope.apply();

var form = scope.context['myForm'];
var model = scope.context['i'].directive(NgModel);

expect(form.invalid).toBe(true);
expect(model.invalid).toBe(true);

scope.context['required'] = false;
scope.apply();

expect(form.valid).toBe(true);
expect(model.valid).toBe(true);
});


describe('custom validators', () {
beforeEachModule((Module module) {
module.type(MyCustomFormValidator);
Expand Down
51 changes: 50 additions & 1 deletion test/directive/ng_model_spec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ void main() {
beforeEachModule((Module module) {
module
..type(ControllerWithNoLove)
..type(MyCustomInputValidator);
..type(MyCustomInputValidator)
..type(CountingValidator);
});

beforeEach((TestBed tb) => _ = tb);
Expand Down Expand Up @@ -1128,6 +1129,7 @@ void main() {

it('should happen automatically upon user input via the onInput event', () {
_.compile('<input type="text" ng-model="model" probe="i" required>');
_.rootScope.apply();

Probe probe = _.rootScope.context['i'];
var model = probe.directive(NgModel);
Expand Down Expand Up @@ -1348,6 +1350,36 @@ void main() {
expect(input.classes.contains('custom-valid')).toBe(true);
expect(input.classes.contains('custom-invalid')).toBe(false);
});

it('should only validate twice during compilation and once upon scope digest',
(TestBed _, Scope scope) {

scope.context['required'] = true;
_.compile('<input type="text" '
'ng-model="model" '
'ng-required="required" '
'ng-pattern="pattern" '
'counting-validator '
'probe="i">');

scope.context['pattern'] = '^[aeiou]+\$';
scope.context['required'] = true;

scope.apply();

var model = scope.context['i'].directive(NgModel);
var counter = model.validators.firstWhere((validator) => validator.name == 'counting');

expect(counter.count).toBe(2); //one for ngModel and one for all the other ones
expect(model.invalid).toBe(true);

counter.count = 0;
scope.context['pattern'] = '';
scope.context['required'] = false;
scope.apply();

expect(counter.count).toBe(1);
});
});

describe('converters', () {
Expand Down Expand Up @@ -1496,3 +1528,20 @@ class MyCustomInputValidator extends NgValidator {
return name != null && name == 'yes';
}
}

@NgDirective(
selector: '[counting-validator]')
class CountingValidator extends NgValidator {

final String name = 'counting';
int count = 0;

CountingValidator(NgModel ngModel) {
ngModel.addValidator(this);
}

bool isValid(String modelValue) {
count++;
return true;
}
}
Loading

0 comments on commit 0622f3a

Please sign in to comment.