| OLD | NEW |
| 1 part of angular.directive; | 1 part of angular.directive; |
| 2 | 2 |
| 3 typedef dynamic ItemEval(dynamic item, num index); |
| 4 |
| 3 /** | 5 /** |
| 4 * HTML [SELECT] element with angular data-binding if used with | 6 * HTML [SELECT] element with angular data-binding if used with |
| 5 * [NgModel]. | 7 * [NgModelDirective]. |
| 6 * | 8 * |
| 7 * The [NgModel] will receive the currently selected item. The binding | 9 * The [NgModelDirective] will receive the currently selected item. The binding |
| 8 * is performed on the [OPTION].[value] property. An empty [OPTION].[value] is | 10 * is performed on the [OPTION].[value] property. An empty [OPTION].[value] is |
| 9 * treated as null. | 11 * treated as null. |
| 10 * | 12 * |
| 11 * If you the model contains value which does not map to any [OPTION] then a new | 13 * If you the model contains value which does not map to any [OPTION] then a new |
| 12 * unknown [OPTION] is inserted into the list. Once the model points to an | 14 * unknown [OPTION] is inserted into the list. Once the model points to an |
| 13 * existing [OPTION] the unknown [OPTION] is removed. | 15 * existing [OPTION] the unknown [OPTION] is removed. |
| 14 * | 16 * |
| 15 * Because [OPTION].[value] attribute is a string, the model is bound to a | 17 * Becouse [OPTION].[value] attribute is a string, the model is bound to a |
| 16 * string. If there is need to bind to an object then [OptionValue] | 18 * string. If there is need to bind to an object then [OptionValueDirective] |
| 17 * should be used. | 19 * should be used. |
| 18 * | 20 * |
| 19 */ | 21 */ |
| 20 @Decorator( | 22 @NgDirective( |
| 21 selector: 'select[ng-model]') | 23 selector: 'select[ng-model]', |
| 22 class InputSelect implements AttachAware { | 24 visibility: NgDirective.CHILDREN_VISIBILITY) |
| 23 final expando = new Expando<OptionValue>(); | 25 class InputSelectDirective implements NgAttachAware { |
| 26 final Expando<OptionValueDirective> expando = |
| 27 new Expando<OptionValueDirective>(); |
| 24 final dom.SelectElement _selectElement; | 28 final dom.SelectElement _selectElement; |
| 25 final NodeAttrs _attrs; | 29 final NodeAttrs _attrs; |
| 26 final NgModel _model; | 30 final NgModel _model; |
| 27 final Scope _scope; | 31 final Scope _scope; |
| 28 | 32 |
| 29 final dom.OptionElement _unknownOption = new dom.OptionElement(); | 33 final dom.OptionElement _unknownOption = new dom.OptionElement(); |
| 30 dom.OptionElement _nullOption; | 34 dom.OptionElement _nullOption; |
| 31 | 35 |
| 32 _SelectMode _mode = new _SelectMode(null, null, null); | 36 _SelectMode _mode = new _SelectMode(null, null, null); |
| 33 bool _dirty = false; | 37 bool _dirty = false; |
| 34 | 38 |
| 35 InputSelect(dom.Element this._selectElement, this._attrs, this._model, | 39 InputSelectDirective(dom.Element this._selectElement, this._attrs, this._model
, |
| 36 this._scope) { | 40 this._scope) { |
| 37 _unknownOption.value = '?'; | 41 _unknownOption.value = '?'; |
| 38 _nullOption = _selectElement.querySelectorAll('option') | 42 _unknownOption.text = ''; // Explicit due to dartbug.com/14407 |
| 39 .firstWhere((o) => o.value == '', orElse: () => null); | 43 _selectElement.querySelectorAll('option').forEach((o) { |
| 44 if (_nullOption == null && o.value == '') { |
| 45 _nullOption = o; |
| 46 } |
| 47 }); |
| 40 } | 48 } |
| 41 | 49 |
| 42 attach() { | 50 attach() { |
| 43 _attrs.observe('multiple', (value) { | 51 _attrs.observe('multiple', (value) { |
| 44 _mode.destroy(); | 52 _mode.destroy(); |
| 45 if (value == null) { | 53 if (value == null) { |
| 46 _model.watchCollection = false; | 54 _model.watchCollection = false; |
| 47 _mode = new _SingleSelectMode(expando, _selectElement, _model, | 55 _mode = new _SingleSelectMode(expando, _selectElement, _model, _nullOpti
on, _unknownOption); |
| 48 _nullOption, _unknownOption); | |
| 49 } else { | 56 } else { |
| 50 _model.watchCollection = true; | 57 _model.watchCollection = true; |
| 51 _mode = new _MultipleSelectionMode(expando, _selectElement, _model); | 58 _mode = new _MultipleSelectionMode(expando, _selectElement, _model); |
| 52 } | 59 } |
| 53 _mode.onModelChange(_model.viewValue); | 60 _mode.onModelChange(_model.viewValue); |
| 54 }); | 61 }); |
| 55 | 62 |
| 56 _selectElement.onChange.listen((event) => _mode.onViewChange(event)); | 63 _selectElement.onChange.listen((event) => _mode.onViewChange(event)); |
| 57 _model.render = (value) { | 64 _model.render = (value) { |
| 58 // TODO(misko): this hack need to delay the rendering until after domRead | 65 // TODO(misko): this hack need to delay the rendering until after domRead |
| 59 // because the modelChange reads from the DOM. We should be able to render | 66 // because the modelChange reads from the DOM. We should be able to render |
| 60 // without DOM changes. | 67 // without DOM changes. |
| 61 _scope.rootScope.domRead(() { | 68 _scope.rootScope.domRead(() { |
| 62 _scope.rootScope.domWrite(() => _mode.onModelChange(value)); | 69 _scope.rootScope.domWrite(() => _mode.onModelChange(value)); |
| 63 }); | 70 }); |
| 64 }; | 71 }; |
| 65 } | 72 } |
| 66 | 73 |
| 67 /** | 74 /** |
| 68 * This method invalidates the current state of the selector and forces a | 75 * This method invalidates the current state of the selector and forces a |
| 69 * re-rendering of the options using the [Scope.evalAsync]. | 76 * re-rendering of the options using the [Scope.evalAsync]. |
| 70 */ | 77 */ |
| 71 dirty() { | 78 dirty() { |
| 72 if (!_dirty) { | 79 if (!_dirty) { |
| 73 _dirty = true; | 80 _dirty = true; |
| 74 // TODO(misko): this hack need to delay the rendering until after domRead | 81 // TODO(misko): this hack need to delay the rendering until after domRead |
| 75 // because the modelChange reads from the DOM. We should be able to render | 82 // becouse the modelChange reads from the DOM. We should be able to render |
| 76 // without DOM changes. | 83 // without DOM changes. |
| 77 _scope.rootScope.domRead(() { | 84 _scope.rootScope.domRead(() { |
| 78 _scope.rootScope.domWrite(() { | 85 _scope.rootScope.domWrite(() { |
| 79 _dirty = false; | 86 _dirty = false; |
| 80 _mode.onModelChange(_model.viewValue); | 87 _mode.onModelChange(_model.viewValue); |
| 81 }); | 88 }); |
| 82 }); | 89 }); |
| 83 } | 90 } |
| 84 } | 91 } |
| 85 } | 92 } |
| 86 | 93 |
| 87 /** | 94 /** |
| 88 * Since the [value] attribute of the [OPTION] can only be a string, Angular | 95 * Since the [value] attribute of the [OPTION] can only be a string, Angular |
| 89 * provides [ng-value] which allows binding to any expression. | 96 * provides [ng-value] which allows binding to any expression. |
| 90 * | 97 * |
| 91 */ | 98 */ |
| 92 @Decorator(selector: 'option', module: NgValue.moduleFactory) | 99 @NgDirective( |
| 93 class OptionValue implements AttachAware, | 100 selector: 'option') |
| 94 DetachAware { | 101 class OptionValueDirective implements NgAttachAware, |
| 95 final InputSelect _inputSelectDirective; | 102 NgDetachAware { |
| 103 final InputSelectDirective _inputSelectDirective; |
| 96 final dom.Element _element; | 104 final dom.Element _element; |
| 97 | 105 |
| 98 NgValue _ngValue; | 106 NgValue _ngValue; |
| 99 | 107 |
| 100 OptionValue(this._element, this._inputSelectDirective, this._ngValue) { | 108 OptionValueDirective(this._element, this._inputSelectDirective, this._ngValue)
{ |
| 101 if (_inputSelectDirective != null) { | 109 if (_inputSelectDirective != null) { |
| 102 _inputSelectDirective.expando[_element] = this; | 110 _inputSelectDirective.expando[_element] = this; |
| 103 } | 111 } |
| 104 } | 112 } |
| 105 | 113 |
| 106 attach() { | 114 attach() { |
| 107 if (_inputSelectDirective != null) _inputSelectDirective.dirty(); | 115 if (_inputSelectDirective != null) { |
| 116 _inputSelectDirective.dirty(); |
| 117 } |
| 108 } | 118 } |
| 109 | 119 |
| 110 detach() { | 120 detach() { |
| 111 if (_inputSelectDirective != null) { | 121 if (_inputSelectDirective != null) { |
| 112 _inputSelectDirective.dirty(); | 122 _inputSelectDirective.dirty(); |
| 113 _inputSelectDirective.expando[_element] = null; | 123 _inputSelectDirective.expando[_element] = null; |
| 114 } | 124 } |
| 115 } | 125 } |
| 116 | 126 |
| 117 get ngValue => _ngValue.value; | 127 get ngValue => _ngValue.readValue(_element); |
| 118 } | 128 } |
| 119 | 129 |
| 120 class _SelectMode { | 130 class _SelectMode { |
| 121 final Expando<OptionValue> expando; | 131 final Expando<OptionValueDirective> expando; |
| 122 final dom.SelectElement select; | 132 final dom.SelectElement select; |
| 123 final NgModel model; | 133 final NgModel model; |
| 124 | 134 |
| 125 _SelectMode(this.expando, this.select, this.model); | 135 _SelectMode(this.expando, this.select, this.model); |
| 126 | 136 |
| 127 onViewChange(event) {} | 137 onViewChange(event) {} |
| 128 onModelChange(value) {} | 138 onModelChange(value) {} |
| 129 destroy() {} | 139 destroy() {} |
| 130 | 140 |
| 131 get _options => select.querySelectorAll('option'); | 141 get _options => select.querySelectorAll('option'); |
| 132 _forEachOption(fn, [quiteOnReturn = false]) { | 142 _forEachOption(fn, [quiteOnReturn = false]) { |
| 133 for (var i = 0; i < _options.length; i++) { | 143 for (var i = 0; i < _options.length; i++) { |
| 134 var retValue = fn(_options[i], i); | 144 var retValue = fn(_options[i], i); |
| 135 if (quiteOnReturn && retValue != null) return retValue; | 145 if (quiteOnReturn && retValue != null) return retValue; |
| 136 } | 146 } |
| 137 return null; | 147 return null; |
| 138 } | 148 } |
| 139 } | 149 } |
| 140 | 150 |
| 141 class _SingleSelectMode extends _SelectMode { | 151 class _SingleSelectMode extends _SelectMode { |
| 142 final dom.OptionElement _unknownOption; | 152 final dom.OptionElement _unknownOption; |
| 143 final dom.OptionElement _nullOption; | 153 final dom.OptionElement _nullOption; |
| 144 | 154 |
| 145 bool _unknownOptionActive = false; | 155 bool _unknownOptionActive = false; |
| 146 | 156 |
| 147 _SingleSelectMode(Expando<OptionValue> expando, | 157 _SingleSelectMode(Expando<OptionValueDirective> expando, |
| 148 dom.SelectElement select, | 158 dom.SelectElement select, |
| 149 NgModel model, | 159 NgModel model, |
| 150 this._nullOption, | 160 this._nullOption, |
| 151 this._unknownOption) | 161 this._unknownOption |
| 152 : super(expando, select, model) { | 162 ): super(expando, select, model) { |
| 153 } | 163 } |
| 154 | 164 |
| 155 onViewChange(event) { | 165 onViewChange(event) { |
| 156 var i = 0; | 166 var i = 0; |
| 157 model.viewValue = _forEachOption((option, _) { | 167 model.viewValue = _forEachOption((option, _) { |
| 158 if (option.selected) { | 168 if (option.selected) { |
| 159 if (option == _nullOption) return null; | 169 return option == _nullOption ? null : expando[option].ngValue; |
| 160 assert(expando[option] != null); | |
| 161 return expando[option].ngValue; | |
| 162 } | 170 } |
| 163 if (option != _unknownOption && option != _nullOption) i++; | 171 if (option != _unknownOption && option != _nullOption) i++; |
| 164 }, true); | 172 }, true); |
| 165 } | 173 } |
| 166 | 174 |
| 167 onModelChange(value) { | 175 onModelChange(value) { |
| 168 var found = false; | 176 var found = false; |
| 169 _forEachOption((option, i) { | 177 _forEachOption((option, i) { |
| 170 if (option == _unknownOption) return; | 178 if (option == _unknownOption) return; |
| 171 var selected; | 179 var selected; |
| 172 if (value == null) { | 180 if (value == null) { |
| 173 selected = option == _nullOption; | 181 selected = option == _nullOption; |
| 174 } else { | 182 } else { |
| 175 OptionValue optionValueDirective = expando[option]; | 183 OptionValueDirective optionValueDirective = expando[option]; |
| 176 selected = optionValueDirective == null ? | 184 selected = optionValueDirective == null ? false : optionValueDirective.n
gValue == value; |
| 177 false : | |
| 178 optionValueDirective.ngValue == value; | |
| 179 } | 185 } |
| 180 found = found || selected; | 186 found = found || selected; |
| 181 option.selected = selected; | 187 option.selected = selected; |
| 182 }); | 188 }); |
| 183 | 189 |
| 184 if (!found) { | 190 if (!found) { |
| 185 if (!_unknownOptionActive) { | 191 if (!_unknownOptionActive) { |
| 186 select.insertBefore(_unknownOption, select.firstChild); | 192 select.insertBefore(_unknownOption, select.firstChild); |
| 187 _unknownOption.selected = true; | 193 _unknownOption.selected = true; |
| 188 _unknownOptionActive = true; | 194 _unknownOptionActive = true; |
| 189 } | 195 } |
| 190 } else { | 196 } else { |
| 191 if (_unknownOptionActive) { | 197 if (_unknownOptionActive) { |
| 192 _unknownOption.remove(); | 198 _unknownOption.remove(); |
| 193 _unknownOptionActive = false; | 199 _unknownOptionActive = false; |
| 194 } | 200 } |
| 195 } | 201 } |
| 196 } | 202 } |
| 197 } | 203 } |
| 198 | 204 |
| 199 class _MultipleSelectionMode extends _SelectMode { | 205 class _MultipleSelectionMode extends _SelectMode { |
| 200 _MultipleSelectionMode(Expando<OptionValue> expando, | 206 _MultipleSelectionMode(Expando<OptionValueDirective> expando, |
| 201 dom.SelectElement select, | 207 dom.SelectElement select, |
| 202 NgModel model) | 208 NgModel model) |
| 203 : super(expando, select, model); | 209 : super(expando, select, model); |
| 204 | 210 |
| 205 onViewChange(event) { | 211 onViewChange(event) { |
| 206 var selected = []; | 212 var selected = []; |
| 207 | 213 |
| 208 _forEachOption((o, i) { | 214 _forEachOption((o, i) { |
| 209 if (o.selected) selected.add(expando[o].ngValue); | 215 if (o.selected) selected.add(expando[o].ngValue); |
| 210 }); | 216 }); |
| 211 model.viewValue = selected; | 217 model.viewValue = selected; |
| 212 } | 218 } |
| 213 | 219 |
| 214 onModelChange(List selectedValues) { | 220 onModelChange(List selectedValues) { |
| 215 Function fn = (o, i) => o.selected = null; | 221 Function fn = (o, i) => o.selected = null; |
| 216 | 222 |
| 217 if (selectedValues is List) { | 223 if (selectedValues is List) { |
| 218 fn = (o, i) { | 224 fn = (o, i) { |
| 219 var selected = expando[o]; | 225 var selected = expando[o]; |
| 220 return selected == null ? | 226 if (selected == null) { |
| 221 false : | 227 return false; |
| 222 o.selected = selectedValues.contains(selected.ngValue); | 228 } else { |
| 229 return o.selected = selectedValues.contains(selected.ngValue); |
| 230 } |
| 223 }; | 231 }; |
| 224 } | 232 } |
| 225 | 233 |
| 226 _forEachOption(fn); | 234 _forEachOption(fn); |
| 227 } | 235 } |
| 228 } | 236 } |
| OLD | NEW |