| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of template_binding; | |
| 6 | |
| 7 | |
| 8 // Note: the JavaScript version monkeypatches(!!) the close method of the passed | |
| 9 // in Bindable. We use a wrapper instead. | |
| 10 class _InputBinding extends Bindable { | |
| 11 // Note: node can be an InputElement or TextAreaElement. Both have "value". | |
| 12 var _node; | |
| 13 StreamSubscription _eventSub; | |
| 14 Bindable _bindable; | |
| 15 String _propertyName; | |
| 16 | |
| 17 _InputBinding(this._node, this._bindable, this._propertyName) { | |
| 18 _eventSub = _getStreamForInputType(_node).listen(_nodeChanged); | |
| 19 _updateNode(open(_updateNode)); | |
| 20 } | |
| 21 | |
| 22 void _updateNode(newValue) => _updateProperty(_node, newValue, _propertyName); | |
| 23 | |
| 24 static void _updateProperty(node, newValue, String propertyName) { | |
| 25 switch (propertyName) { | |
| 26 case 'checked': | |
| 27 node.checked = _toBoolean(newValue); | |
| 28 return; | |
| 29 case 'selectedIndex': | |
| 30 node.selectedIndex = _toInt(newValue); | |
| 31 return; | |
| 32 case 'value': | |
| 33 node.value = _sanitizeValue(newValue); | |
| 34 return; | |
| 35 } | |
| 36 } | |
| 37 | |
| 38 void _nodeChanged(e) { | |
| 39 switch (_propertyName) { | |
| 40 case 'value': | |
| 41 value = _node.value; | |
| 42 break; | |
| 43 case 'checked': | |
| 44 value = _node.checked; | |
| 45 | |
| 46 // Only the radio button that is getting checked gets an event. We | |
| 47 // therefore find all the associated radio buttons and update their | |
| 48 // checked binding manually. | |
| 49 if (_node is InputElement && _node.type == 'radio') { | |
| 50 for (var r in _getAssociatedRadioButtons(_node)) { | |
| 51 var checkedBinding = nodeBind(r).bindings['checked']; | |
| 52 if (checkedBinding != null) { | |
| 53 // Set the value directly to avoid an infinite call stack. | |
| 54 checkedBinding.value = false; | |
| 55 } | |
| 56 } | |
| 57 } | |
| 58 break; | |
| 59 case 'selectedIndex': | |
| 60 value = _node.selectedIndex; | |
| 61 break; | |
| 62 } | |
| 63 | |
| 64 Observable.dirtyCheck(); | |
| 65 } | |
| 66 | |
| 67 open(callback(value)) => _bindable.open(callback); | |
| 68 get value => _bindable.value; | |
| 69 set value(newValue) => _bindable.value = newValue; | |
| 70 | |
| 71 void close() { | |
| 72 if (_eventSub != null) { | |
| 73 _eventSub.cancel(); | |
| 74 _eventSub = null; | |
| 75 } | |
| 76 if (_bindable != null) { | |
| 77 _bindable.close(); | |
| 78 _bindable = null; | |
| 79 } | |
| 80 } | |
| 81 | |
| 82 static EventStreamProvider<Event> _checkboxEventType = () { | |
| 83 // Attempt to feature-detect which event (change or click) is fired first | |
| 84 // for checkboxes. | |
| 85 var div = new DivElement(); | |
| 86 var checkbox = div.append(new InputElement()); | |
| 87 checkbox.type = 'checkbox'; | |
| 88 var fired = []; | |
| 89 checkbox.onClick.listen((e) { | |
| 90 fired.add(Element.clickEvent); | |
| 91 }); | |
| 92 checkbox.onChange.listen((e) { | |
| 93 fired.add(Element.changeEvent); | |
| 94 }); | |
| 95 checkbox.dispatchEvent(new MouseEvent('click', view: window)); | |
| 96 // WebKit/Blink don't fire the change event if the element is outside the | |
| 97 // document, so assume 'change' for that case. | |
| 98 return fired.length == 1 ? Element.changeEvent : fired.first; | |
| 99 }(); | |
| 100 | |
| 101 static Stream<Event> _getStreamForInputType(element) { | |
| 102 if (element is OptionElement) return element.onInput; | |
| 103 switch (element.type) { | |
| 104 case 'checkbox': | |
| 105 return _checkboxEventType.forTarget(element); | |
| 106 case 'radio': | |
| 107 case 'select-multiple': | |
| 108 case 'select-one': | |
| 109 return element.onChange; | |
| 110 case 'range': | |
| 111 if (window.navigator.userAgent.contains(new RegExp('Trident|MSIE'))) { | |
| 112 return element.onChange; | |
| 113 } | |
| 114 } | |
| 115 return element.onInput; | |
| 116 } | |
| 117 | |
| 118 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 119 // Returns an array containing all radio buttons other than |element| that | |
| 120 // have the same |name|, either in the form that |element| belongs to or, | |
| 121 // if no form, in the document tree to which |element| belongs. | |
| 122 // | |
| 123 // This implementation is based upon the HTML spec definition of a | |
| 124 // "radio button group": | |
| 125 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
| 126 // | |
| 127 static Iterable _getAssociatedRadioButtons(element) { | |
| 128 if (element.form != null) { | |
| 129 return element.form.nodes.where((el) { | |
| 130 return el != element && | |
| 131 el is InputElement && | |
| 132 el.type == 'radio' && | |
| 133 el.name == element.name; | |
| 134 }); | |
| 135 } else { | |
| 136 var treeScope = _getTreeScope(element); | |
| 137 if (treeScope == null) return const []; | |
| 138 | |
| 139 var radios = treeScope.querySelectorAll( | |
| 140 'input[type="radio"][name="${element.name}"]'); | |
| 141 return radios.where((el) => el != element && el.form == null); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from | |
| 146 // one type to another (e.g. value-as-number) and whether it is useful to | |
| 147 // have something like a int/num binding converter (either as a base class or | |
| 148 // a wrapper). | |
| 149 static int _toInt(value) { | |
| 150 if (value is String) return int.parse(value, onError: (_) => 0); | |
| 151 return value is int ? value : 0; | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 _getTreeScope(Node node) { | |
| 156 Node parent; | |
| 157 while ((parent = node.parentNode) != null ) { | |
| 158 node = parent; | |
| 159 } | |
| 160 | |
| 161 return _hasGetElementById(node) ? node : null; | |
| 162 } | |
| 163 | |
| 164 // Note: JS code tests that getElementById is present. We can't do that | |
| 165 // easily, so instead check for the types known to implement it. | |
| 166 bool _hasGetElementById(Node node) => | |
| 167 node is Document || node is ShadowRoot || node is SvgSvgElement; | |
| OLD | NEW |