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

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

Issue 256553002: Revert "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 /**
20 * Ng-model directive is responsible for reading/writing to the model. 4 * Ng-model directive is responsible for reading/writing to the model.
21 * 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
22 * 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
23 * provide the rendering and listening capabilities. The directive itself 7 * provide the rendering and listening capabilities. The directive itself
24 * 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
25 * allowing others to register converters (To be implemented). It also 9 * allowing others to register converters (To be implemented). It also
26 * knows 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
27 * (to be implemented) 11 * (to be implemented)
28 */ 12 */
29 @Decorator(selector: '[ng-model]') 13 @NgDirective(selector: '[ng-model]')
30 class NgModel extends NgControl implements AttachAware { 14 class NgModel extends NgControl implements NgAttachAware {
31 final Scope _scope; 15 final NgForm _form;
16 final AstParser _parser;
32 17
18 BoundGetter getter = ([_]) => null;
33 BoundSetter setter = (_, [__]) => null; 19 BoundSetter setter = (_, [__]) => null;
34 20
35 String _expression; 21 var _lastValue;
36 var _originalValue, _viewValue, _modelValue; 22 String _exp;
37 bool _alwaysProcessViewValue; 23 final _validators = <NgValidatable>[];
38 bool _toBeValidated = false; 24
25 Watch _removeWatch;
26 bool _watchCollection;
39 Function render = (value) => null; 27 Function render = (value) => null;
40 28
41 final _validators = <NgValidator>[]; 29 NgModel(Scope _scope, dom.Element _element, Injector injector,
42 NgModelConverter _converter; 30 NgForm this._form, this._parser, NodeAttrs attrs)
43 Watch _watch; 31 : super(_scope, _element, injector)
44 bool _watchCollection;
45
46 NgModel(this._scope, NgElement element, Injector injector, NodeAttrs attrs,
47 Animate animate)
48 : super(element, injector, animate)
49 { 32 {
50 _expression = attrs["ng-model"]; 33 _exp = attrs["ng-model"];
51 watchCollection = false;
52
53 //Since the user will never be editing the value of a select element then
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) {
62 validate();
63 _viewValue = converter.format(value);
64 _scope.rootScope.domWrite(() => render(_viewValue));
65 }
66
67 void attach() {
68 watchCollection = false; 34 watchCollection = false;
69 } 35 }
70 36
71 /** 37 process(value, [_]) {
72 * Resets the model value to it's original (pristine) value. If the model has been interacted 38 validate();
73 * with by the user at all then the model will be also reset to an "untouched " state. 39 _scope.rootScope.domWrite(() => render(value));
74 */
75 void reset() {
76 markAsUntouched();
77 _processViewValue(_originalValue);
78 modelValue = _originalValue;
79 } 40 }
80 41
81 void onSubmit(bool valid) { 42 attach() {
82 super.onSubmit(valid); 43 watchCollection = false;
83 if (valid) _originalValue = modelValue; 44 _scope.on('resetNgModel').listen((e) => reset());
84 } 45 }
85 46
86 void markAsUntouched() { 47 reset() {
87 removeInfoState(this, NgControl.NG_TOUCHED); 48 untouched = true;
88 } 49 modelValue = _lastValue;
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);
123 } 50 }
124 51
125 @NgAttr('name') 52 @NgAttr('name')
126 String get name => _name; 53 get name => _name;
127 void set name(value) { 54 set name(value) {
128 _name = value; 55 _name = value;
129 _parentControl.addControl(this); 56 _parentControl.addControl(this);
130 } 57 }
131 58
132 // TODO(misko): could we get rid of watch collection, and just always watch th e collection? 59 // TODO(misko): could we get rid of watch collection, and just always watch th e collection?
133 bool get watchCollection => _watchCollection; 60 get watchCollection => _watchCollection;
134 void set watchCollection(value) { 61 set watchCollection(value) {
135 if (_watchCollection == value) return; 62 if (_watchCollection == value) return;
136
137 var onChange = (value, [_]) {
138 if (_alwaysProcessViewValue || _modelValue != value) {
139 _modelValue = value;
140 _processViewValue(value);
141 }
142 };
143
144 _watchCollection = value; 63 _watchCollection = value;
145 if (_watch!=null) _watch.remove(); 64 if (_removeWatch!=null) _removeWatch.remove();
146 if (_watchCollection) { 65 if (_watchCollection) {
147 _watch = _scope.watch(_expression, (changeRecord, _) { 66 _removeWatch = _scope.watch(
148 onChange(changeRecord is CollectionChangeRecord 67 _parser(_exp, collection: true),
149 ? changeRecord.iterable 68 (changeRecord, _) {
150 : changeRecord); 69 var value = changeRecord is CollectionChangeRecord ? changeRecord.it erable: changeRecord;
151 }, 70 process(value);
152 collection: true); 71 });
153 } else if (_expression != null) { 72 } else if (_exp != null) {
154 _watch = _scope.watch(_expression, onChange); 73 _removeWatch = _scope.watch(_exp, process);
155 } 74 }
156 } 75 }
157 76
158 // TODO(misko): getters/setters need to go. We need AST here. 77 // TODO(misko): getters/setters need to go. We need AST here.
159 @NgCallback('ng-model') 78 @NgCallback('ng-model')
160 void set model(BoundExpression boundExpression) { 79 set model(BoundExpression boundExpression) {
80 getter = boundExpression;
161 setter = boundExpression.assign; 81 setter = boundExpression.assign;
82
162 _scope.rootScope.runAsync(() { 83 _scope.rootScope.runAsync(() {
163 _modelValue = boundExpression(); 84 _lastValue = modelValue;
164 _originalValue = modelValue;
165 _processViewValue(_modelValue);
166 }); 85 });
167 } 86 }
168 87
169 /** 88 // TODO(misko): right now viewValue and modelValue are the same,
170 * Applies the given [error] to the model. 89 // but this needs to be changed to support converters and form validation
171 */ 90 get viewValue => modelValue;
172 void addError(String error) { 91 set viewValue(value) => modelValue = value;
173 this.addErrorState(this, error); 92
174 } 93 get modelValue => getter();
94 set modelValue(value) => setter(value);
95
96 get validators => _validators;
175 97
176 /** 98 /**
177 * Removes the given [error] from the model. 99 * Executes a validation on the form against each of the validation present on the model.
178 */ 100 */
179 void removeError(String error) { 101 validate() {
180 this.removeErrorState(this, error); 102 if (validators.isNotEmpty) {
181 } 103 validators.forEach((validator) {
182 104 setValidity(validator.name, validator.isValid(viewValue));
183 /** 105 });
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 { 106 } else {
216 markAsDirty(); 107 valid = true;
217 } 108 }
218 } 109 }
219 110
220 /** 111 setValidity(String name, bool valid) {
221 * Returns the list of validators that are registered on the model. 112 this.updateControlValidity(this, name, valid);
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.
228 */
229 void validate() {
230 _toBeValidated = false;
231 if (validators.isNotEmpty) {
232 validators.forEach((validator) {
233 if (validator.isValid(modelValue)) {
234 removeError(validator.name);
235 } else {
236 addError(validator.name);
237 }
238 });
239 }
240
241 if (invalid) {
242 addInfo(NgControl.NG_INVALID);
243 } else {
244 removeInfo(NgControl.NG_INVALID);
245 }
246 } 113 }
247 114
248 /** 115 /**
249 * Registers a validator into the model to consider when running validate(). 116 * Registers a validator into the model to consider when running validate().
250 */ 117 */
251 void addValidator(NgValidator v) { 118 addValidator(NgValidatable v) {
252 validators.add(v); 119 validators.add(v);
253 validateLater(); 120 validate();
254 } 121 }
255 122
256 /** 123 /**
257 * De-registers a validator from the model. 124 * De-registers a validator from the model.
258 */ 125 */
259 void removeValidator(NgValidator v) { 126 removeValidator(NgValidatable v) {
260 validators.remove(v); 127 validators.remove(v);
261 validateLater(); 128 validate();
262 } 129 }
263 } 130 }
264 131
265 /** 132 /**
266 * Usage: 133 * Usage:
267 * 134 *
268 * <input type="checkbox" 135 * <input type="checkbox" ng-model="flag">
269 * ng-model="expr"
270 * [ng-true-value="t_expr"]
271 * [ng-false-value="f_expr"]
272 * >
273 * 136 *
274 * This creates a two way databinding between the `ng-model` expression 137 * This creates a two way databinding between the boolean expression specified
275 * and the checkbox input element state. 138 * in ng-model and the checkbox input element in the DOM.  If the ng-model value
276 * 139 * is falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is
277 * If the optional `ng-true-value` is absent then: if the model expression 140 * unchecked. Otherwise, it is checked.  Likewise, when the checkbox is checked,
278 * evaluates to true or to a nonzero [:num:], then the checkbox is checked; 141 * the model value is set to true. When unchecked, it is set to false.
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].
290 */ 142 */
291 @Decorator(selector: 'input[type=checkbox][ng-model]') 143 @NgDirective(selector: 'input[type=checkbox][ng-model]')
292 class InputCheckbox { 144 class InputCheckboxDirective {
293 final dom.CheckboxInputElement inputElement; 145 final dom.InputElement inputElement;
294 final NgModel ngModel; 146 final NgModel ngModel;
295 final NgTrueValue ngTrueValue; 147 final NgTrueValue ngTrueValue;
296 final NgFalseValue ngFalseValue; 148 final NgFalseValue ngFalseValue;
297 final Scope scope; 149 final Scope scope;
298 150
299 InputCheckbox(dom.Element this.inputElement, this.ngModel, 151 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel,
300 this.scope, this.ngTrueValue, this.ngFalseValue) { 152 this.scope, this.ngTrueValue, this.ngFalseValue) {
301 ngModel.render = (value) { 153 ngModel.render = (value) {
302 scope.rootScope.domWrite(() { 154 inputElement.checked = ngTrueValue.isValue(inputElement, value);
303 inputElement.checked = ngTrueValue.isValue(value);
304 });
305 }; 155 };
306 inputElement 156 inputElement.onChange.listen((value) {
307 ..onChange.listen((_) { 157 ngModel.dirty = true;
308 ngModel.viewValue = inputElement.checked 158 ngModel.viewValue = inputElement.checked
309 ? ngTrueValue.value : ngFalseValue.value; 159 ? ngTrueValue.readValue(inputElement)
310 }) 160 : ngFalseValue.readValue(inputElement);
311 ..onBlur.listen((e) { 161 });
312 ngModel.markAsTouched();
313 });
314 } 162 }
315 } 163 }
316 164
317 /** 165 /**
318 * Usage: 166 * Usage:
319 * 167 *
320 * <input type="text|url|password|email" ng-model="myModel"> 168 * <input type="text|url|password|email" ng-model="myModel">
321 * <textarea ng-model="myModel"></textarea> 169 * <textarea ng-model="myModel"></textarea>
322 * 170 *
323 * This creates a two-way binding between any string-based input element 171 * This creates a two-way binding between any string-based input element
324 * (both `<input>` and `<textarea>`) so long as the ng-model attribute is 172 * (both <input> and <textarea>) so long as the ng-model attribute is
325 * present on the input element. Whenever the value of the input element 173 * present on the input element. Whenever the value of the input element
326 * changes then the matching model property on the scope will be updated 174 * changes then the matching model property on the scope will be updated
327 * as well as the other way around (when the scope property is updated). 175 * as well as the other way around (when the scope property is updated).
328 * 176 *
329 */ 177 */
330 @Decorator(selector: 'textarea[ng-model]') 178 @NgDirective(selector: 'textarea[ng-model]')
331 @Decorator(selector: 'input[type=text][ng-model]') 179 @NgDirective(selector: 'input[type=text][ng-model]')
332 @Decorator(selector: 'input[type=password][ng-model]') 180 @NgDirective(selector: 'input[type=password][ng-model]')
333 @Decorator(selector: 'input[type=url][ng-model]') 181 @NgDirective(selector: 'input[type=url][ng-model]')
334 @Decorator(selector: 'input[type=email][ng-model]') 182 @NgDirective(selector: 'input[type=email][ng-model]')
335 @Decorator(selector: 'input[type=search][ng-model]') 183 @NgDirective(selector: 'input[type=search][ng-model]')
336 class InputTextLike { 184 class InputTextLikeDirective {
337 final dom.Element inputElement; 185 final dom.Element inputElement;
338 final NgModel ngModel; 186 final NgModel ngModel;
339 final Scope scope; 187 final Scope scope;
340 String _inputType; 188 String _inputType;
341 189
342 get typedValue => (inputElement as dynamic).value; 190 get typedValue => (inputElement as dynamic).value;
343 void set typedValue(value) { 191 set typedValue(value) => (inputElement as dynamic).value = (value == null) ?
344 (inputElement as dynamic).value = (value == null) ? '' : value.toString(); 192 '' :
345 } 193 value.toString();
346 194
347 InputTextLike(this.inputElement, this.ngModel, this.scope) { 195 InputTextLikeDirective(this.inputElement, this.ngModel, this.scope) {
348 ngModel.render = (value) { 196 ngModel.render = (value) {
349 scope.rootScope.domWrite(() { 197 if (value == null) value = '';
350 if (value == null) value = '';
351 198
352 var currentValue = typedValue; 199 var currentValue = typedValue;
353 if (value != currentValue && !(value is num && currentValue is num && 200 if (value != currentValue && !(value is num && currentValue is num &&
354 value.isNaN && currentValue.isNaN)) { 201 value.isNaN && currentValue.isNaN)) {
355 typedValue = value; 202 typedValue = value;
356 } 203 }
357 });
358 }; 204 };
359 inputElement 205 inputElement
360 ..onChange.listen(processValue) 206 ..onChange.listen(processValue)
361 ..onInput.listen(processValue) 207 ..onInput.listen(processValue)
362 ..onBlur.listen((e) { 208 ..onBlur.listen((e) {
363 ngModel.markAsTouched(); 209 if (ngModel.touched == null || ngModel.touched == false) {
210 ngModel.touched = true;
211 }
364 }); 212 });
365 } 213 }
366 214
367 void processValue([_]) { 215 processValue([_]) {
368 var value = typedValue; 216 var value = typedValue;
369 if (value != ngModel.viewValue) ngModel.viewValue = value; 217 if (value != ngModel.viewValue) {
218 ngModel.dirty = true;
219 ngModel.viewValue = value;
220 }
370 ngModel.validate(); 221 ngModel.validate();
371 } 222 }
372 } 223 }
373 224
374 /** 225 /**
375 * Usage: 226 * Usage:
376 * 227 *
377 * <input type="number|range" ng-model="myModel"> 228 * <input type="number|range" ng-model="myModel">
378 * 229 *
379 * Model: 230 * Model:
380 * 231 *
381 * num myModel; 232 * num myModel;
382 * 233 *
383 * This creates a two-way binding between the input and the named model property 234 * This creates a two-way binding between the input and the named model property
384 * (e.g., myModel in the example above). When processing the input, its value is 235 * (e.g., myModel in the example above). When processing the input, its value is
385 * read as a [:num:], via the [dom.InputElement.valueAsNumber] field. If the 236 * read as a [num], via the [dom.InputElement.valueAsNumber] field. If the input
386 * input text does not represent a number, then the model is appropriately set 237 * text does not represent a number, then the model is appropriately set to
387 * to [double.NAN]. Setting the model property to [null] will clear the input. 238 * [double.NAN]. Setting the model property to [null] will clear the input.
388 * Setting the model to [double.NAN] will have no effect (input will be left 239 * Setting the model to [double.NAN] will have no effect (input will be left
389 * unchanged). 240 * unchanged).
390 */ 241 */
391 @Decorator(selector: 'input[type=number][ng-model]') 242 @NgDirective(selector: 'input[type=number][ng-model]')
392 @Decorator(selector: 'input[type=range][ng-model]') 243 @NgDirective(selector: 'input[type=range][ng-model]')
393 class InputNumberLike { 244 class InputNumberLikeDirective {
394 final dom.InputElement inputElement; 245 final dom.InputElement inputElement;
395 final NgModel ngModel; 246 final NgModel ngModel;
396 final Scope scope; 247 final Scope scope;
397 248
398 249 num get typedValue => inputElement.valueAsNumber;
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
402 void set typedValue(num value) { 250 void set typedValue(num value) {
403 // [chalin, 2014-02-16] This post 251 // [chalin, 2014-02-16] This post
404 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-January/024829.h tml 252 // http://lists.whatwg.org/pipermail/whatwg-whatwg.org/2010-January/024829.h tml
405 // suggests that setting `valueAsNumber` to null should clear the field, but 253 // suggests that setting `valueAsNumber` to null should clear the field, but
406 // it does not. [TODO: put BUG/ISSUE number here]. We implement a 254 // it does not. [TODO: put BUG/ISSUE number here]. We implement a
407 // workaround by setting `value`. Clean-up once the bug is fixed. 255 // workaround by setting `value`. Clean-up once the bug is fixed.
408 if (value == null) { 256 if (value == null) {
409 inputElement.value = null; 257 inputElement.value = null;
410 } else { 258 } else {
411 // We can't use inputElement.valueAsNumber due to http://dartbug.com/15788 259 inputElement.valueAsNumber = value;
412 inputElement.value = "$value";
413 } 260 }
414 } 261 }
415 262
416 InputNumberLike(dom.Element this.inputElement, this.ngModel, this.scope) { 263 InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.sco pe) {
417 ngModel.render = (value) { 264 ngModel.render = (value) {
418 scope.rootScope.domWrite(() { 265 if (value != typedValue
419 if (value != typedValue 266 && (value == null || value is num && !value.isNaN)) {
420 && (value == null || value is num && !value.isNaN)) { 267 typedValue = value;
421 typedValue = value; 268 }
422 }
423 });
424 }; 269 };
425 inputElement 270 inputElement
426 ..onChange.listen(relaxFnArgs(processValue)) 271 ..onChange.listen(relaxFnArgs(processValue))
427 ..onInput.listen(relaxFnArgs(processValue)) 272 ..onInput.listen(relaxFnArgs(processValue));
428 ..onBlur.listen((e) {
429 ngModel.markAsTouched();
430 });
431 } 273 }
432 274
433 void processValue() { 275 processValue() {
434 num value = typedValue; 276 num value = typedValue;
435 if (value != ngModel.viewValue) { 277 if (value != ngModel.viewValue) {
278 ngModel.dirty = true;
436 scope.eval(() => ngModel.viewValue = value); 279 scope.eval(() => ngModel.viewValue = value);
437 } 280 }
438 ngModel.validate(); 281 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();
622 } 282 }
623 } 283 }
624 284
625 class _UidCounter { 285 class _UidCounter {
626 static final int CHAR_0 = "0".codeUnitAt(0); 286 static final int CHAR_0 = "0".codeUnitAt(0);
627 static final int CHAR_9 = "9".codeUnitAt(0); 287 static final int CHAR_9 = "9".codeUnitAt(0);
628 static final int CHAR_A = "A".codeUnitAt(0); 288 static final int CHAR_A = "A".codeUnitAt(0);
629 static final int CHAR_Z = "Z".codeUnitAt(0); 289 static final int CHAR_Z = "Z".codeUnitAt(0);
630 final charCodes = [CHAR_0, CHAR_0, CHAR_0]; 290 List charCodes = [CHAR_0, CHAR_0, CHAR_0];
631 291
632 String next() { 292 String next() {
633 for (int i = charCodes.length - 1; i >= 0; i--) { 293 for (int i = charCodes.length - 1; i >= 0; i--) {
634 int code = charCodes[i]; 294 int code = charCodes[i];
635 if (code == CHAR_9) { 295 if (code == CHAR_9) {
636 charCodes[i] = CHAR_A; 296 charCodes[i] = CHAR_A;
637 return new String.fromCharCodes(charCodes); 297 return new String.fromCharCodes(charCodes);
638 } else if (code == CHAR_Z) { 298 } else if (code == CHAR_Z) {
639 charCodes[i] = CHAR_0; 299 charCodes[i] = CHAR_0;
640 } else { 300 } else {
641 charCodes[i] = code + 1; 301 charCodes[i] = code + 1;
642 return new String.fromCharCodes(charCodes); 302 return new String.fromCharCodes(charCodes);
643 } 303 }
644 } 304 }
645 charCodes.insert(0, CHAR_0); 305 charCodes.insert(0, CHAR_0);
646 return new String.fromCharCodes(charCodes); 306 return new String.fromCharCodes(charCodes);
647 } 307 }
648 } 308 }
649 309
650 final _uidCounter = new _UidCounter(); 310 final _uidCounter = new _UidCounter();
651 311
652 /** 312 /**
653 * Usage: 313 * Use `ng-value` directive with `<input type="radio">` or `<option>` to
654 * 314 * allow binding to values other then strings. This is needed since the
655 * <input type=radio ng-model=model [ng-value=expr]> 315 * `value` attribute on DOM element `<input type="radio" value="foo">` can
656 * 316 * only be a string. With `ng-value` one can bind to any object.
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].
669 */ 317 */
670 @Decorator(selector: 'input[type=radio][ng-model][ng-value]') 318 @NgDirective(selector: '[ng-value]')
671 @Decorator(selector: 'option[ng-value]')
672 class NgValue { 319 class NgValue {
673 static Module _module = new Module()..type(NgValue);
674 static Module moduleFactory() => _module;
675
676 final dom.Element element; 320 final dom.Element element;
677 var _value; 321 @NgOneWay('ng-value')
322 var value;
678 323
679 NgValue(this.element); 324 NgValue(this.element);
680 325
681 @NgOneWay('ng-value') 326 readValue(dom.Element element) {
682 void set value(val) { this._value = val; } 327 assert(this.element == null || element == this.element);
683 dynamic get value => _value == null ? (element as dynamic).value : _value; 328 return this.element == null ? (element as dynamic).value : value;
329 }
684 } 330 }
685 331
686 /** 332 /**
687 * Usage: 333 * `ng-true-value` allows you to select any expression to be set to
688 * 334 * `ng-model` when checkbox is selected on `<input type="checkbox">`.
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].
696 */ 335 */
697 @Decorator(selector: 'input[type=checkbox][ng-model][ng-true-value]') 336 @NgDirective(selector: '[ng-true-value]')
698 class NgTrueValue { 337 class NgTrueValue {
699 final dom.Element element; 338 final dom.Element element;
700 @NgOneWay('ng-true-value') 339 @NgOneWay('ng-true-value')
701 var value = true; 340 var value;
702 341
703 NgTrueValue([this.element]); 342 NgTrueValue(this.element);
704 343
705 bool isValue(val) => element == null ? toBool(val) : val == value; 344 readValue(dom.Element element) {
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 }
706 } 353 }
707 354
708 /** 355 /**
709 * Usage: 356 * `ng-false-value` allows you to select any expression to be set to
710 * 357 * `ng-model` when checkbox is deselected<input type="checkbox">`.
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].
718 */ 358 */
719 @Decorator(selector: 'input[type=checkbox][ng-model][ng-false-value]') 359 @NgDirective(selector: '[ng-false-value]')
720 class NgFalseValue { 360 class NgFalseValue {
721 final dom.Element element; 361 final dom.Element element;
722 @NgOneWay('ng-false-value') 362 @NgOneWay('ng-false-value')
723 var value = false; 363 var value;
724 364
725 NgFalseValue([this.element]); 365 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 }
726 } 371 }
727 372
728 /** 373 /**
729 * Usage: 374 * Usage:
730 * 375 *
731 * <input type="radio" ng-model="category"> 376 * <input type="radio" ng-model="category">
732 * 377 *
733 * This creates a two way databinding between the expression specified in 378 * This creates a two way databinding between the expression specified in
734 * ng-model and the range input elements in the DOM. If the ng-model value is 379 * ng-model and the range input elements in the DOM.  If the ng-model value is
735 * set to a value not corresponding to one of the radio elements, then none of 380 * set to a value not corresponding to one of the radio elements, then none of
736 * the radio elements will be check. Otherwise, only the corresponding input 381 * the radio elements will be check. Otherwise, only the corresponding input
737 * element in the group is checked. Likewise, when a radio button element is 382 * element in the group is checked. Likewise, when a radio button element is
738 * checked, the model is updated with its value. Radio buttons that have a 383 * checked, the model is updated with its value. Radio buttons that have a
739 * `name` attribute are left alone. Those that are missing the attribute will 384 * `name` attribute are left alone. Those that are missing the attribute will
740 * have a unique `name` assigned to them. This sequence goes `001`, `001`, ... 385 * have a unique `name` assigned to them. This sequence goes `001`, `001`, ...
741 * `009`, `00A`, `00Z`, `010`, and so on using more than 3 characters for the 386 * `009`, `00A`, `00Z`, `010`, and so on using more than 3 characters for the
742 * name when the counter overflows. 387 * name when the counter overflows.
743 */ 388 */
744 @Decorator( 389 @NgDirective(selector: 'input[type=radio][ng-model]')
745 selector: 'input[type=radio][ng-model]', 390 class InputRadioDirective {
746 module: NgValue.moduleFactory)
747 class InputRadio {
748 final dom.RadioButtonInputElement radioButtonElement; 391 final dom.RadioButtonInputElement radioButtonElement;
749 final NgModel ngModel; 392 final NgModel ngModel;
750 final NgValue ngValue; 393 final NgValue ngValue;
751 final Scope scope; 394 final Scope scope;
752 395
753 InputRadio(dom.Element this.radioButtonElement, this.ngModel, 396 InputRadioDirective(dom.Element this.radioButtonElement, this.ngModel,
754 this.scope, this.ngValue, NodeAttrs attrs) { 397 this.scope, this.ngValue, NodeAttrs attrs) {
755 // If there's no "name" set, we'll set a unique name. This ensures 398 // If there's no "name" set, we'll set a unique name. This ensures
756 // less surprising behavior about which radio buttons are grouped together. 399 // less surprising behavior about which radio buttons are grouped together.
757 if (attrs['name'] == '' || attrs['name'] == null) { 400 if (attrs['name'] == '' || attrs['name'] == null) {
758 attrs["name"] = _uidCounter.next(); 401 attrs["name"] = _uidCounter.next();
759 } 402 }
760 ngModel.render = (value) { 403 ngModel.render = (value) {
761 scope.rootScope.domWrite(() { 404 radioButtonElement.checked = (value == ngValue.readValue(radioButtonElemen t));
762 radioButtonElement.checked = (value == ngValue.value);
763 });
764 }; 405 };
765 radioButtonElement 406 radioButtonElement.onClick.listen((_) {
766 ..onClick.listen((_) { 407 if (radioButtonElement.checked) {
767 if (radioButtonElement.checked) ngModel.viewValue = ngValue.value; 408 ngModel.dirty = true;
768 }) 409 ngModel.viewValue = ngValue.readValue(radioButtonElement);
769 ..onBlur.listen((e) { 410 }
770 ngModel.markAsTouched(); 411 });
771 });
772 } 412 }
773 } 413 }
774 414
775 /** 415 /**
776 * Usage (span could be replaced with any element which supports text content, s uch as `p`): 416 * Usage (span could be replaced with any element which supports text content, s uch as `p`):
777 * 417 *
778 * <span contenteditable= ng-model="name"> 418 * <span contenteditable= ng-model="name">
779 * 419 *
780 * This creates a two way databinding between the expression specified in 420 * This creates a two way databinding between the expression specified in
781 * ng-model and the html element in the DOM. If the ng-model value is 421 * ng-model and the html element in the DOM.  If the ng-model value is
782 * `null`, it is treated as equivalent to the empty string for rendering 422 * `null`, it is treated as equivalent to the empty string for rendering
783 * purposes. 423 * purposes.
784 */ 424 */
785 @Decorator(selector: '[contenteditable][ng-model]') 425 @NgDirective(selector: '[contenteditable][ng-model]')
786 class ContentEditable extends InputTextLike { 426 class ContentEditableDirective extends InputTextLikeDirective {
787 ContentEditable(dom.Element inputElement, NgModel ngModel, Scope scope) 427 ContentEditableDirective(dom.Element inputElement, NgModel ngModel,
428 Scope scope)
788 : super(inputElement, ngModel, scope); 429 : super(inputElement, ngModel, scope);
789 430
790 // The implementation is identical to InputTextLike but use innerHtml instead of value 431 // The implementation is identical to InputTextLikeDirective but use innerHtml instead of value
791 String get typedValue => (inputElement as dynamic).innerHtml; 432 get typedValue => (inputElement as dynamic).innerHtml;
792 void set typedValue(String value) { 433 set typedValue(String value) =>
793 (inputElement as dynamic).innerHtml = (value == null) ? '' : value; 434 (inputElement as dynamic).innerHtml = (value == null) ? '' : value;
794 }
795 } 435 }
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