diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index 51628aaa7..8852c603c 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -5,6 +5,8 @@ abstract class NgControl implements NgDetachAware { static const NG_INVALID_CLASS = "ng-invalid"; static const NG_PRISTINE_CLASS = "ng-pristine"; static const NG_DIRTY_CLASS = "ng-dirty"; + static const NG_TOUCHED_CLASS = "ng-touched"; + static const NG_UNTOUCHED_CLASS = "ng-untouched"; static const NG_SUBMIT_VALID_CLASS = "ng-submit-valid"; static const NG_SUBMIT_INVALID_CLASS = "ng-submit-invalid"; @@ -13,6 +15,8 @@ abstract class NgControl implements NgDetachAware { bool _pristine; bool _valid; bool _invalid; + bool _touched; + bool _untouched; final Scope _scope; final NgControl _parentControl; @@ -26,6 +30,8 @@ abstract class NgControl implements NgDetachAware { : _parentControl = injector.parent.get(NgControl) { pristine = true; + untouched = true; + _scope.on('submitNgControl').listen((e) => _onSubmit(e.data)); } @@ -37,6 +43,7 @@ abstract class NgControl implements NgDetachAware { reset() { _scope.broadcast('resetNgModel'); + untouched = true; } _onSubmit(bool valid) { @@ -91,6 +98,25 @@ abstract class NgControl implements NgDetachAware { element.classes..remove(NG_VALID_CLASS)..add(NG_INVALID_CLASS); } + get touched => _touched; + set touched(value) { + _touched = true; + _untouched = false; + + element.classes..remove(NG_UNTOUCHED_CLASS)..add(NG_TOUCHED_CLASS); + + //as soon as one of the controls/models is touched + //then all of the parent controls are touched as well + _parentControl.touched = true; + } + + get untouched => _untouched; + set untouched(value) { + _touched = false; + _untouched = true; + element.classes..remove(NG_TOUCHED_CLASS)..add(NG_UNTOUCHED_CLASS); + } + /** * Registers a form control into the form for validation. * @@ -158,7 +184,7 @@ abstract class NgControl implements NgDetachAware { } class NgNullControl implements NgControl { - var _name, _dirty, _valid, _invalid, _pristine, _element; + var _name, _dirty, _valid, _invalid, _pristine, _element, _touched, _untouched; var _controls, _scope, _parentControl, _controlName; var errors, _controlByName; dom.Element element; @@ -185,6 +211,12 @@ class NgNullControl implements NgControl { get invalid => null; set invalid(value) {} + get touched => null; + set touched(value) {} + + get untouched => null; + set untouched(value) {} + reset() => null; detach() => null; diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart index 2712be93f..c2c4427f7 100644 --- a/lib/directive/ng_form.dart +++ b/lib/directive/ng_form.dart @@ -39,6 +39,7 @@ class NgForm extends NgControl implements Map { element.onSubmit.listen((event) { event.preventDefault(); _scope.broadcast('submitNgControl', valid == null ? false : valid); + reset(); }); } } diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index b4dc3140b..42388645d 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -45,6 +45,7 @@ class NgModel extends NgControl implements NgAttachAware { } reset() { + untouched = true; modelValue = _lastValue; } @@ -203,7 +204,12 @@ class InputTextLikeDirective { }; inputElement ..onChange.listen(processValue) - ..onInput.listen(processValue); + ..onInput.listen(processValue) + ..onBlur.listen((e) { + if(ngModel.touched == null || ngModel.touched == false) { + ngModel.touched = true; + } + }); } processValue([_]) { diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index 3e16d962f..230a6758c 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -362,6 +362,46 @@ describe('form', () { expect(model.modelValue).toEqual('animal'); expect(model.viewValue).toEqual('animal'); })); + + it('should set the form control to be untouched when the model is reset or submitted', inject((TestBed _) { + var form = _.compile('
' + + ' ' + + '
'); + var model = _.rootScope.context['i'].directive(NgModel); + var input = model.element; + + NgForm formModel = _.rootScope.context['duperForm']; + + expect(formModel.touched).toBe(false); + expect(formModel.untouched).toBe(true); + expect(form.classes.contains('ng-touched')).toBe(false); + expect(form.classes.contains('ng-untouched')).toBe(true); + + _.triggerEvent(input, 'blur'); + + expect(formModel.touched).toBe(true); + expect(formModel.untouched).toBe(false); + expect(form.classes.contains('ng-touched')).toBe(true); + expect(form.classes.contains('ng-untouched')).toBe(false); + + formModel.reset(); + + expect(formModel.touched).toBe(false); + expect(formModel.untouched).toBe(true); + expect(form.classes.contains('ng-touched')).toBe(false); + expect(form.classes.contains('ng-untouched')).toBe(true); + + _.triggerEvent(input, 'blur'); + + expect(formModel.touched).toBe(true); + + _.triggerEvent(form, 'submit'); + + expect(formModel.touched).toBe(false); + expect(formModel.untouched).toBe(true); + expect(form.classes.contains('ng-touched')).toBe(false); + expect(form.classes.contains('ng-untouched')).toBe(true); + })); }); describe('regression tests: form', () { diff --git a/test/directive/ng_model_spec.dart b/test/directive/ng_model_spec.dart index f3634151e..80c8179ec 100644 --- a/test/directive/ng_model_spec.dart +++ b/test/directive/ng_model_spec.dart @@ -707,8 +707,8 @@ describe('ng-model', () { var model = probe.directive(NgModel); - var input1 = element.query("#on"); - var input2 = element.query("#off"); + var input1 = element.querySelector("#on"); + var input2 = element.querySelector("#off"); expect(model.pristine).toEqual(true); expect(model.dirty).toEqual(false); @@ -1021,6 +1021,24 @@ describe('ng-model', () { expect(model.modelValue).toEqual('animal'); expect(model.viewValue).toEqual('animal'); }); + + it('should set the model to be untouched when the model is reset', () { + var input = _.compile(''); + var model = _.rootScope.context['i'].directive(NgModel); + + expect(model.touched).toBe(false); + expect(model.untouched).toBe(true); + + _.triggerEvent(input, 'blur'); + + expect(model.touched).toBe(true); + expect(model.untouched).toBe(false); + + model.reset(); + + expect(model.touched).toBe(false); + expect(model.untouched).toBe(true); + }); }); });