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