Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(7)

Side by Side Diff: pkg/mdv/lib/src/input_bindings.dart

Issue 19593006: [mdv] Implement two-way binding to HTMLTextArea.value (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/mdv/lib/src/bindings.dart ('k') | pkg/mdv/lib/src/input_element.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « pkg/mdv/lib/src/bindings.dart ('k') | pkg/mdv/lib/src/input_element.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698