Index: pkg/template_binding/lib/src/input_bindings.dart |
diff --git a/pkg/template_binding/lib/src/input_bindings.dart b/pkg/template_binding/lib/src/input_bindings.dart |
index 06b744fba63386ccc3f211a8284646b72a087c82..6292527d4b3ae3ffe588a116f94d5c3905c1bcc6 100644 |
--- a/pkg/template_binding/lib/src/input_bindings.dart |
+++ b/pkg/template_binding/lib/src/input_bindings.dart |
@@ -4,21 +4,79 @@ |
part of template_binding; |
-abstract class _InputBinding extends NodeBinding { |
+ |
+// Note: the JavaScript version monkeypatches(!!) the close method of the passed |
+// in Bindable. We use a wrapper instead. |
+class _InputBinding extends Bindable { |
+ // Note: node can be an InputElement or TextAreaElement. Both have "value". |
+ var _node; |
StreamSubscription _eventSub; |
+ Bindable _bindable; |
+ String _propertyName; |
- _InputBinding(node, name, model, path): super(node, name, model, path) { |
- _eventSub = _getStreamForInputType(node).listen(nodeValueChanged); |
+ _InputBinding(this._node, this._bindable, this._propertyName) { |
+ _eventSub = _getStreamForInputType(_node).listen(_nodeChanged); |
+ _updateNode(open(_updateNode)); |
} |
- void valueChanged(newValue); |
+ void _updateNode(newValue) => _updateProperty(_node, newValue, _propertyName); |
+ |
+ static void _updateProperty(node, newValue, String propertyName) { |
+ switch (propertyName) { |
+ case 'checked': |
+ node.checked = _toBoolean(newValue); |
+ return; |
+ case 'selectedIndex': |
+ node.selectedIndex = _toInt(newValue); |
+ return; |
+ case 'value': |
+ node.value = _sanitizeValue(newValue); |
+ return; |
+ } |
+ } |
- void nodeValueChanged(e); |
+ void _nodeChanged(e) { |
+ switch (_propertyName) { |
+ case 'value': |
+ value = _node.value; |
+ break; |
+ case 'checked': |
+ value = _node.checked; |
+ |
+ // Only the radio button that is getting checked gets an event. We |
+ // therefore find all the associated radio buttons and update their |
+ // checked binding manually. |
+ if (_node is InputElement && _node.type == 'radio') { |
+ for (var r in _getAssociatedRadioButtons(_node)) { |
+ var checkedBinding = nodeBind(r).bindings['checked']; |
+ if (checkedBinding != null) { |
+ // Set the value directly to avoid an infinite call stack. |
+ checkedBinding.value = false; |
+ } |
+ } |
+ } |
+ break; |
+ case 'selectedIndex': |
+ value = _node.selectedIndex; |
+ break; |
+ } |
+ |
+ Observable.dirtyCheck(); |
+ } |
+ |
+ open(callback(value)) => _bindable.open(callback); |
+ get value => _bindable.value; |
+ set value(newValue) => _bindable.value = newValue; |
void close() { |
- if (closed) return; |
- _eventSub.cancel(); |
- super.close(); |
+ if (_eventSub != null) { |
+ _eventSub.cancel(); |
+ _eventSub = null; |
+ } |
+ if (_bindable != null) { |
+ _bindable.close(); |
+ _bindable = null; |
+ } |
} |
static EventStreamProvider<Event> _checkboxEventType = () { |
@@ -53,51 +111,6 @@ abstract class _InputBinding extends NodeBinding { |
return element.onInput; |
} |
} |
-} |
- |
-class _ValueBinding extends _InputBinding { |
- _ValueBinding(node, model, path) : super(node, 'value', model, path); |
- |
- get node => super.node; |
- |
- void valueChanged(newValue) { |
- // Note: node can be an InputElement or TextAreaElement. Both have "value". |
- node.value = sanitizeBoundValue(newValue); |
- } |
- |
- void nodeValueChanged(e) { |
- value = node.value; |
- Observable.dirtyCheck(); |
- } |
-} |
- |
-class _CheckedBinding extends _InputBinding { |
- _CheckedBinding(node, model, path) : super(node, 'checked', model, path); |
- |
- InputElement get node => super.node; |
- |
- void valueChanged(newValue) { |
- node.checked = _toBoolean(newValue); |
- } |
- |
- void nodeValueChanged(e) { |
- value = node.checked; |
- |
- // Only the radio button that is getting checked gets an event. We |
- // therefore find all the associated radio buttons and update their |
- // CheckedBinding manually. |
- if (node is InputElement && node.type == 'radio') { |
- for (var r in _getAssociatedRadioButtons(node)) { |
- var checkedBinding = nodeBind(r).bindings['checked']; |
- if (checkedBinding != null) { |
- // Set the value directly to avoid an infinite call stack. |
- checkedBinding.value = false; |
- } |
- } |
- } |
- |
- Observable.dirtyCheck(); |
- } |
// |element| is assumed to be an HTMLInputElement with |type| == 'radio'. |
// Returns an array containing all radio buttons other than |element| that |
@@ -125,63 +138,6 @@ class _CheckedBinding extends _InputBinding { |
return radios.where((el) => el != element && el.form == null); |
} |
} |
-} |
- |
-class _SelectBinding extends _InputBinding { |
- MutationObserver _onMutation; |
- |
- _SelectBinding(node, property, model, path) |
- : super(node, property, model, path); |
- |
- SelectElement get node => super.node; |
- |
- void valueChanged(newValue) { |
- _cancelMutationObserver(); |
- |
- if (_tryUpdateValue(newValue)) return; |
- |
- // It could be that a template will expand an <option> child (or grandchild, |
- // if we have an <optgroup> in between). Since selected index cannot be set |
- // if the children aren't created yet, we need to wait for them to be |
- // created do this with a MutationObserver. |
- // Dart note: unlike JS we use mutation observers to avoid: |
- // https://github.com/Polymer/NodeBind/issues/5 |
- |
- // Note: it doesn't matter when the children get added; even if they get |
- // added much later, presumably we want the selected index data binding to |
- // still take effect. |
- _onMutation = new MutationObserver((x, y) { |
- if (_tryUpdateValue(value)) _cancelMutationObserver(); |
- })..observe(node, childList: true, subtree: true); |
- } |
- |
- bool _tryUpdateValue(newValue) { |
- if (property == 'selectedIndex') { |
- var intValue = _toInt(newValue); |
- node.selectedIndex = intValue; |
- return node.selectedIndex == intValue; |
- } else if (property == 'value') { |
- node.value = sanitizeBoundValue(newValue); |
- return node.value == newValue; |
- } |
- } |
- |
- void _cancelMutationObserver() { |
- if (_onMutation != null) { |
- _onMutation.disconnect(); |
- _onMutation = null; |
- } |
- } |
- |
- void nodeValueChanged(e) { |
- _cancelMutationObserver(); |
- |
- if (property == 'selectedIndex') { |
- value = node.selectedIndex; |
- } else if (property == 'value') { |
- value = node.value; |
- } |
- } |
// TODO(jmesserly,sigmund): I wonder how many bindings typically convert from |
// one type to another (e.g. value-as-number) and whether it is useful to |