| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of template_binding; | 5 part of template_binding; |
| 6 | 6 |
| 7 abstract class _InputBinding extends NodeBinding { | 7 abstract class _InputBinding extends NodeBinding { |
| 8 StreamSubscription _eventSub; | 8 StreamSubscription _eventSub; |
| 9 | 9 |
| 10 _InputBinding(node, name, model, path): super(node, name, model, path) { | 10 _InputBinding(node, name, model, path): super(node, name, model, path) { |
| (...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 121 if (treeScope == null) return const []; | 121 if (treeScope == null) return const []; |
| 122 | 122 |
| 123 var radios = treeScope.querySelectorAll( | 123 var radios = treeScope.querySelectorAll( |
| 124 'input[type="radio"][name="${element.name}"]'); | 124 'input[type="radio"][name="${element.name}"]'); |
| 125 return radios.where((el) => el != element && el.form == null); | 125 return radios.where((el) => el != element && el.form == null); |
| 126 } | 126 } |
| 127 } | 127 } |
| 128 } | 128 } |
| 129 | 129 |
| 130 class _SelectBinding extends _InputBinding { | 130 class _SelectBinding extends _InputBinding { |
| 131 MutationObserver _onMutation; |
| 132 |
| 131 _SelectBinding(node, property, model, path) | 133 _SelectBinding(node, property, model, path) |
| 132 : super(node, property, model, path); | 134 : super(node, property, model, path); |
| 133 | 135 |
| 134 SelectElement get node => super.node; | 136 SelectElement get node => super.node; |
| 135 | 137 |
| 136 void valueChanged(newValue) { | 138 void valueChanged(newValue) { |
| 139 _cancelMutationObserver(); |
| 140 |
| 137 if (_tryUpdateValue(newValue)) return; | 141 if (_tryUpdateValue(newValue)) return; |
| 138 | 142 |
| 139 // The binding may wish to bind to an <option> which has not yet been | 143 // It could be that a template will expand an <option> child (or grandchild, |
| 140 // produced by a child <template>. Furthermore, we may need to wait for | 144 // if we have an <optgroup> in between). Since selected index cannot be set |
| 141 // <optgroup> iterating and then for <option>. | 145 // if the children aren't created yet, we need to wait for them to be |
| 142 // | 146 // created do this with a MutationObserver. |
| 143 // Unlike the JavaScript implemenation, we don't have a special | 147 // Dart note: unlike JS we use mutation observers to avoid: |
| 144 // "Object.observe" event loop to schedule on. | 148 // https://github.com/Polymer/NodeBind/issues/5 |
| 145 // (See the the "ensureScheduled" function: | |
| 146 // https://github.com/Polymer/mdv/commit/9a51ad7ed74a292bf71662cea28acbd151f
f65c8) | |
| 147 // | |
| 148 // Instead we use scheduleMicrotask. Each <template repeat> needs a delay of | |
| 149 // 2: | |
| 150 // * once to happen after the child _TemplateIterator is created | |
| 151 // * once to be after _TemplateIterator's CompoundPathObserver resolve | |
| 152 // And then we need to do this delay sequence twice: | |
| 153 // * once for OPTGROUP | |
| 154 // * once for OPTION. | |
| 155 // The resulting 2 * 2 is our maxRetries. | |
| 156 // | |
| 157 // TODO(jmesserly): a much better approach would be to find the nested | |
| 158 // <template> and wait on some future that completes when it has expanded. | |
| 159 var maxRetries = 4; | |
| 160 delaySetSelectedIndex() { | |
| 161 if (!_tryUpdateValue(newValue) && maxRetries-- > 0) { | |
| 162 scheduleMicrotask(delaySetSelectedIndex); | |
| 163 } | |
| 164 } | |
| 165 | 149 |
| 166 scheduleMicrotask(delaySetSelectedIndex); | 150 // Note: it doesn't matter when the children get added; even if they get |
| 151 // added much later, presumably we want the selected index data binding to |
| 152 // still take effect. |
| 153 _onMutation = new MutationObserver((x, y) { |
| 154 if (_tryUpdateValue(value)) _cancelMutationObserver(); |
| 155 })..observe(node, childList: true, subtree: true); |
| 167 } | 156 } |
| 168 | 157 |
| 169 bool _tryUpdateValue(newValue) { | 158 bool _tryUpdateValue(newValue) { |
| 170 if (property == 'selectedIndex') { | 159 if (property == 'selectedIndex') { |
| 171 var intValue = _toInt(newValue); | 160 var intValue = _toInt(newValue); |
| 172 node.selectedIndex = intValue; | 161 node.selectedIndex = intValue; |
| 173 return node.selectedIndex == intValue; | 162 return node.selectedIndex == intValue; |
| 174 } else if (property == 'value') { | 163 } else if (property == 'value') { |
| 175 node.value = sanitizeBoundValue(newValue); | 164 node.value = sanitizeBoundValue(newValue); |
| 176 return node.value == newValue; | 165 return node.value == newValue; |
| 177 } | 166 } |
| 178 } | 167 } |
| 179 | 168 |
| 169 void _cancelMutationObserver() { |
| 170 if (_onMutation != null) { |
| 171 _onMutation.disconnect(); |
| 172 _onMutation = null; |
| 173 } |
| 174 } |
| 175 |
| 180 void nodeValueChanged(e) { | 176 void nodeValueChanged(e) { |
| 177 _cancelMutationObserver(); |
| 178 |
| 181 if (property == 'selectedIndex') { | 179 if (property == 'selectedIndex') { |
| 182 value = node.selectedIndex; | 180 value = node.selectedIndex; |
| 183 } else if (property == 'value') { | 181 } else if (property == 'value') { |
| 184 value = node.value; | 182 value = node.value; |
| 185 } | 183 } |
| 186 } | 184 } |
| 187 | 185 |
| 188 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from | 186 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from |
| 189 // one type to another (e.g. value-as-number) and whether it is useful to | 187 // one type to another (e.g. value-as-number) and whether it is useful to |
| 190 // have something like a int/num binding converter (either as a base class or | 188 // have something like a int/num binding converter (either as a base class or |
| 191 // a wrapper). | 189 // a wrapper). |
| 192 static int _toInt(value) { | 190 static int _toInt(value) { |
| 193 if (value is String) return int.parse(value, onError: (_) => 0); | 191 if (value is String) return int.parse(value, onError: (_) => 0); |
| 194 return value is int ? value : 0; | 192 return value is int ? value : 0; |
| 195 } | 193 } |
| 196 } | 194 } |
| OLD | NEW |