| OLD | NEW |
| 1 part of angular.directive; | 1 part of angular.directive; |
| 2 | 2 |
| 3 /** | 3 abstract class NgControl implements NgDetachAware { |
| 4 * The NgControl class is a super-class for handling info and error states betwe
en | 4 static const NG_VALID_CLASS = "ng-valid"; |
| 5 * inner controls and models. NgControl will automatically apply the associated
CSS | 5 static const NG_INVALID_CLASS = "ng-invalid"; |
| 6 * classes for the error and info states that are applied as well as status flag
s. | 6 static const NG_PRISTINE_CLASS = "ng-pristine"; |
| 7 * NgControl is used with the form and fieldset as well as all other directives
that | 7 static const NG_DIRTY_CLASS = "ng-dirty"; |
| 8 * are used for user input with NgModel. | 8 static const NG_TOUCHED_CLASS = "ng-touched"; |
| 9 */ | 9 static const NG_UNTOUCHED_CLASS = "ng-untouched"; |
| 10 abstract class NgControl implements AttachAware, DetachAware { | 10 static const NG_SUBMIT_VALID_CLASS = "ng-submit-valid"; |
| 11 static const NG_VALID = "ng-valid"; | 11 static const NG_SUBMIT_INVALID_CLASS = "ng-submit-invalid"; |
| 12 static const NG_INVALID = "ng-invalid"; | |
| 13 static const NG_PRISTINE = "ng-pristine"; | |
| 14 static const NG_DIRTY = "ng-dirty"; | |
| 15 static const NG_TOUCHED = "ng-touched"; | |
| 16 static const NG_UNTOUCHED = "ng-untouched"; | |
| 17 static const NG_SUBMIT_VALID = "ng-submit-valid"; | |
| 18 static const NG_SUBMIT_INVALID = "ng-submit-invalid"; | |
| 19 | 12 |
| 20 String _name; | 13 String _name; |
| 21 bool _submitValid; | 14 bool _dirty; |
| 22 | 15 bool _pristine; |
| 16 bool _valid; |
| 17 bool _invalid; |
| 18 bool _touched; |
| 19 bool _untouched; |
| 20 bool _submit_valid; |
| 21 |
| 22 final Scope _scope; |
| 23 final NgControl _parentControl; | 23 final NgControl _parentControl; |
| 24 final Animate _animate; | 24 dom.Element _element; |
| 25 final NgElement _element; | 25 |
| 26 | 26 final Map<String, List<NgControl>> errors = new Map<String, List<NgControl>>
(); |
| 27 final _controls = new List<NgControl>(); | 27 final List<NgControl> _controls = new List<NgControl>(); |
| 28 final _controlByName = new Map<String, List<NgControl>>(); | 28 final Map<String, NgControl> _controlByName = new Map<String, NgControl>(); |
| 29 | 29 |
| 30 /** | 30 NgControl(Scope this._scope, dom.Element this._element, Injector injector) |
| 31 * The list of errors present on the control represented by an error name and | 31 : _parentControl = injector.parent.get(NgControl) |
| 32 * an inner control instance. | 32 { |
| 33 */ | 33 pristine = true; |
| 34 final errorStates = new Map<String, Set<NgControl>>(); | 34 untouched = true; |
| 35 | 35 |
| 36 /** | 36 _scope.on('submitNgControl').listen((e) => _onSubmit(e.data)); |
| 37 * The list of info messages present on the control represented by an state n
ame and | 37 } |
| 38 * an inner control instance. | 38 |
| 39 */ | 39 detach() { |
| 40 final infoStates = new Map<String, Set<NgControl>>(); | 40 for (int i = _controls.length - 1; i >= 0; --i) { |
| 41 | 41 removeControl(_controls[i]); |
| 42 NgControl(NgElement this._element, Injector injector, | 42 } |
| 43 Animate this._animate) | 43 } |
| 44 : _parentControl = injector.parent.get(NgControl); | 44 |
| 45 | 45 reset() { |
| 46 @override | 46 _scope.broadcast('resetNgModel'); |
| 47 void attach() { | 47 untouched = true; |
| 48 } |
| 49 |
| 50 _onSubmit(bool valid) { |
| 51 if (valid) { |
| 52 _submit_valid = true; |
| 53 element.classes..add(NG_SUBMIT_VALID_CLASS)..remove(NG_SUBMIT_INVALID_CLAS
S); |
| 54 } else { |
| 55 _submit_valid = false; |
| 56 element.classes..add(NG_SUBMIT_INVALID_CLASS)..remove(NG_SUBMIT_VALID_CLAS
S); |
| 57 } |
| 58 } |
| 59 |
| 60 get submitted => _submit_valid != null; |
| 61 get valid_submit => _submit_valid == true; |
| 62 get invalid_submit => _submit_valid == false; |
| 63 |
| 64 get name => _name; |
| 65 set name(value) { |
| 66 _name = value; |
| 48 _parentControl.addControl(this); | 67 _parentControl.addControl(this); |
| 49 } | 68 } |
| 50 | 69 |
| 51 @override | 70 get element => _element; |
| 52 void detach() { | 71 |
| 53 _parentControl..removeStates(this)..removeControl(this); | 72 get pristine => _pristine; |
| 54 } | 73 set pristine(value) { |
| 55 | 74 _pristine = true; |
| 56 /** | 75 _dirty = false; |
| 57 * Resets the form and inner models to their pristine state. | 76 |
| 58 */ | 77 element.classes..remove(NG_DIRTY_CLASS)..add(NG_PRISTINE_CLASS); |
| 59 void reset() { | 78 } |
| 60 _controls.forEach((control) { | 79 |
| 61 control.reset(); | 80 get dirty => _dirty; |
| 62 }); | 81 set dirty(value) { |
| 63 } | 82 _dirty = true; |
| 64 | 83 _pristine = false; |
| 65 void onSubmit(bool valid) { | 84 |
| 66 if (valid) { | 85 element.classes..remove(NG_PRISTINE_CLASS)..add(NG_DIRTY_CLASS); |
| 67 _submitValid = true; | 86 |
| 68 element..addClass(NG_SUBMIT_VALID)..removeClass(NG_SUBMIT_INVALID); | 87 //as soon as one of the controls/models is modified |
| 69 } else { | 88 //then all of the parent controls are dirty as well |
| 70 _submitValid = false; | 89 _parentControl.dirty = true; |
| 71 element..addClass(NG_SUBMIT_INVALID)..removeClass(NG_SUBMIT_VALID); | 90 } |
| 72 } | 91 |
| 73 _controls.forEach((control) { | 92 get valid => _valid; |
| 74 control.onSubmit(valid); | 93 set valid(value) { |
| 75 }); | 94 _invalid = false; |
| 76 } | 95 _valid = true; |
| 77 | 96 |
| 78 NgControl get parentControl => _parentControl; | 97 element.classes..remove(NG_INVALID_CLASS)..add(NG_VALID_CLASS); |
| 79 | 98 } |
| 80 /** | 99 |
| 81 * Whether or not the form has been submitted yet. | 100 get invalid => _invalid; |
| 82 */ | 101 set invalid(value) { |
| 83 bool get submitted => _submitValid != null; | 102 _valid = false; |
| 84 | 103 _invalid = true; |
| 85 /** | 104 |
| 86 * Whether or not the form was valid when last submitted. | 105 element.classes..remove(NG_VALID_CLASS)..add(NG_INVALID_CLASS); |
| 87 */ | 106 } |
| 88 bool get validSubmit => _submitValid == true; | 107 |
| 89 | 108 get touched => _touched; |
| 90 /** | 109 set touched(value) { |
| 91 * Whether or not the form was invalid when last submitted. | 110 _touched = true; |
| 92 */ | 111 _untouched = false; |
| 93 bool get invalidSubmit => _submitValid == false; | 112 |
| 94 | 113 element.classes..remove(NG_UNTOUCHED_CLASS)..add(NG_TOUCHED_CLASS); |
| 95 String get name => _name; | 114 |
| 96 void set name(value) { | 115 //as soon as one of the controls/models is touched |
| 97 _name = value; | 116 //then all of the parent controls are touched as well |
| 98 } | 117 _parentControl.touched = true; |
| 99 | 118 } |
| 100 /** | 119 |
| 101 * Whether or not the form was invalid when last submitted. | 120 get untouched => _untouched; |
| 102 */ | 121 set untouched(value) { |
| 103 NgElement get element => _element; | 122 _touched = false; |
| 104 | 123 _untouched = true; |
| 105 /** | 124 element.classes..remove(NG_TOUCHED_CLASS)..add(NG_UNTOUCHED_CLASS); |
| 106 * A control is considered valid if all inner models are valid. | 125 } |
| 107 */ | |
| 108 bool get valid => !invalid; | |
| 109 | |
| 110 /** | |
| 111 * A control is considered invalid if any inner models are invalid. | |
| 112 */ | |
| 113 bool get invalid => errorStates.isNotEmpty; | |
| 114 | |
| 115 /** | |
| 116 * Whether or not the control's or model's data has not been changed. | |
| 117 */ | |
| 118 bool get pristine => !dirty; | |
| 119 | |
| 120 /** | |
| 121 * Whether or not the control's or model's data has been changed. | |
| 122 */ | |
| 123 bool get dirty => infoStates.containsKey(NG_DIRTY); | |
| 124 | |
| 125 /** | |
| 126 * Whether or not the control/model has not been interacted with by the user. | |
| 127 */ | |
| 128 bool get untouched => !touched; | |
| 129 | |
| 130 /** | |
| 131 * Whether or not the control/model has been interacted with by the user. | |
| 132 */ | |
| 133 bool get touched => infoStates.containsKey(NG_TOUCHED); | |
| 134 | 126 |
| 135 /** | 127 /** |
| 136 * Registers a form control into the form for validation. | 128 * Registers a form control into the form for validation. |
| 137 * | 129 * |
| 138 * * [control] - The form control which will be registered (see [NgControl]). | 130 * * [control] - The form control which will be registered (see [ngControl]). |
| 139 */ | 131 */ |
| 140 void addControl(NgControl control) { | 132 addControl(NgControl control) { |
| 141 _controls.add(control); | 133 _controls.add(control); |
| 142 if (control.name != null) { | 134 if (control.name != null) { |
| 143 _controlByName.putIfAbsent(control.name, () => <NgControl>[]).add(control)
; | 135 _controlByName[control.name] = control; |
| 144 } | 136 } |
| 145 } | 137 } |
| 146 | 138 |
| 147 /** | 139 /** |
| 148 * De-registers a form control from the list of controls associated with the | 140 * De-registers a form control from the list of controls associated with the |
| 149 * form. | 141 * form. |
| 150 * | 142 * |
| 151 * * [control] - The form control which will be de-registered (see [NgControl]
). | 143 * * [control] - The form control which will be de-registered (see |
| 144 * [ngControl]). |
| 152 */ | 145 */ |
| 153 void removeControl(NgControl control) { | 146 removeControl(NgControl control) { |
| 154 _controls.remove(control); | 147 _controls.remove(control); |
| 155 String key = control.name; | 148 if (control.name != null) { |
| 156 if (key != null && _controlByName.containsKey(key)) { | 149 _controlByName.remove(control.name); |
| 157 _controlByName[key].remove(control); | |
| 158 if (_controlByName[key].isEmpty) _controlByName.remove(key); | |
| 159 } | 150 } |
| 160 } | 151 } |
| 161 | 152 |
| 162 /** | 153 /** |
| 163 * Clears all the info and error states that are associated with the control. | 154 * Sets the validity status of the given control/errorType pair within |
| 155 * the list of controls registered on the form. Depending on the validation |
| 156 * state of the existing controls, this will either change valid to true |
| 157 * or invalid to true depending on if all controls are valid or if one |
| 158 * or more of them is invalid. |
| 164 * | 159 * |
| 165 * * [control] - The form control which will be cleared of all state (see [NgC
ontrol]). | 160 * * [control] - The registered control object (see [ngControl]). |
| 161 * * [errorType] - The error associated with the control (e.g. required, url, |
| 162 * number, etc...). |
| 163 * * [isValid] - Whether the given error is valid or not (false would mean the |
| 164 * error is real). |
| 166 */ | 165 */ |
| 167 void removeStates(NgControl control) { | 166 updateControlValidity(NgControl control, String errorType, bool isValid) { |
| 168 bool hasRemovals = false; | 167 List queue = errors[errorType]; |
| 169 errorStates.keys.toList().forEach((state) { | 168 |
| 170 Set matchingControls = errorStates[state]; | 169 if (isValid) { |
| 171 matchingControls.remove(control); | 170 if (queue != null) { |
| 172 if (matchingControls.isEmpty) { | 171 queue.remove(control); |
| 173 errorStates.remove(state); | 172 if (queue.isEmpty) { |
| 174 hasRemovals = true; | 173 errors.remove(errorType); |
| 174 _parentControl.updateControlValidity(this, errorType, true); |
| 175 } |
| 175 } | 176 } |
| 176 }); | 177 if (errors.isEmpty) { |
| 177 | 178 valid = true; |
| 178 infoStates.keys.toList().forEach((state) { | |
| 179 Set matchingControls = infoStates[state]; | |
| 180 matchingControls.remove(control); | |
| 181 if (matchingControls.isEmpty) { | |
| 182 infoStates.remove(state); | |
| 183 hasRemovals = true; | |
| 184 } | 179 } |
| 185 }); | 180 } else { |
| 186 | 181 if (queue == null) { |
| 187 if (hasRemovals) _parentControl.removeStates(this); | 182 queue = new List<NgControl>(); |
| 188 } | 183 errors[errorType] = queue; |
| 189 | 184 _parentControl.updateControlValidity(this, errorType, false); |
| 190 /** | 185 } else if (queue.contains(control)) return; |
| 191 * Whether or not the control contains the given error. | 186 |
| 192 * | 187 queue.add(control); |
| 193 * * [errorName] - The name of the error (e.g. ng-required, ng-pattern, etc...
) | 188 invalid = true; |
| 194 */ | |
| 195 bool hasErrorState(String errorName) => errorStates.containsKey(errorName); | |
| 196 | |
| 197 /** | |
| 198 * Adds the given childControl/errorName to the list of errors present on the
control. Once | |
| 199 * added all associated parent controls will be registered with the error as w
ell. | |
| 200 * | |
| 201 * * [childControl] - The child control that contains the error. | |
| 202 * * [errorName] - The name of the given error (e.g. ng-required, ng-pattern,
etc...). | |
| 203 */ | |
| 204 void addErrorState(NgControl childControl, String errorName) { | |
| 205 element..addClass(errorName + '-invalid')..removeClass(errorName + '-valid')
; | |
| 206 errorStates.putIfAbsent(errorName, () => new Set()).add(childControl); | |
| 207 _parentControl.addErrorState(this, errorName); | |
| 208 } | |
| 209 | |
| 210 /** | |
| 211 * Removes the given childControl/errorName from the list of errors present on
the control. Once | |
| 212 * removed the control will update any parent controls depending if error is n
ot present on | |
| 213 * any other inner controls and or models. | |
| 214 * | |
| 215 * * [childControl] - The child control that contains the error. | |
| 216 * * [errorName] - The name of the given error (e.g. ng-required, ng-pattern,
etc...). | |
| 217 */ | |
| 218 void removeErrorState(NgControl childControl, String errorName) { | |
| 219 if (!errorStates.containsKey(errorName)) return; | |
| 220 | |
| 221 bool hasError = _controls.any((child) => child.hasErrorState(errorName)); | |
| 222 if (!hasError) { | |
| 223 errorStates.remove(errorName); | |
| 224 _parentControl.removeErrorState(this, errorName); | |
| 225 element..removeClass(errorName + '-invalid')..addClass(errorName + '-valid
'); | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 String _getOppositeInfoState(String state) { | |
| 230 switch(state) { | |
| 231 case NG_DIRTY: | |
| 232 return NG_PRISTINE; | |
| 233 case NG_TOUCHED: | |
| 234 return NG_UNTOUCHED; | |
| 235 default: | |
| 236 //not all info states have an opposite value | |
| 237 return null; | |
| 238 } | |
| 239 } | |
| 240 | |
| 241 /** | |
| 242 * Registers a non-error state on the control with the given childControl/stat
eName data. Once | |
| 243 * added the control will also add the same data to any associated parent cont
rols. | |
| 244 * | |
| 245 * * [childControl] - The child control that contains the error. | |
| 246 * * [stateName] - The name of the given error (e.g. ng-required, ng-pattern,
etc...). | |
| 247 */ | |
| 248 void addInfoState(NgControl childControl, String stateName) { | |
| 249 String oppositeState = _getOppositeInfoState(stateName); | |
| 250 if (oppositeState != null) element.removeClass(oppositeState); | |
| 251 element.addClass(stateName); | |
| 252 infoStates.putIfAbsent(stateName, () => new Set()).add(childControl); | |
| 253 _parentControl.addInfoState(this, stateName); | |
| 254 } | |
| 255 | |
| 256 /** | |
| 257 * De-registers the provided state on the control with the given childControl.
The state | |
| 258 * will be fully removed from the control if all of the inner controls/models
also do not | |
| 259 * contain the state. If so then the state will also be attempted to be remove
d from the | |
| 260 * associated parent controls. | |
| 261 * | |
| 262 * * [childControl] - The child control that contains the error. | |
| 263 * * [stateName] - The name of the given error (e.g. ng-required, ng-pattern,
etc...). | |
| 264 */ | |
| 265 void removeInfoState(NgControl childControl, String stateName) { | |
| 266 String oppositeState = _getOppositeInfoState(stateName); | |
| 267 if (infoStates.containsKey(stateName)) { | |
| 268 bool hasState = _controls.any((child) => | |
| 269 child.infoStates.containsKey(stateName)); | |
| 270 if (!hasState) { | |
| 271 if (oppositeState != null) element.addClass(oppositeState); | |
| 272 element.removeClass(stateName); | |
| 273 infoStates.remove(stateName); | |
| 274 _parentControl.removeInfoState(this, stateName); | |
| 275 } | |
| 276 } else if (oppositeState != null) { | |
| 277 NgControl parent = this; | |
| 278 do { | |
| 279 parent.element..addClass(oppositeState)..removeClass(stateName); | |
| 280 parent = parent.parentControl; | |
| 281 } | |
| 282 while(parent != null && parent is! NgNullControl); | |
| 283 } | 189 } |
| 284 } | 190 } |
| 285 } | 191 } |
| 286 | 192 |
| 287 class NgNullControl implements NgControl { | 193 class NgNullControl implements NgControl { |
| 288 var _name, _dirty, _valid, _submitValid, _pristine, _element, _touched; | 194 var _name, _dirty, _valid, _invalid, _submit_valid, _pristine, _element; |
| 289 var _controls, _parentControl, _controlName, _animate, infoStates, errorStates
; | 195 var _touched, _untouched; |
| 196 var _controls, _scope, _parentControl, _controlName; |
| 290 var errors, _controlByName; | 197 var errors, _controlByName; |
| 291 NgElement element; | 198 dom.Element element; |
| 292 | 199 |
| 293 void onSubmit(bool valid) {} | 200 NgNullControl() {} |
| 294 | 201 _onSubmit(bool valid) {} |
| 295 void addControl(control) {} | 202 |
| 296 void removeControl(control) {} | 203 addControl(control) {} |
| 297 void updateControlValidity(NgControl ctrl, String errorType, bool isValid) {} | 204 removeControl(control) {} |
| 298 | 205 updateControlValidity(NgControl control, String errorType, bool isValid) {} |
| 299 String get name => null; | 206 |
| 300 void set name(name) {} | 207 get name => null; |
| 301 | 208 set name(name) {} |
| 302 bool get submitted => false; | 209 |
| 303 bool get validSubmit => true; | 210 get submitted => null; |
| 304 bool get invalidSubmit => false; | 211 get valid_submit => null; |
| 305 bool get pristine => true; | 212 get invalid_submit => null; |
| 306 bool get dirty => false; | 213 |
| 307 bool get valid => true; | 214 get pristine => null; |
| 308 bool get invalid => false; | 215 set pristine(value) {} |
| 309 bool get touched => false; | 216 |
| 310 bool get untouched => true; | 217 get dirty => null; |
| 311 | 218 set dirty(value) {} |
| 312 get parentControl => null; | 219 |
| 313 | 220 get valid => null; |
| 314 String _getOppositeInfoState(String state) => null; | 221 set valid(value) {} |
| 315 void addErrorState(NgControl control, String state) {} | 222 |
| 316 void removeErrorState(NgControl control, String state) {} | 223 get invalid => null; |
| 317 void addInfoState(NgControl control, String state) {} | 224 set invalid(value) {} |
| 318 void removeInfoState(NgControl control, String state) {} | 225 |
| 319 | 226 get touched => null; |
| 320 void reset() {} | 227 set touched(value) {} |
| 321 void attach() {} | 228 |
| 322 void detach() {} | 229 get untouched => null; |
| 323 | 230 set untouched(value) {} |
| 324 bool hasErrorState(String key) => false; | 231 |
| 325 | 232 reset() => null; |
| 326 void removeStates(NgControl control) {} | 233 detach() => null; |
| 234 |
| 327 } | 235 } |
| OLD | NEW |