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