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

Commit

Permalink
feat(forms): add support for validation handling for multiple error t…
Browse files Browse the repository at this point in the history
…ypes
  • Loading branch information
matsko authored and mhevery committed Jan 24, 2014
1 parent d1d8638 commit d3ed15c
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 85 deletions.
1 change: 1 addition & 0 deletions lib/directive/module.dart
Expand Up @@ -18,6 +18,7 @@ part 'ng_events.dart';
part 'ng_cloak.dart';
part 'ng_if.dart';
part 'ng_include.dart';
part 'ng_control.dart';
part 'ng_model.dart';
part 'ng_pluralize.dart';
part 'ng_repeat.dart';
Expand Down
56 changes: 56 additions & 0 deletions lib/directive/ng_control.dart
@@ -0,0 +1,56 @@
part of angular.directive;

abstract class NgControl {
static const NG_VALID_CLASS = "ng-valid";
static const NG_INVALID_CLASS = "ng-invalid";
static const NG_PRISTINE_CLASS = "ng-pristine";
static const NG_DIRTY_CLASS = "ng-dirty";

String _name;
bool _dirty;
bool _pristine;
bool _valid;
bool _invalid;

get element => null;

get name => _name;
set name(name) => _name = name;

get pristine => _pristine;
set pristine(value) {
_pristine = true;
_dirty = false;

element.classes.remove(NG_DIRTY_CLASS);
element.classes.add(NG_PRISTINE_CLASS);
}

get dirty => _dirty;
set dirty(value) {
_dirty = true;
_pristine = false;

element.classes.remove(NG_PRISTINE_CLASS);
element.classes.add(NG_DIRTY_CLASS);
}

get valid => _valid;
set valid(value) {
_invalid = false;
_valid = true;

element.classes.remove(NG_INVALID_CLASS);
element.classes.add(NG_VALID_CLASS);
}

get invalid => _invalid;
set invalid(value) {
_valid = false;
_invalid = true;

element.classes.remove(NG_VALID_CLASS);
element.classes.add(NG_INVALID_CLASS);
}

}
82 changes: 32 additions & 50 deletions lib/directive/ng_form.dart
Expand Up @@ -14,24 +14,14 @@ part of angular.directive;
@NgDirective(
selector: '[ng-form]',
visibility: NgDirective.CHILDREN_VISIBILITY)
class NgForm extends NgDetachAware {
static const String NG_VALID_CLASS = "ng-valid";
static const String NG_INVALID_CLASS = "ng-invalid";
static const String NG_PRISTINE_CLASS = "ng-pristine";
static const String NG_DIRTY_CLASS = "ng-dirty";

class NgForm extends NgControl implements NgDetachAware {
final dom.Element _element;
final Scope _scope;

String _name;

bool _dirty;
bool _pristine;
bool _valid;
bool _invalid;
final Map<String, List<NgControl>> currentErrors = new Map<String, List<NgControl>>();

final List<NgModel> _controls = new List<NgModel>();
final Map<String, NgModel> _controlByName = new Map<String, NgModel>();
final List<NgControl> _controls = new List<NgControl>();
final Map<String, NgControl> _controlByName = new Map<String, NgControl>();

NgForm(this._scope, this._element) {
if(!this._element.attributes.containsKey('action')) {
Expand All @@ -49,61 +39,53 @@ class NgForm extends NgDetachAware {
}
}

get element => _element;

@NgAttr('name')
get name => _name;
set name(name) {
_name = name;
_scope[name] = this;
}

get pristine => _pristine;
set pristine(value) {
_pristine = true;
_dirty = false;

_element.classes.remove(NG_DIRTY_CLASS);
_element.classes.add(NG_PRISTINE_CLASS);
}

get dirty => _dirty;
set dirty(value) {
_dirty = true;
_pristine = false;

_element.classes.remove(NG_PRISTINE_CLASS);
_element.classes.add(NG_DIRTY_CLASS);
}

get valid => _valid;
set valid(value) {
_invalid = false;
_valid = true;

_element.classes.remove(NG_INVALID_CLASS);
_element.classes.add(NG_VALID_CLASS);
}

get invalid => _invalid;
set invalid(value) {
_valid = false;
_invalid = true;

_element.classes.remove(NG_VALID_CLASS);
_element.classes.add(NG_INVALID_CLASS);
setValidity(NgControl control, String errorType, bool isValid) {
List queue = currentErrors[errorType];

if(isValid) {
if(queue != null) {
queue.remove(control);
if(queue.isEmpty) {
currentErrors.remove(errorType);
if(currentErrors.isEmpty) {
valid = true;
}
}
}
} else {
if(queue == null) {
queue = new List<NgControl>();
currentErrors[errorType] = queue;
} else if(queue.contains(control)) {
return;
}

queue.add(control);
invalid = true;
}
}

operator[](name) {
return _controlByName[name];
}

addControl(NgModel control) {
addControl(NgControl control) {
_controls.add(control);
if(control.name != null) {
_controlByName[control.name] = control;
}
}

removeControl(NgModel control) {
removeControl(NgControl control) {
_controls.remove(control);
if(control.name != null) {
_controlByName.remove(control.name);
Expand Down
54 changes: 19 additions & 35 deletions lib/directive/ng_model.dart
Expand Up @@ -12,7 +12,7 @@ part of angular.directive;
*/
@NgDirective(
selector: '[ng-model]')
class NgModel {
class NgModel extends NgControl {
final NgForm _form;
final dom.Element _element;
final Scope _scope;
Expand All @@ -23,10 +23,7 @@ class NgModel {
String _exp;
String _name;

bool _dirty;
bool _pristine;
bool _valid;
bool _invalid;
final Map<String, bool> currentErrors = new Map<String, bool>();

Function _removeWatch = () => null;
bool _watchCollection;
Expand All @@ -41,6 +38,8 @@ class NgModel {
pristine = true;
}

get element => _element;

@NgAttr('name')
get name => _name;
set name(value) {
Expand Down Expand Up @@ -74,39 +73,24 @@ class NgModel {
get modelValue => getter();
set modelValue(value) => setter(value);

get pristine => _pristine;
set pristine(value) {
_pristine = true;
_dirty = false;
_element.classes.remove(NgForm.NG_DIRTY_CLASS);
_element.classes.add(NgForm.NG_PRISTINE_CLASS);
}

get dirty => _dirty;
set dirty(value) {
_dirty = true;
_pristine = false;
_element.classes.remove(NgForm.NG_PRISTINE_CLASS);
_element.classes.add(NgForm.NG_DIRTY_CLASS);
}

get valid => _valid;
set valid(value) {
_invalid = false;
_valid = true;
_element.classes.remove(NgForm.NG_INVALID_CLASS);
_element.classes.add(NgForm.NG_VALID_CLASS);
}
setValidity(String errorType, bool isValid) {
if(isValid) {
if(currentErrors.containsKey(errorType)) {
currentErrors.remove(errorType);
if(currentErrors.isEmpty) {
valid = true;
}
}
} else if(!currentErrors.containsKey(errorType)) {
currentErrors[errorType] = true;
invalid = true;
}

get invalid => _invalid;
set invalid(value) {
_valid = false;
_invalid = true;
_element.classes.remove(NgForm.NG_VALID_CLASS);
_element.classes.add(NgForm.NG_INVALID_CLASS);
if(_form != null) {
_form.setValidity(this, errorType, isValid);
}
}


destroy() {
_form.removeControl(this);
}
Expand Down
86 changes: 86 additions & 0 deletions test/directive/ng_form_spec.dart
Expand Up @@ -77,6 +77,92 @@ describe('form', () {
expect(element.hasClass('ng-invalid')).toBe(false);
expect(element.hasClass('ng-valid')).toBe(true);
}));

it('should set the validity with respect to all existing validations when setValidity() is used', inject((Scope scope) {
var element = $('<form name="myForm">' +
' <input type="text" ng-model="one" name="one" />' +
' <input type="text" ng-model="two" name="two" />' +
' <input type="text" ng-model="three" name="three" />' +
'</form>');

_.compile(element);
scope.$apply();

var form = scope['myForm'];
NgModel one = form['one'];
NgModel two = form['two'];
NgModel three = form['three'];

form.setValidity(one, "some error", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

form.setValidity(two, "some error", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

form.setValidity(one, "some error", true);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

form.setValidity(two, "some error", true);
expect(form.valid).toBe(true);
expect(form.invalid).toBe(false);
}));

it('should not handle the control + errorType pair more than once', inject((Scope scope) {
var element = $('<form name="myForm">' +
' <input type="text" ng-model="one" name="one" />' +
'</form>');

_.compile(element);
scope.$apply();

var form = scope['myForm'];
NgModel one = form['one'];

form.setValidity(one, "validation error", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

form.setValidity(one, "validation error", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

form.setValidity(one, "validation error", true);
expect(form.valid).toBe(true);
expect(form.invalid).toBe(false);
}));

it('should update the validity of the parent form when the inner model changes', inject((Scope scope) {
var element = $('<form name="myForm">' +
' <input type="text" ng-model="one" name="one" />' +
' <input type="text" ng-model="two" name="two" />' +
'</form>');

_.compile(element);
scope.$apply();

var form = scope['myForm'];
NgModel one = form['one'];
NgModel two = form['two'];

one.setValidity("required", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

two.setValidity("required", false);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

one.setValidity("required", true);
expect(form.valid).toBe(false);
expect(form.invalid).toBe(true);

two.setValidity("required", true);
expect(form.valid).toBe(true);
expect(form.invalid).toBe(false);
}));
});

describe('controls', () {
Expand Down

0 comments on commit d3ed15c

Please sign in to comment.