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

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

Issue 132403010: big update to observe, template_binding, polymer (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 10 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
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of template_binding; 5 part of template_binding;
6 6
7 abstract class _InputBinding extends NodeBinding { 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;
8 StreamSubscription _eventSub; 13 StreamSubscription _eventSub;
14 Bindable _bindable;
15 String _propertyName;
9 16
10 _InputBinding(node, name, model, path): super(node, name, model, path) { 17 _InputBinding(this._node, this._bindable, this._propertyName) {
11 _eventSub = _getStreamForInputType(node).listen(nodeValueChanged); 18 _eventSub = _getStreamForInputType(_node).listen(_nodeChanged);
19 _updateNode(open(_updateNode));
12 } 20 }
13 21
14 void valueChanged(newValue); 22 void _updateNode(newValue) => _updateProperty(_node, newValue, _propertyName);
15 23
16 void nodeValueChanged(e); 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;
17 70
18 void close() { 71 void close() {
19 if (closed) return; 72 if (_eventSub != null) {
20 _eventSub.cancel(); 73 _eventSub.cancel();
21 super.close(); 74 _eventSub = null;
75 }
76 if (_bindable != null) {
77 _bindable.close();
78 _bindable = null;
79 }
22 } 80 }
23 81
24 static EventStreamProvider<Event> _checkboxEventType = () { 82 static EventStreamProvider<Event> _checkboxEventType = () {
25 // Attempt to feature-detect which event (change or click) is fired first 83 // Attempt to feature-detect which event (change or click) is fired first
26 // for checkboxes. 84 // for checkboxes.
27 var div = new DivElement(); 85 var div = new DivElement();
28 var checkbox = div.append(new InputElement()); 86 var checkbox = div.append(new InputElement());
29 checkbox.type = 'checkbox'; 87 checkbox.type = 'checkbox';
30 var fired = []; 88 var fired = [];
31 checkbox.onClick.listen((e) { 89 checkbox.onClick.listen((e) {
(...skipping 14 matching lines...) Expand all
46 case 'checkbox': 104 case 'checkbox':
47 return _checkboxEventType.forTarget(element); 105 return _checkboxEventType.forTarget(element);
48 case 'radio': 106 case 'radio':
49 case 'select-multiple': 107 case 'select-multiple':
50 case 'select-one': 108 case 'select-one':
51 return element.onChange; 109 return element.onChange;
52 default: 110 default:
53 return element.onInput; 111 return element.onInput;
54 } 112 }
55 } 113 }
56 }
57
58 class _ValueBinding extends _InputBinding {
59 _ValueBinding(node, model, path) : super(node, 'value', model, path);
60
61 get node => super.node;
62
63 void valueChanged(newValue) {
64 // Note: node can be an InputElement or TextAreaElement. Both have "value".
65 node.value = sanitizeBoundValue(newValue);
66 }
67
68 void nodeValueChanged(e) {
69 value = node.value;
70 Observable.dirtyCheck();
71 }
72 }
73
74 class _CheckedBinding extends _InputBinding {
75 _CheckedBinding(node, model, path) : super(node, 'checked', model, path);
76
77 InputElement get node => super.node;
78
79 void valueChanged(newValue) {
80 node.checked = _toBoolean(newValue);
81 }
82
83 void nodeValueChanged(e) {
84 value = node.checked;
85
86 // Only the radio button that is getting checked gets an event. We
87 // therefore find all the associated radio buttons and update their
88 // CheckedBinding manually.
89 if (node is InputElement && node.type == 'radio') {
90 for (var r in _getAssociatedRadioButtons(node)) {
91 var checkedBinding = nodeBind(r).bindings['checked'];
92 if (checkedBinding != null) {
93 // Set the value directly to avoid an infinite call stack.
94 checkedBinding.value = false;
95 }
96 }
97 }
98
99 Observable.dirtyCheck();
100 }
101 114
102 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'. 115 // |element| is assumed to be an HTMLInputElement with |type| == 'radio'.
103 // Returns an array containing all radio buttons other than |element| that 116 // 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, 117 // 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. 118 // if no form, in the document tree to which |element| belongs.
106 // 119 //
107 // This implementation is based upon the HTML spec definition of a 120 // This implementation is based upon the HTML spec definition of a
108 // "radio button group": 121 // "radio button group":
109 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state. html#radio-button-group 122 // http://www.whatwg.org/specs/web-apps/current-work/multipage/number-state. html#radio-button-group
110 // 123 //
111 static Iterable _getAssociatedRadioButtons(element) { 124 static Iterable _getAssociatedRadioButtons(element) {
112 if (element.form != null) { 125 if (element.form != null) {
113 return element.form.nodes.where((el) { 126 return element.form.nodes.where((el) {
114 return el != element && 127 return el != element &&
115 el is InputElement && 128 el is InputElement &&
116 el.type == 'radio' && 129 el.type == 'radio' &&
117 el.name == element.name; 130 el.name == element.name;
118 }); 131 });
119 } else { 132 } else {
120 var treeScope = _getTreeScope(element); 133 var treeScope = _getTreeScope(element);
121 if (treeScope == null) return const []; 134 if (treeScope == null) return const [];
122 135
123 var radios = treeScope.querySelectorAll( 136 var radios = treeScope.querySelectorAll(
124 'input[type="radio"][name="${element.name}"]'); 137 'input[type="radio"][name="${element.name}"]');
125 return radios.where((el) => el != element && el.form == null); 138 return radios.where((el) => el != element && el.form == null);
126 } 139 }
127 } 140 }
128 }
129
130 class _SelectBinding extends _InputBinding {
131 MutationObserver _onMutation;
132
133 _SelectBinding(node, property, model, path)
134 : super(node, property, model, path);
135
136 SelectElement get node => super.node;
137
138 void valueChanged(newValue) {
139 _cancelMutationObserver();
140
141 if (_tryUpdateValue(newValue)) return;
142
143 // It could be that a template will expand an <option> child (or grandchild,
144 // if we have an <optgroup> in between). Since selected index cannot be set
145 // if the children aren't created yet, we need to wait for them to be
146 // created do this with a MutationObserver.
147 // Dart note: unlike JS we use mutation observers to avoid:
148 // https://github.com/Polymer/NodeBind/issues/5
149
150 // Note: it doesn't matter when the children get added; even if they get
151 // added much later, presumably we want the selected index data binding to
152 // still take effect.
153 _onMutation = new MutationObserver((x, y) {
154 if (_tryUpdateValue(value)) _cancelMutationObserver();
155 })..observe(node, childList: true, subtree: true);
156 }
157
158 bool _tryUpdateValue(newValue) {
159 if (property == 'selectedIndex') {
160 var intValue = _toInt(newValue);
161 node.selectedIndex = intValue;
162 return node.selectedIndex == intValue;
163 } else if (property == 'value') {
164 node.value = sanitizeBoundValue(newValue);
165 return node.value == newValue;
166 }
167 }
168
169 void _cancelMutationObserver() {
170 if (_onMutation != null) {
171 _onMutation.disconnect();
172 _onMutation = null;
173 }
174 }
175
176 void nodeValueChanged(e) {
177 _cancelMutationObserver();
178
179 if (property == 'selectedIndex') {
180 value = node.selectedIndex;
181 } else if (property == 'value') {
182 value = node.value;
183 }
184 }
185 141
186 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from 142 // TODO(jmesserly,sigmund): I wonder how many bindings typically convert from
187 // one type to another (e.g. value-as-number) and whether it is useful to 143 // one type to another (e.g. value-as-number) and whether it is useful to
188 // have something like a int/num binding converter (either as a base class or 144 // have something like a int/num binding converter (either as a base class or
189 // a wrapper). 145 // a wrapper).
190 static int _toInt(value) { 146 static int _toInt(value) {
191 if (value is String) return int.parse(value, onError: (_) => 0); 147 if (value is String) return int.parse(value, onError: (_) => 0);
192 return value is int ? value : 0; 148 return value is int ? value : 0;
193 } 149 }
194 } 150 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698