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