Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(24)

Side by Side Diff: third_party/pkg/angular/lib/directive/ng_model.dart

Issue 148453003: Updating Angular version (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 part of angular.directive; 1 part of angular.directive;
2 2
3 /** 3 /**
4 * Ng-model directive is responsible for reading/writing to the model. 4 * Ng-model directive is responsible for reading/writing to the model.
5 * The directive itself is headless. (It does not know how to render or what 5 * The directive itself is headless. (It does not know how to render or what
6 * events to listen for.) It is meant to be used with other directives which 6 * events to listen for.) It is meant to be used with other directives which
7 * provide the rendering and listening capabilities. The directive itself 7 * provide the rendering and listening capabilities. The directive itself
8 * knows how to convert the view-value into model-value and vice versa by 8 * knows how to convert the view-value into model-value and vice versa by
9 * allowing others to register converters (To be implemented). It also 9 * allowing others to register converters (To be implemented). It also
10 * knwos how to (in)validate the model and the form in which it is declared 10 * knows how to (in)validate the model and the form in which it is declared
11 * (to be implemented) 11 * (to be implemented)
12 */ 12 */
13 @NgDirective( 13 @NgDirective(
14 selector: '[ng-model]', 14 selector: '[ng-model]')
15 map: const {'ng-model': '&model'}) 15 class NgModel extends NgControl {
16 class NgModel { 16 final NgForm _form;
17 final dom.Element _element;
17 final Scope _scope; 18 final Scope _scope;
18 19
19 Getter getter = ([_]) => null; 20 Getter getter = ([_]) => null;
20 Setter setter = (_, [__]) => null; 21 Setter setter = (_, [__]) => null;
22
21 String _exp; 23 String _exp;
24 String _name;
22 25
26 final List<_NgModelValidator> _validators = new List<_NgModelValidator>();
27 final Map<String, bool> currentErrors = new Map<String, bool>();
23 28
24 Function _removeWatch = () => null; 29 Function _removeWatch = () => null;
25 bool _watchCollection; 30 bool _watchCollection;
26 31
27 Function render = (value) => null; 32 Function render = (value) => null;
28 33
29 NgModel(this._scope, NodeAttrs attrs) { 34 NgModel(this._scope, NodeAttrs attrs, [dom.Element this._element, NgForm this. _form]) {
30 _exp = 'ng-model=${attrs["ng-model"]}'; 35 _exp = 'ng-model=${attrs["ng-model"]}';
31 watchCollection = false; 36 watchCollection = false;
37
38 _form.addControl(this);
39 pristine = true;
40 }
41
42 get element => _element;
43
44 @NgAttr('name')
45 get name => _name;
46 set name(value) {
47 _name = value;
48 _form.addControl(this);
32 } 49 }
33 50
34 get watchCollection => _watchCollection; 51 get watchCollection => _watchCollection;
35 set watchCollection(value) { 52 set watchCollection(value) {
36 if (_watchCollection == value) return; 53 if (_watchCollection == value) return;
37 _watchCollection = value; 54 _watchCollection = value;
38 _removeWatch(); 55 _removeWatch();
39 if (_watchCollection) { 56 if (_watchCollection) {
40 _removeWatch = _scope.$watchCollection((s) => getter(), (value) => render( value), _exp); 57 _removeWatch = _scope.$watchCollection((s) => getter(), (value) => render( value), _exp);
41 } else { 58 } else {
42 _removeWatch = _scope.$watch((s) => getter(), (value) => render(value), _e xp); 59 _removeWatch = _scope.$watch((s) => getter(), (value) => render(value), _e xp);
43 } 60 }
44 } 61 }
45 62
63 @NgCallback('ng-model')
46 set model(BoundExpression boundExpression) { 64 set model(BoundExpression boundExpression) {
47 getter = boundExpression; 65 getter = boundExpression;
48 setter = boundExpression.assign; 66 setter = boundExpression.assign;
49 } 67 }
50 68
51 // TODO(misko): right now viewValue and modelValue are the same, 69 // TODO(misko): right now viewValue and modelValue are the same,
52 // but this needs to be changed to support converters and form validation 70 // but this needs to be changed to support converters and form validation
53 get viewValue => modelValue; 71 get viewValue => modelValue;
54 set viewValue(value) => modelValue = value; 72 set viewValue(value) => modelValue = value;
55 73
56 get modelValue => getter(); 74 get modelValue => getter();
57 set modelValue(value) => setter(value); 75 set modelValue(value) => setter(value);
76
77 get validators => _validators;
78
79 /**
80 * Executes a validation on the form against each of the validation present on the model.
81 */
82 validate() {
83 if(validators.length > 0) {
84 validators.forEach((validator) {
85 setValidity(validator.name, validator.isValid());
86 });
87 } else {
88 valid = true;
89 }
90 }
91
92 /**
93 * Sets the validity status of the given errorType on the model. Depending on if
94 * valid or invalid, the matching CSS classes will be added/removed on the inp ut
95 * element associated with the model. If any errors exist on the model then in valid
96 * will be set to true otherwise valid will be set to true.
97 *
98 * * [errorType] - The name of the error (e.g. required, url, number, etc...).
99 * * [isValid] - Whether or not the given error is valid or not (false would m ean the error is real).
100 */
101 setValidity(String errorType, bool isValid) {
102 if(isValid) {
103 if(currentErrors.containsKey(errorType)) {
104 currentErrors.remove(errorType);
105 }
106 if(valid != true && currentErrors.isEmpty) {
107 valid = true;
108 }
109 } else if(!currentErrors.containsKey(errorType)) {
110 currentErrors[errorType] = true;
111 invalid = true;
112 }
113
114 if(_form != null) {
115 _form.setValidity(this, errorType, isValid);
116 }
117 }
118
119 /**
120 * Registers a validator into the model to consider when running validate().
121 */
122 addValidator(_NgModelValidator v) {
123 validators.add(v);
124 validate();
125 }
126
127 /**
128 * De-registers a validator from the model.
129 */
130 removeValidator(_NgModelValidator v) {
131 validators.remove(v);
132 validate();
133 }
134
135 /**
136 * Removes the model from the control/form.
137 */
138 destroy() {
139 _form.removeControl(this);
140 }
58 } 141 }
59 142
60 /** 143 /**
61 * Usage: 144 * Usage:
62 * 145 *
63 * <input type="checkbox" ng-model="flag"> 146 * <input type="checkbox" ng-model="flag">
64 * 147 *
65 * This creates a two way databinding between the boolean expression specified i n 148 * This creates a two way databinding between the boolean expression specified i n
66 * ng-model and the checkbox input element in the DOM.  If the ng-model value is 149 * ng-model and the checkbox input element in the DOM.  If the ng-model value is
67 * falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is unchecked. 150 * falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is unchecked.
(...skipping 11 matching lines...) Expand all
79 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, this.scope ) { 162 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, this.scope ) {
80 ngModel.render = (value) { 163 ngModel.render = (value) {
81 inputElement.checked = value == null ? false : toBool(value); 164 inputElement.checked = value == null ? false : toBool(value);
82 }; 165 };
83 inputElement.onChange.listen((value) { 166 inputElement.onChange.listen((value) {
84 scope.$apply(() => ngModel.viewValue = inputElement.checked); 167 scope.$apply(() => ngModel.viewValue = inputElement.checked);
85 }); 168 });
86 } 169 }
87 } 170 }
88 171
89 172 /**
90 abstract class _InputTextlikeDirective { 173 * Usage:
174 *
175 * <input type="text|number|url|password|email" ng-model="myModel">
176 * <textarea ng-model="myModel"></textarea>
177 *
178 * This creates a two-way binding between any string-based input element
179 * (both <input> and <textarea>) so long as the ng-model attribute is
180 * present on the input element. Whenever the value of the input element
181 * changes then the matching model property on the scope will be updated
182 * as well as the other way around (when the scope property is updated).
183 *
184 */
185 @NgDirective(selector: 'textarea[ng-model]')
186 @NgDirective(selector: 'input[type=text][ng-model]')
187 @NgDirective(selector: 'input[type=password][ng-model]')
188 @NgDirective(selector: 'input[type=url][ng-model]')
189 @NgDirective(selector: 'input[type=email][ng-model]')
190 @NgDirective(selector: 'input[type=number][ng-model]')
191 class InputTextLikeDirective {
91 dom.Element inputElement; 192 dom.Element inputElement;
92 NgModel ngModel; 193 NgModel ngModel;
93 Scope scope; 194 Scope scope;
195 String _inputType;
94 196
95 get typedValue => (inputElement as dynamic).value; 197 get typedValue => (inputElement as dynamic).value;
96 set typedValue(String value) => (inputElement as dynamic).value = (value == nu ll) ? '' : value; 198 set typedValue(value) => (inputElement as dynamic).value = (value == null) ? ' ' : value.toString();
97 199
98 _InputTextlikeDirective(dom.Element this.inputElement, this.ngModel, this.scop e) { 200 InputTextLikeDirective(dom.Element this.inputElement, NgModel this.ngModel, Sc ope this.scope) {
99 ngModel.render = (value) { 201 ngModel.render = (value) {
100 if (value == null) value = ''; 202 if (value == null) value = '';
101 203
102 var currentValue = typedValue; 204 var currentValue = typedValue;
103 if (value != currentValue && !(value is num && currentValue is num && valu e.isNaN && currentValue.isNaN)) { 205 if (value != currentValue && !(value is num && currentValue is num && valu e.isNaN && currentValue.isNaN)) {
104 typedValue = value; 206 typedValue = value;
105 } 207 }
106 }; 208 };
107 inputElement.onChange.listen(relaxFnArgs(processValue)); 209 inputElement.onChange.listen(relaxFnArgs(processValue));
108 inputElement.onKeyDown.listen((e) { 210 inputElement.onKeyDown.listen((e) {
109 new async.Timer(Duration.ZERO, processValue); 211 new async.Timer(Duration.ZERO, processValue);
110 scope.$skipAutoDigest(); 212 scope.$skipAutoDigest();
111 }); 213 });
112 } 214 }
113 215
114 processValue() { 216 processValue() {
217 ngModel.validate();
115 var value = typedValue; 218 var value = typedValue;
116 if (value != ngModel.viewValue) { 219 if (value != ngModel.viewValue) {
117 scope.$apply(() => ngModel.viewValue = value); 220 scope.$apply(() => ngModel.viewValue = value);
118 } 221 }
119 } 222 }
120 } 223 }
121 224
122 /**
123 * Usage:
124 *
125 * <input type="text" ng-model="name">
126 *
127 * This creates a two way databinding between the expression specified in
128 * ng-model and the text input element in the DOM.  If the ng-model value is
129 * `null`, it is treated as equivalent to the empty string for rendering
130 * purposes.
131 */
132 @NgDirective(selector: 'input[type=text][ng-model]')
133 class InputTextDirective extends _InputTextlikeDirective {
134 InputTextDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
135 super(inputElement, ngModel, scope);
136
137 }
138
139 /**
140 * Usage:
141 *
142 * <input type="password" ng-model="name">
143 *
144 * This creates a two way databinding between the expression specified in
145 * ng-model and the password input element in the DOM.  If the ng-model value is
146 * `null`, it is treated as equivalent to the empty string for rendering
147 * purposes.
148 */
149 @NgDirective(selector: 'input[type=password][ng-model]')
150 class InputPasswordDirective extends _InputTextlikeDirective {
151 InputPasswordDirective(dom.Element inputElement, NgModel ngModel, Scope scope) :
152 super(inputElement, ngModel, scope);
153 }
154
155 /**
156 * Usage:
157 *
158 * <textarea ng-model="text">
159 *
160 * This creates a two way databinding between the expression specified in
161 * ng-model and the textarea element in the DOM.  If the ng-model value is
162 * `null`, it is treated as equivalent to the empty string for rendering
163 * purposes.
164 */
165 @NgDirective(selector: 'textarea[ng-model]')
166 class TextAreaDirective extends _InputTextlikeDirective {
167 TextAreaDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
168 super(inputElement, ngModel, scope);
169 }
170
171 /**
172 * Usage:
173 *
174 * <input type="number" ng-model="name">
175 *
176 * This creates a two way databinding between the expression specified in
177 * ng-model and the number input element in the DOM.  If the ng-model value is
178 * `null` or `NaN`, the DOM element is not updated. If the value in the DOM
179 * element is an invalid number, then the expression specified by the `ng-model`
180 * is set to null.,
181 */
182 @NgDirective(selector: 'input[type=number][ng-model]')
183 class InputNumberDirective extends _InputTextlikeDirective {
184 InputNumberDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
185 super(inputElement, ngModel, scope);
186
187 get typedValue => (inputElement as dom.InputElement).valueAsNumber;
188
189 set typedValue(var value) {
190 if (value != null && value is num) {
191 num number = value as num;
192 if (!value.isNaN) {
193 (inputElement as dom.InputElement).valueAsNumber = value;
194 }
195 }
196 }
197 }
198
199 /**
200 * Usage:
201 *
202 * <input type="email" ng-model="emailAddress">
203 *
204 * This creates a two way databinding between the expression specified in
205 * ng-model and the email input element in the DOM.  If the ng-model value is
206 * `null`, the DOM element is not updated. If the value in the DOM element is
207 * an invalid e-mail address, then the expression specified by the `ng-model` is
208 * set to null.,
209 */
210 @NgDirective(selector: 'input[type=email][ng-model]')
211 class InputEmailDirective extends _InputTextlikeDirective {
212 static final EMAIL_REGEXP = new RegExp(
213 r'^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}$');
214 InputEmailDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
215 super(inputElement, ngModel, scope);
216
217 String get typedValue {
218 String value = (inputElement as dom.InputElement).value;
219 return EMAIL_REGEXP.hasMatch(value) ? value : null;
220 }
221
222 set typedValue(String value) {
223 if (value != null && EMAIL_REGEXP.hasMatch(value)) {
224 (inputElement as dom.InputElement).value = value;
225 }
226 }
227 }
228
229
230 /**
231 * Usage:
232 *
233 * <input type="url" ng-model="website">
234 *
235 * This creates a two way databinding between the expression specified in
236 * ng-model and the `url` input element in the DOM.  If the ng-model value is
237 * `null`, the DOM element is not updated. If the value in the DOM element is
238 * an invalid URL, then the expression specified by the `ng-model` is set to
239 * null.,
240 */
241 @NgDirective(selector: 'input[type=url][ng-model]')
242 class InputUrlDirective extends _InputTextlikeDirective {
243 static final URL_REGEXP = new RegExp(
244 r'^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?' +
245 r'(\/|\/([\w#!:.?+=&%@!\-\/]))?$');
246 InputUrlDirective(dom.Element inputElement, NgModel ngModel, Scope scope):
247 super(inputElement, ngModel, scope);
248
249 String get typedValue {
250 String value = (inputElement as dom.InputElement).value;
251 return URL_REGEXP.hasMatch(value) ? value : null;
252 }
253
254 set typedValue(String value) {
255 if (value != null && URL_REGEXP.hasMatch(value)) {
256 (inputElement as dom.InputElement).value = value;
257 }
258 }
259 }
260
261 class _UidCounter { 225 class _UidCounter {
262 static final int CHAR_0 = "0".codeUnitAt(0); 226 static final int CHAR_0 = "0".codeUnitAt(0);
263 static final int CHAR_9 = "9".codeUnitAt(0); 227 static final int CHAR_9 = "9".codeUnitAt(0);
264 static final int CHAR_A = "A".codeUnitAt(0); 228 static final int CHAR_A = "A".codeUnitAt(0);
265 static final int CHAR_Z = "Z".codeUnitAt(0); 229 static final int CHAR_Z = "Z".codeUnitAt(0);
266 List charCodes = [CHAR_0, CHAR_0, CHAR_0]; 230 List charCodes = [CHAR_0, CHAR_0, CHAR_0];
267 231
268 String next() { 232 String next() {
269 for (int i = charCodes.length-1; i >= 0; i--) { 233 for (int i = charCodes.length-1; i >= 0; i--) {
270 int code = charCodes[i]; 234 int code = charCodes[i];
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
318 ngModel.render = (String value) { 282 ngModel.render = (String value) {
319 radioButtonElement.checked = (value == radioButtonElement.value); 283 radioButtonElement.checked = (value == radioButtonElement.value);
320 }; 284 };
321 radioButtonElement.onClick.listen((_) { 285 radioButtonElement.onClick.listen((_) {
322 if (radioButtonElement.checked) { 286 if (radioButtonElement.checked) {
323 scope.$apply(() => ngModel.viewValue = radioButtonElement.value); 287 scope.$apply(() => ngModel.viewValue = radioButtonElement.value);
324 } 288 }
325 }); 289 });
326 } 290 }
327 } 291 }
292
293 /**
294 * Usage (span could be replaced with any element which supports text content, s uch as `p`):
295 *
296 * <span contenteditable= ng-model="name">
297 *
298 * This creates a two way databinding between the expression specified in
299 * ng-model and the html element in the DOM.  If the ng-model value is
300 * `null`, it is treated as equivalent to the empty string for rendering
301 * purposes.
302 */
303 @NgDirective(selector: '[contenteditable][ng-model]')
304 class ContentEditableDirective extends InputTextLikeDirective {
305 ContentEditableDirective(dom.Element inputElement, NgModel ngModel, Scope scop e):
306 super(inputElement, ngModel, scope);
307
308 // The implementation is identical to InputTextLikeDirective but use innerHtml instead of value
309 get typedValue => (inputElement as dynamic).innerHtml;
310 set typedValue(String value) => (inputElement as dynamic).innerHtml = (value = = null) ? '' : value;
311 }
OLDNEW
« no previous file with comments | « third_party/pkg/angular/lib/directive/ng_form.dart ('k') | third_party/pkg/angular/lib/directive/ng_model_validators.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698