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

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

Issue 257423008: Update all Angular libs (run update_all.sh). (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 8 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 * NgModelConverter is the class interface for performing transformations on
5 * the viewValue and modelValue properties on a model. A new converter can be cr eated
6 * by implementing the NgModelConverter class and then attaching to a model via the
7 * provided setter.
8 */
9 abstract class NgModelConverter {
10 String get name;
11 parse(value) => value;
12 format(value) => value;
13 }
14
15 class _NoopModelConverter extends NgModelConverter {
16 final name = 'ng-noop';
17 }
18
19 /**
4 * Ng-model directive is responsible for reading/writing to the model. 20 * 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 21 * 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 22 * events to listen for.) It is meant to be used with other directives which
7 * provide the rendering and listening capabilities. The directive itself 23 * provide the rendering and listening capabilities. The directive itself
8 * knows how to convert the view-value into model-value and vice versa by 24 * 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 25 * allowing others to register converters (To be implemented). It also
10 * knows how to (in)validate the model and the form in which it is declared 26 * knows how to (in)validate the model and the form in which it is declared
11 * (to be implemented) 27 * (to be implemented)
12 */ 28 */
13 @NgDirective(selector: '[ng-model]') 29 @Decorator(selector: '[ng-model]')
14 class NgModel extends NgControl implements NgAttachAware { 30 class NgModel extends NgControl implements AttachAware {
15 final NgForm _form; 31 final Scope _scope;
16 final AstParser _parser; 32
17
18 BoundGetter getter = ([_]) => null;
19 BoundSetter setter = (_, [__]) => null; 33 BoundSetter setter = (_, [__]) => null;
20 34
21 var _lastValue; 35 String _expression;
22 String _exp; 36 var _originalValue, _viewValue, _modelValue;
23 final _validators = <NgValidatable>[]; 37 bool _alwaysProcessViewValue;
24 38 bool _toBeValidated = false;
25 Watch _removeWatch; 39 Function render = (value) => null;
40
41 final _validators = <NgValidator>[];
42 NgModelConverter _converter;
43 Watch _watch;
26 bool _watchCollection; 44 bool _watchCollection;
27 Function render = (value) => null; 45
28 46 NgModel(this._scope, NgElement element, Injector injector, NodeAttrs attrs,
29 NgModel(Scope _scope, dom.Element _element, Injector injector, 47 Animate animate)
30 NgForm this._form, this._parser, NodeAttrs attrs) 48 : super(element, injector, animate)
31 : super(_scope, _element, injector)
32 { 49 {
33 _exp = attrs["ng-model"]; 50 _expression = attrs["ng-model"];
34 watchCollection = false; 51 watchCollection = false;
35 } 52
36 53 //Since the user will never be editing the value of a select element then
37 process(value, [_]) { 54 //there is no reason to guard the formatter from changing the DOM value.
55 _alwaysProcessViewValue = element.node.tagName == 'SELECT';
56 converter = new _NoopModelConverter();
57 markAsUntouched();
58 markAsPristine();
59 }
60
61 void _processViewValue(value) {
38 validate(); 62 validate();
39 _scope.rootScope.domWrite(() => render(value)); 63 _viewValue = converter.format(value);
40 } 64 _scope.rootScope.domWrite(() => render(_viewValue));
41 65 }
42 attach() { 66
67 void attach() {
43 watchCollection = false; 68 watchCollection = false;
44 _scope.on('resetNgModel').listen((e) => reset()); 69 }
45 } 70
46 71 /**
47 reset() { 72 * Resets the model value to it's original (pristine) value. If the model has been interacted
48 untouched = true; 73 * with by the user at all then the model will be also reset to an "untouched " state.
49 modelValue = _lastValue; 74 */
75 void reset() {
76 markAsUntouched();
77 _processViewValue(_originalValue);
78 modelValue = _originalValue;
79 }
80
81 void onSubmit(bool valid) {
82 super.onSubmit(valid);
83 if (valid) _originalValue = modelValue;
84 }
85
86 void markAsUntouched() {
87 removeInfoState(this, NgControl.NG_TOUCHED);
88 }
89
90 void markAsTouched() {
91 addInfoState(this, NgControl.NG_TOUCHED);
92 }
93
94 void markAsPristine() {
95 removeInfoState(this, NgControl.NG_DIRTY);
96 }
97
98 void markAsDirty() {
99 addInfoState(this, NgControl.NG_DIRTY);
100 }
101
102 /**
103 * Flags the model to be set for validation upon the next digest. This operat ion is useful
104 * to optimize validations incase multiple validations are triggered one afte r the other.
105 */
106 void validateLater() {
107 if (_toBeValidated) return;
108 _toBeValidated = true;
109 _scope.rootScope.runAsync(() {
110 if (_toBeValidated) {
111 validate();
112 }
113 });
114 }
115
116 /**
117 * Returns the associated converter that is used with the model.
118 */
119 NgModelConverter get converter => _converter;
120 set converter(NgModelConverter c) {
121 _converter = c;
122 _processViewValue(modelValue);
50 } 123 }
51 124
52 @NgAttr('name') 125 @NgAttr('name')
53 get name => _name; 126 String get name => _name;
54 set name(value) { 127 void set name(value) {
55 _name = value; 128 _name = value;
56 _parentControl.addControl(this); 129 _parentControl.addControl(this);
57 } 130 }
58 131
59 // TODO(misko): could we get rid of watch collection, and just always watch th e collection? 132 // TODO(misko): could we get rid of watch collection, and just always watch th e collection?
60 get watchCollection => _watchCollection; 133 bool get watchCollection => _watchCollection;
61 set watchCollection(value) { 134 void set watchCollection(value) {
62 if (_watchCollection == value) return; 135 if (_watchCollection == value) return;
136
137 var onChange = (value, [_]) {
138 if (_alwaysProcessViewValue || _modelValue != value) {
139 _modelValue = value;
140 _processViewValue(value);
141 }
142 };
143
63 _watchCollection = value; 144 _watchCollection = value;
64 if (_removeWatch!=null) _removeWatch.remove(); 145 if (_watch!=null) _watch.remove();
65 if (_watchCollection) { 146 if (_watchCollection) {
66 _removeWatch = _scope.watch( 147 _watch = _scope.watch(_expression, (changeRecord, _) {
67 _parser(_exp, collection: true), 148 onChange(changeRecord is CollectionChangeRecord
68 (changeRecord, _) { 149 ? changeRecord.iterable
69 var value = changeRecord is CollectionChangeRecord ? changeRecord.it erable: changeRecord; 150 : changeRecord);
70 process(value); 151 },
71 }); 152 collection: true);
72 } else if (_exp != null) { 153 } else if (_expression != null) {
73 _removeWatch = _scope.watch(_exp, process); 154 _watch = _scope.watch(_expression, onChange);
74 } 155 }
75 } 156 }
76 157
77 // TODO(misko): getters/setters need to go. We need AST here. 158 // TODO(misko): getters/setters need to go. We need AST here.
78 @NgCallback('ng-model') 159 @NgCallback('ng-model')
79 set model(BoundExpression boundExpression) { 160 void set model(BoundExpression boundExpression) {
80 getter = boundExpression;
81 setter = boundExpression.assign; 161 setter = boundExpression.assign;
82
83 _scope.rootScope.runAsync(() { 162 _scope.rootScope.runAsync(() {
84 _lastValue = modelValue; 163 _modelValue = boundExpression();
164 _originalValue = modelValue;
165 _processViewValue(_modelValue);
85 }); 166 });
86 } 167 }
87 168
88 // TODO(misko): right now viewValue and modelValue are the same, 169 /**
89 // but this needs to be changed to support converters and form validation 170 * Applies the given [error] to the model.
90 get viewValue => modelValue; 171 */
91 set viewValue(value) => modelValue = value; 172 void addError(String error) {
92 173 this.addErrorState(this, error);
93 get modelValue => getter(); 174 }
94 set modelValue(value) => setter(value); 175
95 176 /**
96 get validators => _validators; 177 * Removes the given [error] from the model.
97 178 */
98 /** 179 void removeError(String error) {
99 * Executes a validation on the form against each of the validation present on the model. 180 this.removeErrorState(this, error);
181 }
182
183 /**
184 * Adds the given [info] state to the model.
185 */
186 void addInfo(String info) {
187 this.addInfoState(this, info);
188 }
189
190 /**
191 * Removes the given [info] state from the model.
192 */
193 void removeInfo(String info) {
194 this.removeInfoState(this, info);
195 }
196
197 get viewValue => _viewValue;
198 void set viewValue(value) {
199 _viewValue = value;
200 modelValue = value;
201 }
202
203 get modelValue => _modelValue;
204 void set modelValue(value) {
205 try {
206 value = converter.parse(value);
207 } catch(e) {
208 value = null;
209 }
210 _modelValue = value;
211 setter(value);
212
213 if (modelValue == _originalValue) {
214 markAsPristine();
215 } else {
216 markAsDirty();
217 }
218 }
219
220 /**
221 * Returns the list of validators that are registered on the model.
222 */
223 List<NgValidator> get validators => _validators;
224
225 /**
226 * Executes a validation on the model against each of the validators present o n the model.
227 * Once complete, the model will either be set as valid or invalid.
100 */ 228 */
101 validate() { 229 void validate() {
230 _toBeValidated = false;
102 if (validators.isNotEmpty) { 231 if (validators.isNotEmpty) {
103 validators.forEach((validator) { 232 validators.forEach((validator) {
104 setValidity(validator.name, validator.isValid(viewValue)); 233 if (validator.isValid(modelValue)) {
234 removeError(validator.name);
235 } else {
236 addError(validator.name);
237 }
105 }); 238 });
239 }
240
241 if (invalid) {
242 addInfo(NgControl.NG_INVALID);
106 } else { 243 } else {
107 valid = true; 244 removeInfo(NgControl.NG_INVALID);
108 } 245 }
109 }
110
111 setValidity(String name, bool valid) {
112 this.updateControlValidity(this, name, valid);
113 } 246 }
114 247
115 /** 248 /**
116 * Registers a validator into the model to consider when running validate(). 249 * Registers a validator into the model to consider when running validate().
117 */ 250 */
118 addValidator(NgValidatable v) { 251 void addValidator(NgValidator v) {
119 validators.add(v); 252 validators.add(v);
120 validate(); 253 validateLater();
121 } 254 }
122 255
123 /** 256 /**
124 * De-registers a validator from the model. 257 * De-registers a validator from the model.
125 */ 258 */
126 removeValidator(NgValidatable v) { 259 void removeValidator(NgValidator v) {
127 validators.remove(v); 260 validators.remove(v);
128 validate(); 261 validateLater();
129 } 262 }
130 } 263 }
131 264
132 /** 265 /**
133 * Usage: 266 * Usage:
134 * 267 *
135 * <input type="checkbox" ng-model="flag"> 268 * <input type="checkbox"
136 * 269 * ng-model="expr"
137 * This creates a two way databinding between the boolean expression specified 270 * [ng-true-value="t_expr"]
138 * in ng-model and the checkbox input element in the DOM.  If the ng-model value 271 * [ng-false-value="f_expr"]
139 * is falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is 272 * >
140 * unchecked. Otherwise, it is checked.  Likewise, when the checkbox is checked, 273 *
141 * the model value is set to true. When unchecked, it is set to false. 274 * This creates a two way databinding between the `ng-model` expression
275 * and the checkbox input element state.
276 *
277 * If the optional `ng-true-value` is absent then: if the model expression
278 * evaluates to true or to a nonzero [:num:], then the checkbox is checked;
279 * otherwise, it is unchecked.
280 *
281 * If `ng-true-value="t_expr"` is present, then: if the model expression
282 * evaluates to the same value as `t_expr` then the checkbox is checked;
283 * otherwise, it is unchecked.
284 *
285 * When the checkbox is checked, the model is set to the value of `t_expr` if
286 * present, true otherwise. When unchecked, it is set to the value of
287 * `f_expr` if present, false otherwise.
288 *
289 * Also see [NgTrueValue] and [NgFalseValue].
142 */ 290 */
143 @NgDirective(selector: 'input[type=checkbox][ng-model]') 291 @Decorator(selector: 'input[type=checkbox][ng-model]')
144 class InputCheckboxDirective { 292 class InputCheckbox {
145 final dom.InputElement inputElement; 293 final dom.CheckboxInputElement inputElement;
146 final NgModel ngModel; 294 final NgModel ngModel;
147 final NgTrueValue ngTrueValue; 295 final NgTrueValue ngTrueValue;
148 final NgFalseValue ngFalseValue; 296 final NgFalseValue ngFalseValue;
149 final Scope scope; 297 final Scope scope;
150 298
151 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, 299 InputCheckbox(dom.Element this.inputElement, this.ngModel,
152 this.scope, this.ngTrueValue, this.ngFalseValue) { 300 this.scope, this.ngTrueValue, this.ngFalseValue) {
153 ngModel.render = (value) { 301 ngModel.render = (value) {
154 inputElement.checked = ngTrueValue.isValue(inputElement, value); 302 scope.rootScope.domWrite(() {
303 inputElement.checked = ngTrueValue.isValue(value);
304 });
155 }; 305 };
156 inputElement.onChange.listen((value) { 306 inputElement
157 ngModel.dirty = true; 307 ..onChange.listen((_) {
158 ngModel.viewValue = inputElement.checked 308 ngModel.viewValue = inputElement.checked
159 ? ngTrueValue.readValue(inputElement) 309 ? ngTrueValue.value : ngFalseValue.value;
160 : ngFalseValue.readValue(inputElement); 310 })
161 }); 311 ..onBlur.listen((e) {
312 ngModel.markAsTouched();
313 });
162 } 314 }
163 } 315 }
164 316
165 /** 317 /**
166 * Usage: 318 * Usage:
167 * 319 *
168 * <input type="text|url|password|email" ng-model="myModel"> 320 * <input type="text|url|password|email" ng-model="myModel">
169 * <textarea ng-model="myModel"></textarea> 321 * <textarea ng-model="myModel"></textarea>
170 * 322 *
171 * This creates a two-way binding between any string-based input element 323 * This creates a two-way binding between any string-based input element
172 * (both <input> and <textarea>) so long as the ng-model attribute is 324 * (both `<input>` and `<textarea>`) so long as the ng-model attribute is
173 * present on the input element. Whenever the value of the input element 325 * present on the input element. Whenever the value of the input element
174 * changes then the matching model property on the scope will be updated 326 * changes then the matching model property on the scope will be updated
175 * as well as the other way around (when the scope property is updated). 327 * as well as the other way around (when the scope property is updated).
176 * 328 *
177 */ 329 */
178 @NgDirective(selector: 'textarea[ng-model]') 330 @Decorator(selector: 'textarea[ng-model]')
179 @NgDirective(selector: 'input[type=text][ng-model]') 331 @Decorator(selector: 'input[type=text][ng-model]')
180 @NgDirective(selector: 'input[type=password][ng-model]') 332 @Decorator(selector: 'input[type=password][ng-model]')
181 @NgDirective(selector: 'input[type=url][ng-model]') 333 @Decorator(selector: 'input[type=url][ng-model]')
182 @NgDirective(selector: 'input[type=email][ng-model]') 334 @Decorator(selector: 'input[type=email][ng-model]')
183 @NgDirective(selector: 'input[type=search][ng-model]') 335 @Decorator(selector: 'input[type=search][ng-model]')
184 class InputTextLikeDirective { 336 class InputTextLike {
185 final dom.Element inputElement; 337 final dom.Element inputElement;
186 final NgModel ngModel; 338 final NgModel ngModel;
187 final Scope scope; 339 final Scope scope;
188 String _inputType; 340 String _inputType;
189 341
190 get typedValue => (inputElement as dynamic).value; 342 get typedValue => (inputElement as dynamic).value;
191 set typedValue(value) => (inputElement as dynamic).value = (value == null) ? 343 void set typedValue(value) {
192 '' : 344 (inputElement as dynamic).value = (value == null) ? '' : value.toString();
193 value.toString(); 345 }
194 346
195 InputTextLikeDirective(this.inputElement, this.ngModel, this.scope) { 347 InputTextLike(this.inputElement, this.ngModel, this.scope) {
196 ngModel.render = (value) { 348 ngModel.render = (value) {
197 if (value == null) value = ''; 349 scope.rootScope.domWrite(() {
350 if (value == null) value = '';
198 351
199 var currentValue = typedValue; 352 var currentValue = typedValue;
200 if (value != currentValue && !(value is num && currentValue is num && 353 if (value != currentValue && !(value is num && currentValue is num &&
201 value.isNaN && currentValue.isNaN)) { 354 value.isNaN && currentValue.isNaN)) {
202 typedValue = value; 355 typedValue = value;
203 } 356 }
357 });
204 }; 358 };
205 inputElement 359 inputElement
206 ..onChange.listen(processValue) 360 ..onChange.listen(processValue)
207 ..onInput.listen(processValue) 361 ..onInput.listen(processValue)
208 ..onBlur.listen((e) { 362 ..onBlur.listen((e) {
209 if (ngModel.touched == null || ngModel.touched == false) { 363 ngModel.markAsTouched();
210 ngModel.touched = true;
211 }
212 }); 364 });
213 } 365 }
214 366
215 processValue([_]) { 367 void processValue([_]) {
216 var value = typedValue; 368 var value = typedValue;
217 if (value != ngModel.viewValue) { 369 if (value != ngModel.viewValue) ngModel.viewValue = value;
218 ngModel.dirty = true;
219 ngModel.viewValue = value;
220 }
221 ngModel.validate(); 370 ngModel.validate();
222 } 371 }
223 } 372 }
224 373
225 /** 374 /**
226 * Usage: 375 * Usage:
227 * 376 *
228 * <input type="number|range" ng-model="myModel"> 377 * <input type="number|range" ng-model="myModel">
229 * 378 *
230 * Model: 379 * Model:
231 * 380 *
232 * num myModel; 381 * num myModel;
233 * 382 *
234 * This creates a two-way binding between the input and the named model property 383 * This creates a two-way binding between the input and the named model property
235 * (e.g., myModel in the example above). When processing the input, its value is 384 * (e.g., myModel in the example above). When processing the input, its value is
236 * read as a [num], via the [dom.InputElement.valueAsNumber] field. If the input 385 * read as a [:num:], via the [dom.InputElement.valueAsNumber] field. If the
237 * text does not represent a number, then the model is appropriately set to 386 * input text does not represent a number, then the model is appropriately set
238 * [double.NAN]. Setting the model property to [null] will clear the input. 387 * to [double.NAN]. Setting the model property to [null] will clear the input.
239 * Setting the model to [double.NAN] will have no effect (input will be left 388 * Setting the model to [double.NAN] will have no effect (input will be left
240 * unchanged). 389 * unchanged).
241 */ 390 */
242 @NgDirective(selector: 'input[type=number][ng-model]') 391 @Decorator(selector: 'input[type=number][ng-model]')
243 @NgDirective(selector: 'input[type=range][ng-model]') 392 @Decorator(selector: 'input[type=range][ng-model]')
244 class InputNumberLikeDirective { 393 class InputNumberLike {
245 final dom.InputElement inputElement; 394 final dom.InputElement inputElement;
246 final NgModel ngModel; 395 final NgModel ngModel;
247 final Scope scope; 396 final Scope scope;
248 397
249 num get typedValue => inputElement.valueAsNumber; 398
399 // We can't use [inputElement.valueAsNumber] due to http://dartbug.com/15788
400 num get typedValue => num.parse(inputElement.value, (v) => double.NAN);
401
250 void set typedValue(num value) { 402 void set typedValue(num value) {
251 // [chalin, 2014-02-16] This post 403 // [chalin, 2014-02-16] This post
252 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-January/024829.h tml 404 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-January/024829.h tml
253 // suggests that setting `valueAsNumber` to null should clear the field, but 405 // suggests that setting `valueAsNumber` to null should clear the field, but
254 // it does not. [TODO: put BUG/ISSUE number here]. We implement a 406 // it does not. [TODO: put BUG/ISSUE number here]. We implement a
255 // workaround by setting `value`. Clean-up once the bug is fixed. 407 // workaround by setting `value`. Clean-up once the bug is fixed.
256 if (value == null) { 408 if (value == null) {
257 inputElement.value = null; 409 inputElement.value = null;
258 } else { 410 } else {
259 inputElement.valueAsNumber = value; 411 // We can't use inputElement.valueAsNumber due to http://dartbug.com/15788
260 } 412 inputElement.value = "$value";
261 } 413 }
262 414 }
263 InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.sco pe) { 415
416 InputNumberLike(dom.Element this.inputElement, this.ngModel, this.scope) {
264 ngModel.render = (value) { 417 ngModel.render = (value) {
265 if (value != typedValue 418 scope.rootScope.domWrite(() {
266 && (value == null || value is num && !value.isNaN)) { 419 if (value != typedValue
267 typedValue = value; 420 && (value == null || value is num && !value.isNaN)) {
268 } 421 typedValue = value;
422 }
423 });
269 }; 424 };
270 inputElement 425 inputElement
271 ..onChange.listen(relaxFnArgs(processValue)) 426 ..onChange.listen(relaxFnArgs(processValue))
272 ..onInput.listen(relaxFnArgs(processValue)); 427 ..onInput.listen(relaxFnArgs(processValue))
273 } 428 ..onBlur.listen((e) {
274 429 ngModel.markAsTouched();
275 processValue() { 430 });
431 }
432
433 void processValue() {
276 num value = typedValue; 434 num value = typedValue;
277 if (value != ngModel.viewValue) { 435 if (value != ngModel.viewValue) {
278 ngModel.dirty = true;
279 scope.eval(() => ngModel.viewValue = value); 436 scope.eval(() => ngModel.viewValue = value);
280 } 437 }
281 ngModel.validate(); 438 ngModel.validate();
439 }
440 }
441
442 /**
443 * This directive affects which IDL attribute will be used to read the value of
444 * date/time related input directives. Recognized values for this directive are:
445 *
446 * - [DATE]: [dom.InputElement].valueAsDate will be read.
447 * - [NUMBER]: [dom.InputElement].valueAsNumber will be read.
448 * - [STRING]: [dom.InputElement].value will be read.
449 *
450 * The default is [DATE]. Use other settings, e.g., when an app needs to support
451 * browsers that treat date-like inputs as text (in such a case the [STRING]
452 * kind would be appropriate) or, for browsers that fail to conform to the
453 * HTML5 standard in their processing of date-like inputs.
454 */
455 @Decorator(selector: 'input[type=date][ng-model][ng-bind-type]')
456 @Decorator(selector: 'input[type=time][ng-model][ng-bind-type]')
457 @Decorator(selector: 'input[type=datetime][ng-model][ng-bind-type]')
458 @Decorator(selector: 'input[type=datetime-local][ng-model][ng-bind-type]')
459 @Decorator(selector: 'input[type=month][ng-model][ng-bind-type]')
460 @Decorator(selector: 'input[type=week][ng-model][ng-bind-type]')
461 class NgBindTypeForDateLike {
462 static const DATE = 'date';
463 static const NUMBER = 'number';
464 static const STRING = 'string';
465 static const DEFAULT = DATE;
466 static const VALID_VALUES = const <String>[DATE, NUMBER, STRING];
467
468 final dom.InputElement inputElement;
469 String _idlAttrKind = DEFAULT;
470
471 NgBindTypeForDateLike(dom.Element this.inputElement);
472
473 @NgAttr('ng-bind-type')
474 void set idlAttrKind(final String _kind) {
475 String kind = _kind == null ? DEFAULT : _kind.toLowerCase();
476 if (!VALID_VALUES.contains(kind))
477 throw "Unsupported ng-bind-type attribute value '$_kind'; "
478 "it should be one of $VALID_VALUES";
479 _idlAttrKind = kind;
480 }
481
482 String get idlAttrKind => _idlAttrKind;
483
484 dynamic get inputTypedValue {
485 switch (idlAttrKind) {
486 case DATE: return inputValueAsDate;
487 case NUMBER: return inputElement.valueAsNumber;
488 default: return inputElement.value;
489 }
490 }
491
492 void set inputTypedValue(dynamic inputValue) {
493 if (inputValue is DateTime) {
494 inputValueAsDate = inputValue;
495 } else if (inputValue is num) {
496 inputElement.valueAsNumber = inputValue;
497 } else {
498 inputElement.value = inputValue;
499 }
500 }
501
502 /// Input's `valueAsDate` normalized to UTC (per HTML5 std).
503 DateTime get inputValueAsDate {
504 DateTime dt;
505 // Wrap in try-catch due to
506 // https://code.google.com/p/dart/issues/detail?id=17625
507 try {
508 dt = inputElement.valueAsDate;
509 } catch (e) {
510 dt = null;
511 }
512 return (dt != null && !dt.isUtc) ? dt.toUtc() : dt;
513 }
514
515 /// Set input's `valueAsDate`. Argument is normalized to UTC if necessary
516 /// (per HTML standard).
517 void set inputValueAsDate(DateTime dt) {
518 inputElement.valueAsDate = (dt != null && !dt.isUtc) ? dt.toUtc() : dt;
519 }
520 }
521
522 /**
523 * **Background: Standards and Browsers**
524 *
525 * According to the
526 * [HTML5 Standard](http://www.w3.org/TR/html5/forms.html#the-input-element),
527 * the [dom.InputElement.valueAsDate] and [dom.InputElement.valueAsNumber] IDL
528 * attributes should be available for all date/time related input types,
529 * except for `datetime-local` which is limited to
530 * [dom.InputElement.valueNumber]. Of course, all input types support
531 * [dom.InputElement.value] which yields a [String];
532 * [dom.InputElement.valueAsDate] yields a [DateTime] and
533 * [dom.InputElement.valueNumber] yields a [num].
534 *
535 * But not all browsers currently support date/time related inputs and of
536 * those that do, some deviate from the standard. Hence, this directive
537 * allows developers to control the IDL attribute that will be used
538 * to read the value of a date/time input. This is achieved via the subordinate
539 * 'ng-bind-type' directive; see [NgBindTypeForDateLike] for details.
540 *
541 * **Usage**:
542 *
543 * <input type="date|datetime|datetime-local|month|time|week"
544 * [ng-bind-type="date"]
545 * ng-model="myModel">
546 *
547 * **Model**:
548 *
549 * dynamic myModel; // one of DateTime | num | String
550 *
551 * This directive creates a two-way binding between the input and a model
552 * property. The subordinate 'ng-bind-type' directive determines which input
553 * IDL attribute is read (see [NgBindTypeForDateLike] for details) and
554 * hence the type of the read values. The type of the model property value
555 * determines which IDL attribute is written to: [DateTime] and [num] values
556 * are assigned to [dom.InputElement.valueAsDate] and
557 * [dom.InputElement.valueNumber], respectively; [String] and `null` values
558 * are assigned to [dom.InputElement.value]. Setting the model to `null` will
559 * clear the input if it is currently valid, otherwise, invalid input is left
560 * untouched (so that the user has an opportunity to correct it). To clear the
561 * input unconditionally, set the model property to the empty string ('').
562 *
563 * **Notes**:
564 * - As prescribed by the HTML5 standard, [DateTime] values returned by the
565 * `valueAsDate` IDL attribute are meant to be in UTC.
566 * - As of the HTML5 Editor's Draft 29 March 2014, datetime-local is no longer
567 * part of the standard. Other date related input are also at risk of being
568 * dropped.
569 */
570
571 @Decorator(selector: 'input[type=date][ng-model]',
572 module: InputDateLike.moduleFactory)
573 @Decorator(selector: 'input[type=time][ng-model]',
574 module: InputDateLike.moduleFactory)
575 @Decorator(selector: 'input[type=datetime][ng-model]',
576 module: InputDateLike.moduleFactory)
577 @Decorator(selector: 'input[type=datetime-local][ng-model]',
578 module: InputDateLike.moduleFactory)
579 @Decorator(selector: 'input[type=month][ng-model]',
580 module: InputDateLike.moduleFactory)
581 @Decorator(selector: 'input[type=week][ng-model]',
582 module: InputDateLike.moduleFactory)
583 class InputDateLike {
584 static Module moduleFactory() => new Module()..factory(NgBindTypeForDateLike,
585 (Injector i) => new NgBindTypeForDateLike(i.get(dom.Element)));
586 final dom.InputElement inputElement;
587 final NgModel ngModel;
588 final Scope scope;
589 NgBindTypeForDateLike ngBindType;
590
591 InputDateLike(dom.Element this.inputElement, this.ngModel, this.scope,
592 this.ngBindType) {
593 if (inputElement.type == 'datetime-local') {
594 ngBindType.idlAttrKind = NgBindTypeForDateLike.NUMBER;
595 }
596 ngModel.render = (value) {
597 scope.rootScope.domWrite(() {
598 if (!eqOrNaN(value, typedValue)) typedValue = value;
599 });
600 };
601 inputElement
602 ..onChange.listen(relaxFnArgs(processValue))
603 ..onInput.listen(relaxFnArgs(processValue))
604 ..onBlur.listen((e) {
605 ngModel.markAsTouched();
606 });
607 }
608
609 dynamic get typedValue => ngBindType.inputTypedValue;
610
611 void set typedValue(dynamic value) {
612 ngBindType.inputTypedValue = value;
613 }
614
615 void processValue() {
616 var value = typedValue;
617 // print("processValue: value=$value, model=${ngModel.viewValue}");
618 if (!eqOrNaN(value, ngModel.viewValue)) {
619 scope.eval(() => ngModel.viewValue = value);
620 }
621 ngModel.validate();
282 } 622 }
283 } 623 }
284 624
285 class _UidCounter { 625 class _UidCounter {
286 static final int CHAR_0 = "0".codeUnitAt(0); 626 static final int CHAR_0 = "0".codeUnitAt(0);
287 static final int CHAR_9 = "9".codeUnitAt(0); 627 static final int CHAR_9 = "9".codeUnitAt(0);
288 static final int CHAR_A = "A".codeUnitAt(0); 628 static final int CHAR_A = "A".codeUnitAt(0);
289 static final int CHAR_Z = "Z".codeUnitAt(0); 629 static final int CHAR_Z = "Z".codeUnitAt(0);
290 List charCodes = [CHAR_0, CHAR_0, CHAR_0]; 630 final charCodes = [CHAR_0, CHAR_0, CHAR_0];
291 631
292 String next() { 632 String next() {
293 for (int i = charCodes.length - 1; i >= 0; i--) { 633 for (int i = charCodes.length - 1; i >= 0; i--) {
294 int code = charCodes[i]; 634 int code = charCodes[i];
295 if (code == CHAR_9) { 635 if (code == CHAR_9) {
296 charCodes[i] = CHAR_A; 636 charCodes[i] = CHAR_A;
297 return new String.fromCharCodes(charCodes); 637 return new String.fromCharCodes(charCodes);
298 } else if (code == CHAR_Z) { 638 } else if (code == CHAR_Z) {
299 charCodes[i] = CHAR_0; 639 charCodes[i] = CHAR_0;
300 } else { 640 } else {
301 charCodes[i] = code + 1; 641 charCodes[i] = code + 1;
302 return new String.fromCharCodes(charCodes); 642 return new String.fromCharCodes(charCodes);
303 } 643 }
304 } 644 }
305 charCodes.insert(0, CHAR_0); 645 charCodes.insert(0, CHAR_0);
306 return new String.fromCharCodes(charCodes); 646 return new String.fromCharCodes(charCodes);
307 } 647 }
308 } 648 }
309 649
310 final _uidCounter = new _UidCounter(); 650 final _uidCounter = new _UidCounter();
311 651
312 /** 652 /**
313 * Use `ng-value` directive with `<input type="radio">` or `<option>` to 653 * Usage:
314 * allow binding to values other then strings. This is needed since the 654 *
315 * `value` attribute on DOM element `<input type="radio" value="foo">` can 655 * <input type=radio ng-model=model [ng-value=expr]>
316 * only be a string. With `ng-value` one can bind to any object. 656 *
657 * <option [ng-value=expr]>...</option>
658 *
659 * Example:
660 *
661 * <select ng-model="robot">
662 * <option ng-repeat="r in robots" ng-value="r">{{r.name}}</option>
663 * </select>
664 *
665 * When present, the value of this `ng-value` one-way attribute is assigned to
666 * the `ng-model` property when the corresponding radio element or option is
667 * selected. Note that `expr` can be not any type; i.e., it is not restricted
668 * to [String].
317 */ 669 */
318 @NgDirective(selector: '[ng-value]') 670 @Decorator(selector: 'input[type=radio][ng-model][ng-value]')
671 @Decorator(selector: 'option[ng-value]')
319 class NgValue { 672 class NgValue {
673 static Module _module = new Module()..type(NgValue);
674 static Module moduleFactory() => _module;
675
320 final dom.Element element; 676 final dom.Element element;
321 @NgOneWay('ng-value') 677 var _value;
322 var value;
323 678
324 NgValue(this.element); 679 NgValue(this.element);
325 680
326 readValue(dom.Element element) { 681 @NgOneWay('ng-value')
327 assert(this.element == null || element == this.element); 682 void set value(val) { this._value = val; }
328 return this.element == null ? (element as dynamic).value : value; 683 dynamic get value => _value == null ? (element as dynamic).value : _value;
329 }
330 } 684 }
331 685
332 /** 686 /**
333 * `ng-true-value` allows you to select any expression to be set to 687 * Usage:
334 * `ng-model` when checkbox is selected on `<input type="checkbox">`. 688 *
689 * <input type=checkbox
690 * ng-model=model
691 * [ng-true-value=expr]>
692 *
693 * The initial value of the expression bound to this directive is assigned to
694 * the model when the input is checked. Note that the expression can be of any
695 * type, not just [String]. Also see [InputCheckboxDirective], [NgFalseValue].
335 */ 696 */
336 @NgDirective(selector: '[ng-true-value]') 697 @Decorator(selector: 'input[type=checkbox][ng-model][ng-true-value]')
337 class NgTrueValue { 698 class NgTrueValue {
338 final dom.Element element; 699 final dom.Element element;
339 @NgOneWay('ng-true-value') 700 @NgOneWay('ng-true-value')
340 var value; 701 var value = true;
341 702
342 NgTrueValue(this.element); 703 NgTrueValue([this.element]);
343 704
344 readValue(dom.Element element) { 705 bool isValue(val) => element == null ? toBool(val) : val == value;
345 assert(this.element == null || element == this.element);
346 return this.element == null ? true : value;
347 }
348
349 isValue(dom.Element element, value) {
350 assert(this.element == null || element == this.element);
351 return this.element == null ? toBool(value) : value == this.value;
352 }
353 } 706 }
354 707
355 /** 708 /**
356 * `ng-false-value` allows you to select any expression to be set to 709 * Usage:
357 * `ng-model` when checkbox is deselected<input type="checkbox">`. 710 *
711 * <input type=checkbox
712 * ng-model=model
713 * [ng-false-value=expr]>
714 *
715 * The initial value of the expression bound to this directive is assigned to
716 * the model when the input is unchecked. Note that the expression can be of any
717 * type, not just [String]. Also see [InputCheckboxDirective], [NgTrueValue].
358 */ 718 */
359 @NgDirective(selector: '[ng-false-value]') 719 @Decorator(selector: 'input[type=checkbox][ng-model][ng-false-value]')
360 class NgFalseValue { 720 class NgFalseValue {
361 final dom.Element element; 721 final dom.Element element;
362 @NgOneWay('ng-false-value') 722 @NgOneWay('ng-false-value')
363 var value; 723 var value = false;
364 724
365 NgFalseValue(this.element); 725 NgFalseValue([this.element]);
366
367 readValue(dom.Element element) {
368 assert(this.element == null || element == this.element);
369 return this.element == null ? false : value;
370 }
371 } 726 }
372 727
373 /** 728 /**
374 * Usage: 729 * Usage:
375 * 730 *
376 * <input type="radio" ng-model="category"> 731 * <input type="radio" ng-model="category">
377 * 732 *
378 * This creates a two way databinding between the expression specified in 733 * This creates a two way databinding between the expression specified in
379 * ng-model and the range input elements in the DOM.  If the ng-model value is 734 * ng-model and the range input elements in the DOM. If the ng-model value is
380 * set to a value not corresponding to one of the radio elements, then none of 735 * set to a value not corresponding to one of the radio elements, then none of
381 * the radio elements will be check. Otherwise, only the corresponding input 736 * the radio elements will be check. Otherwise, only the corresponding input
382 * element in the group is checked. Likewise, when a radio button element is 737 * element in the group is checked. Likewise, when a radio button element is
383 * checked, the model is updated with its value. Radio buttons that have a 738 * checked, the model is updated with its value. Radio buttons that have a
384 * `name` attribute are left alone. Those that are missing the attribute will 739 * `name` attribute are left alone. Those that are missing the attribute will
385 * have a unique `name` assigned to them. This sequence goes `001`, `001`, ... 740 * have a unique `name` assigned to them. This sequence goes `001`, `001`, ...
386 * `009`, `00A`, `00Z`, `010`, and so on using more than 3 characters for the 741 * `009`, `00A`, `00Z`, `010`, and so on using more than 3 characters for the
387 * name when the counter overflows. 742 * name when the counter overflows.
388 */ 743 */
389 @NgDirective(selector: 'input[type=radio][ng-model]') 744 @Decorator(
390 class InputRadioDirective { 745 selector: 'input[type=radio][ng-model]',
746 module: NgValue.moduleFactory)
747 class InputRadio {
391 final dom.RadioButtonInputElement radioButtonElement; 748 final dom.RadioButtonInputElement radioButtonElement;
392 final NgModel ngModel; 749 final NgModel ngModel;
393 final NgValue ngValue; 750 final NgValue ngValue;
394 final Scope scope; 751 final Scope scope;
395 752
396 InputRadioDirective(dom.Element this.radioButtonElement, this.ngModel, 753 InputRadio(dom.Element this.radioButtonElement, this.ngModel,
397 this.scope, this.ngValue, NodeAttrs attrs) { 754 this.scope, this.ngValue, NodeAttrs attrs) {
398 // If there's no "name" set, we'll set a unique name. This ensures 755 // If there's no "name" set, we'll set a unique name. This ensures
399 // less surprising behavior about which radio buttons are grouped together. 756 // less surprising behavior about which radio buttons are grouped together.
400 if (attrs['name'] == '' || attrs['name'] == null) { 757 if (attrs['name'] == '' || attrs['name'] == null) {
401 attrs["name"] = _uidCounter.next(); 758 attrs["name"] = _uidCounter.next();
402 } 759 }
403 ngModel.render = (value) { 760 ngModel.render = (value) {
404 radioButtonElement.checked = (value == ngValue.readValue(radioButtonElemen t)); 761 scope.rootScope.domWrite(() {
762 radioButtonElement.checked = (value == ngValue.value);
763 });
405 }; 764 };
406 radioButtonElement.onClick.listen((_) { 765 radioButtonElement
407 if (radioButtonElement.checked) { 766 ..onClick.listen((_) {
408 ngModel.dirty = true; 767 if (radioButtonElement.checked) ngModel.viewValue = ngValue.value;
409 ngModel.viewValue = ngValue.readValue(radioButtonElement); 768 })
410 } 769 ..onBlur.listen((e) {
411 }); 770 ngModel.markAsTouched();
771 });
412 } 772 }
413 } 773 }
414 774
415 /** 775 /**
416 * Usage (span could be replaced with any element which supports text content, s uch as `p`): 776 * Usage (span could be replaced with any element which supports text content, s uch as `p`):
417 * 777 *
418 * <span contenteditable= ng-model="name"> 778 * <span contenteditable= ng-model="name">
419 * 779 *
420 * This creates a two way databinding between the expression specified in 780 * This creates a two way databinding between the expression specified in
421 * ng-model and the html element in the DOM.  If the ng-model value is 781 * ng-model and the html element in the DOM. If the ng-model value is
422 * `null`, it is treated as equivalent to the empty string for rendering 782 * `null`, it is treated as equivalent to the empty string for rendering
423 * purposes. 783 * purposes.
424 */ 784 */
425 @NgDirective(selector: '[contenteditable][ng-model]') 785 @Decorator(selector: '[contenteditable][ng-model]')
426 class ContentEditableDirective extends InputTextLikeDirective { 786 class ContentEditable extends InputTextLike {
427 ContentEditableDirective(dom.Element inputElement, NgModel ngModel, 787 ContentEditable(dom.Element inputElement, NgModel ngModel, Scope scope)
428 Scope scope)
429 : super(inputElement, ngModel, scope); 788 : super(inputElement, ngModel, scope);
430 789
431 // The implementation is identical to InputTextLikeDirective but use innerHtml instead of value 790 // The implementation is identical to InputTextLike but use innerHtml instead of value
432 get typedValue => (inputElement as dynamic).innerHtml; 791 String get typedValue => (inputElement as dynamic).innerHtml;
433 set typedValue(String value) => 792 void set typedValue(String value) {
434 (inputElement as dynamic).innerHtml = (value == null) ? '' : value; 793 (inputElement as dynamic).innerHtml = (value == null) ? '' : value;
794 }
435 } 795 }
OLDNEW
« no previous file with comments | « third_party/pkg/angular/lib/directive/ng_include.dart ('k') | third_party/pkg/angular/lib/directive/ng_model_select.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698