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