| 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 mdv; | |
| 6 | |
| 7 abstract class _InputBinding extends NodeBinding { | |
| 8 StreamSubscription _eventSub; | |
| 9 | |
| 10 _InputBinding(node, name, model, path): super(node, name, model, path) { | |
| 11 _eventSub = _getStreamForInputType(node).listen(nodeValueChanged); | |
| 12 } | |
| 13 | |
| 14 void boundValueChanged(newValue); | |
| 15 | |
| 16 void nodeValueChanged(e); | |
| 17 | |
| 18 void close() { | |
| 19 if (closed) return; | |
| 20 _eventSub.cancel(); | |
| 21 super.close(); | |
| 22 } | |
| 23 | |
| 24 static EventStreamProvider<Event> _checkboxEventType = () { | |
| 25 // Attempt to feature-detect which event (change or click) is fired first | |
| 26 // for checkboxes. | |
| 27 var div = new DivElement(); | |
| 28 var checkbox = div.append(new InputElement()); | |
| 29 checkbox.type = 'checkbox'; | |
| 30 var fired = []; | |
| 31 checkbox.onClick.listen((e) { | |
| 32 fired.add(Element.clickEvent); | |
| 33 }); | |
| 34 checkbox.onChange.listen((e) { | |
| 35 fired.add(Element.changeEvent); | |
| 36 }); | |
| 37 checkbox.dispatchEvent(new MouseEvent('click', view: window)); | |
| 38 // WebKit/Blink don't fire the change event if the element is outside the | |
| 39 // document, so assume 'change' for that case. | |
| 40 return fired.length == 1 ? Element.changeEvent : fired.first; | |
| 41 }(); | |
| 42 | |
| 43 static Stream<Event> _getStreamForInputType(element) { | |
| 44 switch (element.type) { | |
| 45 case 'checkbox': | |
| 46 return _checkboxEventType.forTarget(element); | |
| 47 case 'radio': | |
| 48 case 'select-multiple': | |
| 49 case 'select-one': | |
| 50 return element.onChange; | |
| 51 default: | |
| 52 return element.onInput; | |
| 53 } | |
| 54 } | |
| 55 } | |
| 56 | |
| 57 class _ValueBinding extends _InputBinding { | |
| 58 _ValueBinding(node, model, path) : super(node, 'value', model, path); | |
| 59 | |
| 60 get node => super.node; | |
| 61 | |
| 62 void boundValueChanged(newValue) { | |
| 63 // Note: node can be an InputElement or TextAreaElement. Both have "value". | |
| 64 (node as dynamic).value = sanitizeBoundValue(newValue); | |
| 65 } | |
| 66 | |
| 67 void nodeValueChanged(e) { | |
| 68 value = (node as dynamic).value; | |
| 69 } | |
| 70 } | |
| 71 | |
| 72 class _CheckedBinding extends _InputBinding { | |
| 73 _CheckedBinding(node, model, path) : super(node, 'checked', model, path); | |
| 74 | |
| 75 InputElement get node => super.node; | |
| 76 | |
| 77 void boundValueChanged(newValue) { | |
| 78 node.checked = _toBoolean(newValue); | |
| 79 } | |
| 80 | |
| 81 void nodeValueChanged(e) { | |
| 82 value = node.checked; | |
| 83 | |
| 84 // Only the radio button that is getting checked gets an event. We | |
| 85 // therefore find all the associated radio buttons and update their | |
| 86 // CheckedBinding manually. | |
| 87 if (node is InputElement && node.type == 'radio') { | |
| 88 for (var r in _getAssociatedRadioButtons(node)) { | |
| 89 var checkedBinding = r.bindings['checked']; | |
| 90 if (checkedBinding != null) { | |
| 91 // Set the value directly to avoid an infinite call stack. | |
| 92 checkedBinding.value = false; | |
| 93 } | |
| 94 } | |
| 95 } | |
| 96 } | |
| 97 | |
| 98 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. | |
| 99 // Returns an array containing all radio buttons other than |element| that | |
| 100 // have the same |name|, either in the form that |element| belongs to or, | |
| 101 // if no form, in the document tree to which |element| belongs. | |
| 102 // | |
| 103 // This implementation is based upon the HTML spec definition of a | |
| 104 // "radio button group": | |
| 105 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state.
html#radio-button-group | |
| 106 // | |
| 107 static Iterable _getAssociatedRadioButtons(element) { | |
| 108 if (!_isNodeInDocument(element)) return []; | |
| 109 if (element.form != null) { | |
| 110 return element.form.nodes.where((el) { | |
| 111 return el != element && | |
| 112 el is InputElement && | |
| 113 el.type == 'radio' && | |
| 114 el.name == element.name; | |
| 115 }); | |
| 116 } else { | |
| 117 var radios = element.ownerDocument.queryAll( | |
| 118 'input[type="radio"][name="${element.name}"]'); | |
| 119 return radios.where((el) => el != element && el.form == null); | |
| 120 } | |
| 121 } | |
| 122 | |
| 123 // TODO(jmesserly): polyfill document.contains API instead of doing it here | |
| 124 static bool _isNodeInDocument(Node node) { | |
| 125 // On non-IE this works: | |
| 126 // return node.ownerDocument.contains(node); | |
| 127 var document = node.document; | |
| 128 if (node == document || node.parentNode == document) return true; | |
| 129 return document.documentElement.contains(node); | |
| 130 } | |
| 131 } | |
| 132 | |
| 133 class _SelectedIndexBinding extends _InputBinding { | |
| 134 _SelectedIndexBinding(node, model, path) | |
| 135 : super(node, 'selectedIndex', model, path); | |
| 136 | |
| 137 SelectElement get node => super.node; | |
| 138 | |
| 139 void boundValueChanged(value) { | |
| 140 var newValue = _toInt(value); | |
| 141 if (newValue <= node.length) { | |
| 142 node.selectedIndex = newValue; | |
| 143 return; | |
| 144 } | |
| 145 | |
| 146 // The binding may wish to bind to an <option> which has not yet been | |
| 147 // produced by a child <template>. Furthermore, we may need to wait for | |
| 148 // <optgroup> iterating and then for <option>. | |
| 149 // | |
| 150 // Unlike the JavaScript MDV, we don't have a special "Object.observe" event | |
| 151 // loop to schedule on. (See the the "ensureScheduled" function: | |
| 152 // https://github.com/Polymer/mdv/commit/9a51ad7ed74a292bf71662cea28acbd151f
f65c8) | |
| 153 // | |
| 154 // Instead we use scheduleMicrotask. Each <template repeat> needs a delay of | |
| 155 // 2: | |
| 156 // * once to happen after the child _TemplateIterator is created | |
| 157 // * once to be after _TemplateIterator.inputs CompoundBinding resolve | |
| 158 // And then we need to do this delay sequence twice: | |
| 159 // * once for OPTGROUP | |
| 160 // * once for OPTION. | |
| 161 // The resulting 2 * 2 is our maxRetries. | |
| 162 var maxRetries = 4; | |
| 163 delaySetSelectedIndex() { | |
| 164 if (newValue > node.length && --maxRetries >= 0) { | |
| 165 scheduleMicrotask(delaySetSelectedIndex); | |
| 166 } else { | |
| 167 node.selectedIndex = newValue; | |
| 168 } | |
| 169 } | |
| 170 | |
| 171 scheduleMicrotask(delaySetSelectedIndex); | |
| 172 } | |
| 173 | |
| 174 void nodeValueChanged(e) { | |
| 175 value = node.selectedIndex; | |
| 176 } | |
| 177 | |
| 178 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from | |
| 179 // one type to another (e.g. value-as-number) and whether it is useful to | |
| 180 // have something like a int/num binding converter (either as a base class or | |
| 181 // a wrapper). | |
| 182 static int _toInt(value) { | |
| 183 if (value is String) return int.parse(value, onError: (_) => null); | |
| 184 return value is int ? value : null; | |
| 185 } | |
| 186 } | |
| OLD | NEW |