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