From 1be0eff5425c7f60c7a18db0c3aaaf1b4acda457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 24 Mar 2014 13:53:17 -0400 Subject: [PATCH 1/2] docs(forms): provide documentation for newly refactored code --- lib/directive/ng_control.dart | 201 +++++++++++++++++-------- lib/directive/ng_form.dart | 15 +- lib/directive/ng_model.dart | 54 +++++-- lib/directive/ng_model_validators.dart | 9 ++ 4 files changed, 207 insertions(+), 72 deletions(-) diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index 29336dac0..669df5351 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -1,5 +1,12 @@ part of angular.directive; +/** + * The NgControl class is a super-class for handling info and error states between + * inner controls and models. NgControl will automatically apply the associated CSS + * classes for the error and info states that are applied as well as status flags. + * NgControl is used with the form and fieldset as well as all other directives that + * are used for user input with NgModel. + */ abstract class NgControl implements NgAttachAware, NgDetachAware { static const NG_VALID = "ng-valid"; static const NG_INVALID = "ng-invalid"; @@ -15,12 +22,21 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { final NgControl _parentControl; final NgAnimate _animate; - NgElement _element; + final NgElement _element; final _controls = new List(); final _controlByName = new Map>(); + /** + * The list of errors present on the control represented by an error name and + * an inner control instance. + */ final errorStates = new Map>(); + + /** + * The list of info messages present on the control represented by an state name and + * an inner control instance. + */ final infoStates = new Map>(); NgControl(NgElement this._element, Injector injector, @@ -36,13 +52,16 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { _parentControl.removeControl(this); } - reset() { + /** + * Resets the form and inner models to their pristine state. + */ + void reset() { _controls.forEach((control) { control.reset(); }); } - onSubmit(bool valid) { + void onSubmit(bool valid) { if (valid) { _submit_valid = true; element..addClass(NG_SUBMIT_VALID)..removeClass(NG_SUBMIT_INVALID); @@ -55,34 +74,69 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { }); } - get parentControl => _parentControl; + NgControl get parentControl => _parentControl; - get submitted => _submit_valid != null; - get valid_submit => _submit_valid == true; - get invalid_submit => _submit_valid == false; + /** + * Whether or not the form has been submitted yet. + */ + bool get submitted => _submit_valid != null; + + /** + * Whether or not the form was valid when last submitted. + */ + bool get valid_submit => _submit_valid == true; + + /** + * Whether or not the form was invalid when last submitted. + */ + bool get invalid_submit => _submit_valid == false; - get name => _name; + String get name => _name; set name(value) { _name = value; } - get element => _element; + /** + * Whether or not the form was invalid when last submitted. + */ + NgElement get element => _element; - get valid => !invalid; - get invalid => errorStates.isNotEmpty; + /** + * A control is considered valid if all inner models are valid. + */ + bool get valid => !invalid; - get pristine => !dirty; - get dirty => infoStates.containsKey(NG_DIRTY); + /** + * A control is considered invalid if any inner models are invalid. + */ + bool get invalid => errorStates.isNotEmpty; - get untouched => !touched; - get touched => infoStates.containsKey(NG_TOUCHED); + /** + * Whether or not the control's or model's data has not been changed. + */ + bool get pristine => !dirty; + + /** + * Whether or not the control's or model's data has been changed. + */ + bool get dirty => infoStates.containsKey(NG_DIRTY); + + /** + * Whether or not the control/model has not been interacted with by the user. + */ + bool get untouched => !touched; + + /** + * Whether or not the control/model has been interacted with by the user. + */ + bool get touched => infoStates.containsKey(NG_TOUCHED); /** * Registers a form control into the form for validation. * - * * [control] - The form control which will be registered (see [ngControl]). + * * [control] - The form control which will be registered (see [NgControl]). */ - addControl(NgControl control) { + void addControl(NgControl control) { _controls.add(control); if (control.name != null) { _controlByName.putIfAbsent(control.name, () => new List()).add(control); @@ -93,10 +147,9 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { * De-registers a form control from the list of controls associated with the * form. * - * * [control] - The form control which will be de-registered (see - * [ngControl]). + * * [control] - The form control which will be de-registered (see [NgControl]). */ - removeControl(NgControl control) { + void removeControl(NgControl control) { _controls.remove(control); String key = control.name; if (key != null && _controlByName.containsKey(key)) { @@ -107,7 +160,12 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { } } - removeStates(NgControl control) { + /** + * Clears all the info and error states that are associated with the control. + * + * * [control] - The form control which will be cleared of all state (see [NgControl]). + */ + void removeStates(NgControl control) { bool hasRemovals = false; errorStates.keys.toList().forEach((state) { Set matchingControls = errorStates[state]; @@ -133,41 +191,48 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { } /** - * Sets the validity status of the given control/errorType pair within - * the list of controls registered on the form. Depending on the validation - * state of the existing controls, this will either change valid to true - * or invalid to true depending on if all controls are valid or if one - * or more of them is invalid. + * Whether or not the control contains the given error. * - * * [control] - The registered control object (see [ngControl]). - * * [errorType] - The error associated with the control (e.g. required, url, - * number, etc...). - * * [isValid] - Whether the given error is valid or not (false would mean the - * error is real). + * * [errorName] - The name of the error (e.g. ng-required, ng-pattern, etc...) */ - bool hasErrorState(String key) => errorStates.containsKey(key); + bool hasErrorState(String errorName) => errorStates.containsKey(errorName); - addErrorState(NgControl control, String state) { - element..addClass(state + '-invalid')..removeClass(state + '-valid'); - errorStates.putIfAbsent(state, () => new Set()).add(control); - _parentControl.addErrorState(this, state); + /** + * Adds the given childControl/errorName to the list of errors present on the control. Once + * added all associated parent controls will be registered with the error as well. + * + * * [childControl] - The child control that contains the error. + * * [errorName] - The name of the given error (e.g. ng-required, ng-pattern, etc...). + */ + void addErrorState(NgControl childControl, String errorName) { + element..addClass(errorName + '-invalid')..removeClass(errorName + '-valid'); + errorStates.putIfAbsent(errorName, () => new Set()).add(childControl); + _parentControl.addErrorState(this, errorName); } - removeErrorState(NgControl control, String state) { - if (!errorStates.containsKey(state)) return; + /** + * Removes the given childControl/errorName from the list of errors present on the control. Once + * removed the control will update any parent controls depending if error is not present on + * any other inner controls and or models. + * + * * [childControl] - The child control that contains the error. + * * [errorName] - The name of the given error (e.g. ng-required, ng-pattern, etc...). + */ + void removeErrorState(NgControl childControl, String errorName) { + if (!errorStates.containsKey(errorName)) return; - bool hasState = _controls.isEmpty || - _controls.every((control) { - return !control.hasErrorState(state); + bool hasError = _controls.isEmpty || + _controls.every((childControl) { + return !childControl.hasErrorState(errorName); }); - if (hasState) { - errorStates.remove(state); - _parentControl.removeErrorState(this, state); - element..removeClass(state + '-invalid')..addClass(state + '-valid'); + if (hasError) { + errorStates.remove(errorName); + _parentControl.removeErrorState(this, errorName); + element..removeClass(errorName + '-invalid')..addClass(errorName + '-valid'); } } - _getOppositeInfoState(String state) { + String _getOppositeInfoState(String state) { switch(state) { case NG_DIRTY: return NG_PRISTINE; @@ -181,35 +246,51 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { } } - addInfoState(NgControl control, String state) { - String oppositeState = _getOppositeInfoState(state); + /** + * Registers a non-error state on the control with the given childControl/stateName data. Once + * added the control will also add the same data to any associated parent controls. + * + * * [childControl] - The child control that contains the error. + * * [stateName] - The name of the given error (e.g. ng-required, ng-pattern, etc...). + */ + void addInfoState(NgControl childControl, String stateName) { + String oppositeState = _getOppositeInfoState(stateName); if (oppositeState != null) { element.removeClass(oppositeState); } - element.addClass(state); - infoStates.putIfAbsent(state, () => new Set()).add(control); - _parentControl.addInfoState(this, state); + element.addClass(stateName); + infoStates.putIfAbsent(stateName, () => new Set()).add(childControl); + _parentControl.addInfoState(this, stateName); } - removeInfoState(NgControl control, String state) { - String oppositeState = _getOppositeInfoState(state); - if (infoStates.containsKey(state)) { + /** + * De-registers the provided state on the control with the given childControl. The state + * will be fully removed from the control if all of the inner controls/models also do not + * contain the state. If so then the state will also be attempted to be removed from the + * associated parent controls. + * + * * [childControl] - The child control that contains the error. + * * [stateName] - The name of the given error (e.g. ng-required, ng-pattern, etc...). + */ + void removeInfoState(NgControl childControl, String stateName) { + String oppositeState = _getOppositeInfoState(stateName); + if (infoStates.containsKey(stateName)) { bool hasState = _controls.isEmpty || - _controls.every((control) { - return !control.infoStates.containsKey(state); + _controls.every((childControl) { + return !childControl.infoStates.containsKey(stateName); }); if (hasState) { if (oppositeState != null) { element.addClass(oppositeState); } - element.removeClass(state); - infoStates.remove(state); - _parentControl.removeInfoState(this, state); + element.removeClass(stateName); + infoStates.remove(stateName); + _parentControl.removeInfoState(this, stateName); } } else if (oppositeState != null) { NgControl parent = this; do { - parent.element..addClass(oppositeState)..removeClass(state); + parent.element..addClass(oppositeState)..removeClass(stateName); parent = parent.parentControl; } while(parent != null && !(parent is NgNullControl)); diff --git a/lib/directive/ng_form.dart b/lib/directive/ng_form.dart index 9672916e7..8e02ff549 100644 --- a/lib/directive/ng_form.dart +++ b/lib/directive/ng_form.dart @@ -49,18 +49,29 @@ class NgForm extends NgControl { } } + /** + * The name of the control. This is usually fetched via the name attribute that is + * present on the element that the control is bound to. + */ @NgAttr('name') get name => _name; - set name(value) { + set name(String value) { if (value != null) { super.name = value; _scope.context[name] = this; } } + /** + * The list of associated child controls. + */ get controls => _controlByName; - NgControl operator[](name) => + /** + * Returns the child control that is associated with the given name. If multiple + * child controls contain the same name then the first instance will be returned. + */ + NgControl operator[](String name) => controls.containsKey(name) ? controls[name][0] : null; } diff --git a/lib/directive/ng_model.dart b/lib/directive/ng_model.dart index 14d1e98d8..204de391f 100644 --- a/lib/directive/ng_model.dart +++ b/lib/directive/ng_model.dart @@ -1,5 +1,11 @@ part of angular.directive; +/** + * NgModelConverter is the class interface for performing transformations on + * the viewValue and modelValue properties on a model. A new converter can be created + * by implementing the NgModelConverter class and then attaching to a model via the + * provided setter. + */ abstract class NgModelConverter { String get name; parse(value) => value; @@ -27,16 +33,17 @@ class NgModel extends NgControl implements NgAttachAware { BoundSetter setter = (_, [__]) => null; - var _originalValue, _viewValue, _modelValue; String _exp; - final _validators = []; + var _originalValue, _viewValue, _modelValue; bool _alwaysProcessViewValue; bool _toBeValidated = false; + Function render = (value) => null; + final _validators = []; NgModelConverter _converter; + Watch _removeWatch; bool _watchCollection; - Function render = (value) => null; NgModel(this._scope, NgElement element, Injector injector, this._parser, NodeAttrs attrs, NgAnimate animate) @@ -53,7 +60,7 @@ class NgModel extends NgControl implements NgAttachAware { markAsPristine(); } - void processViewValue(value) { + void _processViewValue(value) { validate(); _viewValue = converter.format(value); _scope.rootScope.domWrite(() => render(_viewValue)); @@ -63,9 +70,13 @@ class NgModel extends NgControl implements NgAttachAware { watchCollection = false; } + /** + * Resets the model value to it's original (pristine) value. If the model has been interacted + * with by the user at all then the model will be also reset to an "untouched" state. + */ void reset() { markAsUntouched(); - processViewValue(_originalValue); + _processViewValue(_originalValue); modelValue = _originalValue; } @@ -90,6 +101,10 @@ class NgModel extends NgControl implements NgAttachAware { addInfoState(this, NgControl.NG_DIRTY); } + /** + * Flags the model to be set for validation upon the next digest. This operation is useful + * to optimize validations incase multiple validations are triggered one after the other. + */ void validateLater() { if (_toBeValidated) return; _toBeValidated = true; @@ -100,10 +115,13 @@ class NgModel extends NgControl implements NgAttachAware { }); } - get converter => _converter; + /** + * Returns the associated converter that is used with the model. + */ + NgModelConverter get converter => _converter; set converter(NgModelConverter c) { _converter = c; - processViewValue(modelValue); + _processViewValue(modelValue); } @NgAttr('name') @@ -121,7 +139,7 @@ class NgModel extends NgControl implements NgAttachAware { var onChange = (value, [_]) { if (_alwaysProcessViewValue || _modelValue != value) { _modelValue = value; - processViewValue(value); + _processViewValue(value); } }; @@ -147,22 +165,34 @@ class NgModel extends NgControl implements NgAttachAware { _scope.rootScope.runAsync(() { _modelValue = boundExpression(); _originalValue = modelValue; - processViewValue(_modelValue); + _processViewValue(_modelValue); }); } + /** + * Applies the given [error] to the model. + */ void addError(String error) { this.addErrorState(this, error); } + /** + * Removes the given [error] from the model. + */ void removeError(String error) { this.removeErrorState(this, error); } + /** + * Adds the given [info] state to the model. + */ void addInfo(String info) { this.addInfoState(this, info); } + /** + * Removes the given [info] state from the model. + */ void removeInfo(String info) { this.removeInfoState(this, info); } @@ -188,10 +218,14 @@ class NgModel extends NgControl implements NgAttachAware { : markAsDirty(); } + /** + * Returns the list of validators that are registered on the model. + */ List get validators => _validators; /** - * Executes a validation on the form against each of the validation present on the model. + * Executes a validation on the model against each of the validators present on the model. + * Once complete, the model will either be set as valid or invalid. */ void validate() { _toBeValidated = false; diff --git a/lib/directive/ng_model_validators.dart b/lib/directive/ng_model_validators.dart index b8fced816..e01c69fab 100644 --- a/lib/directive/ng_model_validators.dart +++ b/lib/directive/ng_model_validators.dart @@ -1,6 +1,15 @@ part of angular.directive; +/** + * NgValidator is the class interface for performing validations for an NgModel instance. + */ abstract class NgValidator { + /** + * The name of the validator. This name will be used as the key value within the + * model.errorStates map and it will also be applied as a CSS class on the associated + * DOM element. Therefore, as a best practice, please do not include spaces for the validator + * name since it may cause issues with the CSS naming. + */ String get name; bool isValid(modelValue); } From 7fc1182b5971373020abaeeec12a25a9c2288318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matias=20Niemel=C3=A4?= Date: Mon, 24 Mar 2014 13:57:04 -0400 Subject: [PATCH 2/2] fix(forms): change valid_submit and invalid_submit to camelcase BREAKING CHANGE: All form code that uses control.valid_submit and control.invalid_submit will throw an error. Instead use control.validSubmit and control.invalidSubmit to check the submission validitity on a control. --- lib/directive/ng_control.dart | 18 +++++++++--------- test/directive/ng_form_spec.dart | 12 ++++++------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/directive/ng_control.dart b/lib/directive/ng_control.dart index 669df5351..d9d8c5905 100644 --- a/lib/directive/ng_control.dart +++ b/lib/directive/ng_control.dart @@ -18,7 +18,7 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { static const NG_SUBMIT_INVALID = "ng-submit-invalid"; String _name; - bool _submit_valid; + bool _submitValid; final NgControl _parentControl; final NgAnimate _animate; @@ -63,10 +63,10 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { void onSubmit(bool valid) { if (valid) { - _submit_valid = true; + _submitValid = true; element..addClass(NG_SUBMIT_VALID)..removeClass(NG_SUBMIT_INVALID); } else { - _submit_valid = false; + _submitValid = false; element..addClass(NG_SUBMIT_INVALID)..removeClass(NG_SUBMIT_VALID); } _controls.forEach((control) { @@ -79,17 +79,17 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { /** * Whether or not the form has been submitted yet. */ - bool get submitted => _submit_valid != null; + bool get submitted => _submitValid != null; /** * Whether or not the form was valid when last submitted. */ - bool get valid_submit => _submit_valid == true; + bool get validSubmit => _submitValid == true; /** * Whether or not the form was invalid when last submitted. */ - bool get invalid_submit => _submit_valid == false; + bool get invalidSubmit => _submitValid == false; String get name => _name; set name(value) { @@ -299,7 +299,7 @@ abstract class NgControl implements NgAttachAware, NgDetachAware { } class NgNullControl implements NgControl { - var _name, _dirty, _valid, _submit_valid, _pristine, _element, _touched; + var _name, _dirty, _valid, _submitValid, _pristine, _element, _touched; var _controls, _parentControl, _controlName, _animate, infoStates, errorStates; var errors, _controlByName; NgElement element; @@ -315,8 +315,8 @@ class NgNullControl implements NgControl { set name(name) {} bool get submitted => false; - bool get valid_submit => true; - bool get invalid_submit => false; + bool get validSubmit => true; + bool get invalidSubmit => false; bool get pristine => true; bool get dirty => false; bool get valid => true; diff --git a/test/directive/ng_form_spec.dart b/test/directive/ng_form_spec.dart index f1b020517..afef7782f 100644 --- a/test/directive/ng_form_spec.dart +++ b/test/directive/ng_form_spec.dart @@ -522,8 +522,8 @@ void main() { var formElement = form.element.node; expect(form.submitted).toBe(false); - expect(form.valid_submit).toBe(false); - expect(form.invalid_submit).toBe(false); + expect(form.validSubmit).toBe(false); + expect(form.invalidSubmit).toBe(false); expect(formElement.classes.contains('ng-submit-invalid')).toBe(false); expect(formElement.classes.contains('ng-submit-valid')).toBe(false); @@ -533,8 +533,8 @@ void main() { scope.apply(); expect(form.submitted).toBe(true); - expect(form.valid_submit).toBe(false); - expect(form.invalid_submit).toBe(true); + expect(form.validSubmit).toBe(false); + expect(form.invalidSubmit).toBe(true); expect(formElement.classes.contains('ng-submit-invalid')).toBe(true); expect(formElement.classes.contains('ng-submit-valid')).toBe(false); @@ -543,8 +543,8 @@ void main() { scope.apply(); expect(form.submitted).toBe(true); - expect(form.valid_submit).toBe(true); - expect(form.invalid_submit).toBe(false); + expect(form.validSubmit).toBe(true); + expect(form.invalidSubmit).toBe(false); expect(formElement.classes.contains('ng-submit-invalid')).toBe(false); expect(formElement.classes.contains('ng-submit-valid')).toBe(true); });