| OLD | NEW |
| 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 * 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 |
| 11 * (to be implemented) | 11 * (to be implemented) |
| 12 */ | 12 */ |
| 13 @NgDirective( | 13 @NgDirective(selector: '[ng-model]') |
| 14 selector: '[ng-model]') | 14 class NgModel extends NgControl implements NgAttachAware { |
| 15 class NgModel extends NgControl { | |
| 16 final NgForm _form; | 15 final NgForm _form; |
| 17 final dom.Element _element; | 16 final AstParser _parser; |
| 18 final Scope _scope; | |
| 19 | 17 |
| 20 Getter getter = ([_]) => null; | 18 BoundGetter getter = ([_]) => null; |
| 21 Setter setter = (_, [__]) => null; | 19 BoundSetter setter = (_, [__]) => null; |
| 22 | 20 |
| 21 var _lastValue; |
| 23 String _exp; | 22 String _exp; |
| 24 String _name; | 23 final _validators = <NgValidatable>[]; |
| 25 | 24 |
| 26 final List<_NgModelValidator> _validators = new List<_NgModelValidator>(); | 25 Watch _removeWatch; |
| 27 final Map<String, bool> currentErrors = new Map<String, bool>(); | |
| 28 | |
| 29 Function _removeWatch = () => null; | |
| 30 bool _watchCollection; | 26 bool _watchCollection; |
| 31 | |
| 32 Function render = (value) => null; | 27 Function render = (value) => null; |
| 33 | 28 |
| 34 NgModel(this._scope, NodeAttrs attrs, [dom.Element this._element, NgForm this.
_form]) { | 29 NgModel(Scope _scope, dom.Element _element, Injector injector, |
| 35 _exp = 'ng-model=${attrs["ng-model"]}'; | 30 NgForm this._form, this._parser, NodeAttrs attrs) |
| 31 : super(_scope, _element, injector) |
| 32 { |
| 33 _exp = attrs["ng-model"]; |
| 36 watchCollection = false; | 34 watchCollection = false; |
| 37 | |
| 38 _form.addControl(this); | |
| 39 pristine = true; | |
| 40 } | 35 } |
| 41 | 36 |
| 42 get element => _element; | 37 process(value, [_]) { |
| 38 validate(); |
| 39 _scope.rootScope.domWrite(() => render(value)); |
| 40 } |
| 41 |
| 42 attach() { |
| 43 watchCollection = false; |
| 44 _scope.on('resetNgModel').listen((e) => reset()); |
| 45 } |
| 46 |
| 47 reset() { |
| 48 untouched = true; |
| 49 modelValue = _lastValue; |
| 50 } |
| 43 | 51 |
| 44 @NgAttr('name') | 52 @NgAttr('name') |
| 45 get name => _name; | 53 get name => _name; |
| 46 set name(value) { | 54 set name(value) { |
| 47 _name = value; | 55 _name = value; |
| 48 _form.addControl(this); | 56 _parentControl.addControl(this); |
| 49 } | 57 } |
| 50 | 58 |
| 59 // TODO(misko): could we get rid of watch collection, and just always watch th
e collection? |
| 51 get watchCollection => _watchCollection; | 60 get watchCollection => _watchCollection; |
| 52 set watchCollection(value) { | 61 set watchCollection(value) { |
| 53 if (_watchCollection == value) return; | 62 if (_watchCollection == value) return; |
| 54 _watchCollection = value; | 63 _watchCollection = value; |
| 55 _removeWatch(); | 64 if (_removeWatch!=null) _removeWatch.remove(); |
| 56 if (_watchCollection) { | 65 if (_watchCollection) { |
| 57 _removeWatch = _scope.$watchCollection((s) => getter(), (value) => render(
value), _exp); | 66 _removeWatch = _scope.watch( |
| 58 } else { | 67 _parser(_exp, collection: true), |
| 59 _removeWatch = _scope.$watch((s) => getter(), (value) => render(value), _e
xp); | 68 (changeRecord, _) { |
| 69 var value = changeRecord is CollectionChangeRecord ? changeRecord.it
erable: changeRecord; |
| 70 process(value); |
| 71 }); |
| 72 } else if (_exp != null) { |
| 73 _removeWatch = _scope.watch(_exp, process); |
| 60 } | 74 } |
| 61 } | 75 } |
| 62 | 76 |
| 77 // TODO(misko): getters/setters need to go. We need AST here. |
| 63 @NgCallback('ng-model') | 78 @NgCallback('ng-model') |
| 64 set model(BoundExpression boundExpression) { | 79 set model(BoundExpression boundExpression) { |
| 65 getter = boundExpression; | 80 getter = boundExpression; |
| 66 setter = boundExpression.assign; | 81 setter = boundExpression.assign; |
| 82 |
| 83 _scope.rootScope.runAsync(() { |
| 84 _lastValue = modelValue; |
| 85 }); |
| 67 } | 86 } |
| 68 | 87 |
| 69 // TODO(misko): right now viewValue and modelValue are the same, | 88 // TODO(misko): right now viewValue and modelValue are the same, |
| 70 // but this needs to be changed to support converters and form validation | 89 // but this needs to be changed to support converters and form validation |
| 71 get viewValue => modelValue; | 90 get viewValue => modelValue; |
| 72 set viewValue(value) => modelValue = value; | 91 set viewValue(value) => modelValue = value; |
| 73 | 92 |
| 74 get modelValue => getter(); | 93 get modelValue => getter(); |
| 75 set modelValue(value) => setter(value); | 94 set modelValue(value) => setter(value); |
| 76 | 95 |
| 77 get validators => _validators; | 96 get validators => _validators; |
| 78 | 97 |
| 79 /** | 98 /** |
| 80 * Executes a validation on the form against each of the validation present on
the model. | 99 * Executes a validation on the form against each of the validation present on
the model. |
| 81 */ | 100 */ |
| 82 validate() { | 101 validate() { |
| 83 if(validators.length > 0) { | 102 if (validators.isNotEmpty) { |
| 84 validators.forEach((validator) { | 103 validators.forEach((validator) { |
| 85 setValidity(validator.name, validator.isValid()); | 104 setValidity(validator.name, validator.isValid(viewValue)); |
| 86 }); | 105 }); |
| 87 } else { | 106 } else { |
| 88 valid = true; | 107 valid = true; |
| 89 } | 108 } |
| 90 } | 109 } |
| 91 | 110 |
| 92 /** | 111 setValidity(String name, bool valid) { |
| 93 * Sets the validity status of the given errorType on the model. Depending on
if | 112 this.updateControlValidity(this, name, valid); |
| 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 } | 113 } |
| 118 | 114 |
| 119 /** | 115 /** |
| 120 * Registers a validator into the model to consider when running validate(). | 116 * Registers a validator into the model to consider when running validate(). |
| 121 */ | 117 */ |
| 122 addValidator(_NgModelValidator v) { | 118 addValidator(NgValidatable v) { |
| 123 validators.add(v); | 119 validators.add(v); |
| 124 validate(); | 120 validate(); |
| 125 } | 121 } |
| 126 | 122 |
| 127 /** | 123 /** |
| 128 * De-registers a validator from the model. | 124 * De-registers a validator from the model. |
| 129 */ | 125 */ |
| 130 removeValidator(_NgModelValidator v) { | 126 removeValidator(NgValidatable v) { |
| 131 validators.remove(v); | 127 validators.remove(v); |
| 132 validate(); | 128 validate(); |
| 133 } | 129 } |
| 134 | |
| 135 /** | |
| 136 * Removes the model from the control/form. | |
| 137 */ | |
| 138 destroy() { | |
| 139 _form.removeControl(this); | |
| 140 } | |
| 141 } | 130 } |
| 142 | 131 |
| 143 /** | 132 /** |
| 144 * Usage: | 133 * Usage: |
| 145 * | 134 * |
| 146 * <input type="checkbox" ng-model="flag"> | 135 * <input type="checkbox" ng-model="flag"> |
| 147 * | 136 * |
| 148 * This creates a two way databinding between the boolean expression specified i
n | 137 * This creates a two way databinding between the boolean expression specified |
| 149 * ng-model and the checkbox input element in the DOM. If the ng-model value is | 138 * in ng-model and the checkbox input element in the DOM. If the ng-model value |
| 150 * falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is unchecked. | 139 * is falsy (i.e. one of `false`, `null`, and `0`), then the checkbox is |
| 151 * Otherwise, it is checked. Likewise, when the checkbox is checked, the model | 140 * unchecked. Otherwise, it is checked. Likewise, when the checkbox is checked, |
| 152 * value is set to true. When unchecked, it is set to false. | 141 * the model value is set to true. When unchecked, it is set to false. |
| 153 * | |
| 154 * The AngularJS style ng-true-value / ng-false-value is not supported. | |
| 155 */ | 142 */ |
| 156 @NgDirective(selector: 'input[type=checkbox][ng-model]') | 143 @NgDirective(selector: 'input[type=checkbox][ng-model]') |
| 157 class InputCheckboxDirective { | 144 class InputCheckboxDirective { |
| 158 dom.InputElement inputElement; | 145 final dom.InputElement inputElement; |
| 159 NgModel ngModel; | 146 final NgModel ngModel; |
| 160 Scope scope; | 147 final NgTrueValue ngTrueValue; |
| 148 final NgFalseValue ngFalseValue; |
| 149 final Scope scope; |
| 161 | 150 |
| 162 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, this.scope
) { | 151 InputCheckboxDirective(dom.Element this.inputElement, this.ngModel, |
| 152 this.scope, this.ngTrueValue, this.ngFalseValue) { |
| 163 ngModel.render = (value) { | 153 ngModel.render = (value) { |
| 164 inputElement.checked = value == null ? false : toBool(value); | 154 inputElement.checked = ngTrueValue.isValue(inputElement, value); |
| 165 }; | 155 }; |
| 166 inputElement.onChange.listen((value) { | 156 inputElement.onChange.listen((value) { |
| 167 scope.$apply(() => ngModel.viewValue = inputElement.checked); | 157 ngModel.dirty = true; |
| 158 ngModel.viewValue = inputElement.checked |
| 159 ? ngTrueValue.readValue(inputElement) |
| 160 : ngFalseValue.readValue(inputElement); |
| 168 }); | 161 }); |
| 169 } | 162 } |
| 170 } | 163 } |
| 171 | 164 |
| 172 /** | 165 /** |
| 173 * Usage: | 166 * Usage: |
| 174 * | 167 * |
| 175 * <input type="text|number|url|password|email" ng-model="myModel"> | 168 * <input type="text|url|password|email" ng-model="myModel"> |
| 176 * <textarea ng-model="myModel"></textarea> | 169 * <textarea ng-model="myModel"></textarea> |
| 177 * | 170 * |
| 178 * 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 |
| 179 * (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 |
| 180 * 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 |
| 181 * 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 |
| 182 * 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). |
| 183 * | 176 * |
| 184 */ | 177 */ |
| 185 @NgDirective(selector: 'textarea[ng-model]') | 178 @NgDirective(selector: 'textarea[ng-model]') |
| 186 @NgDirective(selector: 'input[type=text][ng-model]') | 179 @NgDirective(selector: 'input[type=text][ng-model]') |
| 187 @NgDirective(selector: 'input[type=password][ng-model]') | 180 @NgDirective(selector: 'input[type=password][ng-model]') |
| 188 @NgDirective(selector: 'input[type=url][ng-model]') | 181 @NgDirective(selector: 'input[type=url][ng-model]') |
| 189 @NgDirective(selector: 'input[type=email][ng-model]') | 182 @NgDirective(selector: 'input[type=email][ng-model]') |
| 190 @NgDirective(selector: 'input[type=number][ng-model]') | 183 @NgDirective(selector: 'input[type=search][ng-model]') |
| 191 class InputTextLikeDirective { | 184 class InputTextLikeDirective { |
| 192 dom.Element inputElement; | 185 final dom.Element inputElement; |
| 193 NgModel ngModel; | 186 final NgModel ngModel; |
| 194 Scope scope; | 187 final Scope scope; |
| 195 String _inputType; | 188 String _inputType; |
| 196 | 189 |
| 197 get typedValue => (inputElement as dynamic).value; | 190 get typedValue => (inputElement as dynamic).value; |
| 198 set typedValue(value) => (inputElement as dynamic).value = (value == null) ? '
' : value.toString(); | 191 set typedValue(value) => (inputElement as dynamic).value = (value == null) ? |
| 192 '' : |
| 193 value.toString(); |
| 199 | 194 |
| 200 InputTextLikeDirective(dom.Element this.inputElement, NgModel this.ngModel, Sc
ope this.scope) { | 195 InputTextLikeDirective(this.inputElement, this.ngModel, this.scope) { |
| 201 ngModel.render = (value) { | 196 ngModel.render = (value) { |
| 202 if (value == null) value = ''; | 197 if (value == null) value = ''; |
| 203 | 198 |
| 204 var currentValue = typedValue; | 199 var currentValue = typedValue; |
| 205 if (value != currentValue && !(value is num && currentValue is num && valu
e.isNaN && currentValue.isNaN)) { | 200 if (value != currentValue && !(value is num && currentValue is num && |
| 201 value.isNaN && currentValue.isNaN)) { |
| 206 typedValue = value; | 202 typedValue = value; |
| 207 } | 203 } |
| 208 }; | 204 }; |
| 209 inputElement.onChange.listen(relaxFnArgs(processValue)); | 205 inputElement |
| 210 inputElement.onKeyDown.listen((e) { | 206 ..onChange.listen(processValue) |
| 211 new async.Timer(Duration.ZERO, processValue); | 207 ..onInput.listen(processValue) |
| 212 scope.$skipAutoDigest(); | 208 ..onBlur.listen((e) { |
| 213 }); | 209 if (ngModel.touched == null || ngModel.touched == false) { |
| 210 ngModel.touched = true; |
| 211 } |
| 212 }); |
| 213 } |
| 214 |
| 215 processValue([_]) { |
| 216 var value = typedValue; |
| 217 if (value != ngModel.viewValue) { |
| 218 ngModel.dirty = true; |
| 219 ngModel.viewValue = value; |
| 220 } |
| 221 ngModel.validate(); |
| 222 } |
| 223 } |
| 224 |
| 225 /** |
| 226 * Usage: |
| 227 * |
| 228 * <input type="number|range" ng-model="myModel"> |
| 229 * |
| 230 * Model: |
| 231 * |
| 232 * num myModel; |
| 233 * |
| 234 * 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 |
| 236 * read as a [num], via the [dom.InputElement.valueAsNumber] field. If the input |
| 237 * text does not represent a number, then the model is appropriately set to |
| 238 * [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 |
| 240 * unchanged). |
| 241 */ |
| 242 @NgDirective(selector: 'input[type=number][ng-model]') |
| 243 @NgDirective(selector: 'input[type=range][ng-model]') |
| 244 class InputNumberLikeDirective { |
| 245 final dom.InputElement inputElement; |
| 246 final NgModel ngModel; |
| 247 final Scope scope; |
| 248 |
| 249 num get typedValue => inputElement.valueAsNumber; |
| 250 void set typedValue(num value) { |
| 251 // [chalin, 2014-02-16] This post |
| 252 // 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 |
| 254 // it does not. [TODO: put BUG/ISSUE number here]. We implement a |
| 255 // workaround by setting `value`. Clean-up once the bug is fixed. |
| 256 if (value == null) { |
| 257 inputElement.value = null; |
| 258 } else { |
| 259 inputElement.valueAsNumber = value; |
| 260 } |
| 261 } |
| 262 |
| 263 InputNumberLikeDirective(dom.Element this.inputElement, this.ngModel, this.sco
pe) { |
| 264 ngModel.render = (value) { |
| 265 if (value != typedValue |
| 266 && (value == null || value is num && !value.isNaN)) { |
| 267 typedValue = value; |
| 268 } |
| 269 }; |
| 270 inputElement |
| 271 ..onChange.listen(relaxFnArgs(processValue)) |
| 272 ..onInput.listen(relaxFnArgs(processValue)); |
| 214 } | 273 } |
| 215 | 274 |
| 216 processValue() { | 275 processValue() { |
| 276 num value = typedValue; |
| 277 if (value != ngModel.viewValue) { |
| 278 ngModel.dirty = true; |
| 279 scope.eval(() => ngModel.viewValue = value); |
| 280 } |
| 217 ngModel.validate(); | 281 ngModel.validate(); |
| 218 var value = typedValue; | |
| 219 if (value != ngModel.viewValue) { | |
| 220 scope.$apply(() => ngModel.viewValue = value); | |
| 221 } | |
| 222 } | 282 } |
| 223 } | 283 } |
| 224 | 284 |
| 225 class _UidCounter { | 285 class _UidCounter { |
| 226 static final int CHAR_0 = "0".codeUnitAt(0); | 286 static final int CHAR_0 = "0".codeUnitAt(0); |
| 227 static final int CHAR_9 = "9".codeUnitAt(0); | 287 static final int CHAR_9 = "9".codeUnitAt(0); |
| 228 static final int CHAR_A = "A".codeUnitAt(0); | 288 static final int CHAR_A = "A".codeUnitAt(0); |
| 229 static final int CHAR_Z = "Z".codeUnitAt(0); | 289 static final int CHAR_Z = "Z".codeUnitAt(0); |
| 230 List charCodes = [CHAR_0, CHAR_0, CHAR_0]; | 290 List charCodes = [CHAR_0, CHAR_0, CHAR_0]; |
| 231 | 291 |
| 232 String next() { | 292 String next() { |
| 233 for (int i = charCodes.length-1; i >= 0; i--) { | 293 for (int i = charCodes.length - 1; i >= 0; i--) { |
| 234 int code = charCodes[i]; | 294 int code = charCodes[i]; |
| 235 if (code == CHAR_9) { | 295 if (code == CHAR_9) { |
| 236 charCodes[i] = CHAR_A; | 296 charCodes[i] = CHAR_A; |
| 237 return new String.fromCharCodes(charCodes); | 297 return new String.fromCharCodes(charCodes); |
| 238 } else if (code == CHAR_Z) { | 298 } else if (code == CHAR_Z) { |
| 239 charCodes[i] = CHAR_0; | 299 charCodes[i] = CHAR_0; |
| 240 } else { | 300 } else { |
| 241 charCodes[i] = code + 1; | 301 charCodes[i] = code + 1; |
| 242 return new String.fromCharCodes(charCodes); | 302 return new String.fromCharCodes(charCodes); |
| 243 } | 303 } |
| 244 } | 304 } |
| 245 charCodes.insert(0, CHAR_0); | 305 charCodes.insert(0, CHAR_0); |
| 246 return new String.fromCharCodes(charCodes); | 306 return new String.fromCharCodes(charCodes); |
| 247 } | 307 } |
| 248 } | 308 } |
| 249 | 309 |
| 250 final _uidCounter = new _UidCounter(); | 310 final _uidCounter = new _UidCounter(); |
| 251 | 311 |
| 312 /** |
| 313 * Use `ng-value` directive with `<input type="radio">` or `<option>` to |
| 314 * allow binding to values other then strings. This is needed since the |
| 315 * `value` attribute on DOM element `<input type="radio" value="foo">` can |
| 316 * only be a string. With `ng-value` one can bind to any object. |
| 317 */ |
| 318 @NgDirective(selector: '[ng-value]') |
| 319 class NgValue { |
| 320 final dom.Element element; |
| 321 @NgOneWay('ng-value') |
| 322 var value; |
| 323 |
| 324 NgValue(this.element); |
| 325 |
| 326 readValue(dom.Element element) { |
| 327 assert(this.element == null || element == this.element); |
| 328 return this.element == null ? (element as dynamic).value : value; |
| 329 } |
| 330 } |
| 331 |
| 332 /** |
| 333 * `ng-true-value` allows you to select any expression to be set to |
| 334 * `ng-model` when checkbox is selected on `<input type="checkbox">`. |
| 335 */ |
| 336 @NgDirective(selector: '[ng-true-value]') |
| 337 class NgTrueValue { |
| 338 final dom.Element element; |
| 339 @NgOneWay('ng-true-value') |
| 340 var value; |
| 341 |
| 342 NgTrueValue(this.element); |
| 343 |
| 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 } |
| 353 } |
| 354 |
| 355 /** |
| 356 * `ng-false-value` allows you to select any expression to be set to |
| 357 * `ng-model` when checkbox is deselected<input type="checkbox">`. |
| 358 */ |
| 359 @NgDirective(selector: '[ng-false-value]') |
| 360 class NgFalseValue { |
| 361 final dom.Element element; |
| 362 @NgOneWay('ng-false-value') |
| 363 var value; |
| 364 |
| 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 } |
| 371 } |
| 252 | 372 |
| 253 /** | 373 /** |
| 254 * Usage: | 374 * Usage: |
| 255 * | 375 * |
| 256 * <input type="radio" ng-model="category"> | 376 * <input type="radio" ng-model="category"> |
| 257 * | 377 * |
| 258 * This creates a two way databinding between the expression specified in | 378 * This creates a two way databinding between the expression specified in |
| 259 * 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 |
| 260 * 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 |
| 261 * the radio elements will be check. Otherwise, only the corresponding input | 381 * the radio elements will be check. Otherwise, only the corresponding input |
| 262 * 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 |
| 263 * 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 |
| 264 * `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 |
| 265 * 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`, ... |
| 266 * `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 |
| 267 * name when the counter overflows. | 387 * name when the counter overflows. |
| 268 */ | 388 */ |
| 269 @NgDirective(selector: 'input[type=radio][ng-model]') | 389 @NgDirective(selector: 'input[type=radio][ng-model]') |
| 270 class InputRadioDirective { | 390 class InputRadioDirective { |
| 271 dom.RadioButtonInputElement radioButtonElement; | 391 final dom.RadioButtonInputElement radioButtonElement; |
| 272 NgModel ngModel; | 392 final NgModel ngModel; |
| 273 Scope scope; | 393 final NgValue ngValue; |
| 394 final Scope scope; |
| 274 | 395 |
| 275 InputRadioDirective(dom.Element this.radioButtonElement, this.ngModel, | 396 InputRadioDirective(dom.Element this.radioButtonElement, this.ngModel, |
| 276 this.scope, NodeAttrs attrs) { | 397 this.scope, this.ngValue, NodeAttrs attrs) { |
| 277 // 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 |
| 278 // less surprising behavior about which radio buttons are grouped together. | 399 // less surprising behavior about which radio buttons are grouped together. |
| 279 if (attrs['name'] == '' || attrs['name'] == null) { | 400 if (attrs['name'] == '' || attrs['name'] == null) { |
| 280 attrs["name"] = _uidCounter.next(); | 401 attrs["name"] = _uidCounter.next(); |
| 281 } | 402 } |
| 282 ngModel.render = (String value) { | 403 ngModel.render = (value) { |
| 283 radioButtonElement.checked = (value == radioButtonElement.value); | 404 radioButtonElement.checked = (value == ngValue.readValue(radioButtonElemen
t)); |
| 284 }; | 405 }; |
| 285 radioButtonElement.onClick.listen((_) { | 406 radioButtonElement.onClick.listen((_) { |
| 286 if (radioButtonElement.checked) { | 407 if (radioButtonElement.checked) { |
| 287 scope.$apply(() => ngModel.viewValue = radioButtonElement.value); | 408 ngModel.dirty = true; |
| 409 ngModel.viewValue = ngValue.readValue(radioButtonElement); |
| 288 } | 410 } |
| 289 }); | 411 }); |
| 290 } | 412 } |
| 291 } | 413 } |
| 292 | 414 |
| 293 /** | 415 /** |
| 294 * 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`): |
| 295 * | 417 * |
| 296 * <span contenteditable= ng-model="name"> | 418 * <span contenteditable= ng-model="name"> |
| 297 * | 419 * |
| 298 * This creates a two way databinding between the expression specified in | 420 * 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 | 421 * 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 | 422 * `null`, it is treated as equivalent to the empty string for rendering |
| 301 * purposes. | 423 * purposes. |
| 302 */ | 424 */ |
| 303 @NgDirective(selector: '[contenteditable][ng-model]') | 425 @NgDirective(selector: '[contenteditable][ng-model]') |
| 304 class ContentEditableDirective extends InputTextLikeDirective { | 426 class ContentEditableDirective extends InputTextLikeDirective { |
| 305 ContentEditableDirective(dom.Element inputElement, NgModel ngModel, Scope scop
e): | 427 ContentEditableDirective(dom.Element inputElement, NgModel ngModel, |
| 306 super(inputElement, ngModel, scope); | 428 Scope scope) |
| 429 : super(inputElement, ngModel, scope); |
| 307 | 430 |
| 308 // The implementation is identical to InputTextLikeDirective but use innerHtml
instead of value | 431 // The implementation is identical to InputTextLikeDirective but use innerHtml
instead of value |
| 309 get typedValue => (inputElement as dynamic).innerHtml; | 432 get typedValue => (inputElement as dynamic).innerHtml; |
| 310 set typedValue(String value) => (inputElement as dynamic).innerHtml = (value =
= null) ? '' : value; | 433 set typedValue(String value) => |
| 434 (inputElement as dynamic).innerHtml = (value == null) ? '' : value; |
| 311 } | 435 } |
| OLD | NEW |