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

Form Validation Toggling #761

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions lib/directive/ng_control.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@ abstract class NgControl implements NgAttachAware, NgDetachAware {
static const NG_SUBMIT_INVALID = "ng-submit-invalid";

String _name;
bool _dirty;
bool _touched;
bool _valid;
bool _submit_valid;

final NgControl _parentControl;
Expand Down
9 changes: 0 additions & 9 deletions lib/directive/ng_form.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,6 @@ class NgForm extends NgControl {
}
}

//FIXME: fix this reflection bug that shows up when Map is implemented
operator []=(String key, value) {
if (key == 'name') {
name = value;
} else {
_controlByName[key] = value;
}
}

get controls => _controlByName;

NgControl operator[](name) =>
Expand Down
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
74 changes: 48 additions & 26 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,7 +132,11 @@ class NgModelMaxNumberValidator implements NgValidator {
try {
num parsedValue = double.parse(value);
_max = parsedValue.isNaN ? _max : parsedValue;
} catch(e) {};
} catch(e) {
_max = null;
} finally {
_ngModel.validateLater();
}
}

bool isValid(modelValue) {
Expand Down Expand Up @@ -161,11 +169,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,7 +183,11 @@ class NgModelMinNumberValidator implements NgValidator {
try {
num parsedValue = double.parse(value);
_min = parsedValue.isNaN ? _min : parsedValue;
} catch(e) {};
} catch(e) {
_min = null;
} finally {
_ngModel.validateLater();
}
}

bool isValid(modelValue) {
Expand Down Expand Up @@ -203,12 +216,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 +232,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 +248,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 +264,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 +280,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