@samitouri / QOS-React-2 / commits / 7b047111a0

Support two-way binding for checkboxes in LinkedValueMixin via 'checkedLink' property

Johannes Baiter committed Jan 13, 2014 at 22:26 UTC 7b047111a0584d5a730764ff33fa37a74fc7b5fc
3 files changed +127 -5
src/dom/components/LinkedValueUtils.js
+51 -3
@@ -27,7 +27,7 @@ var hasReadOnlyValue = {
27 'radio': true
28 };
29
30 -function _assertLink(input) {
30 +function _assertValueLink(input) {
31 invariant(
32 input.props.value == null && input.props.onChange == null,
33 'Cannot provide a valueLink and a value or onChange event. If you want ' +
@@ -35,6 +35,15 @@ function _assertLink(input) {
35 );
36 }
37
38 +function _assertCheckedLink(input) {
39 + invariant(
40 + input.props.checked == null && input.props.onChange == null,
41 + 'Cannot provide a checkedLink and a checked property or onChange event. ' +
42 + 'If you want to use checked or onChange, you probably don\'t want to ' +
43 + 'use checkedLink'
44 + );
45 +}
46 +
47 /**
48 * @param {SyntheticEvent} e change event to handle
49 */
@@ -43,6 +52,14 @@ function _handleLinkedValueChange(e) {
52 this.props.valueLink.requestChange(e.target.value);
53 }
54
55 +/**
56 + * @param {SyntheticEvent} e change event to handle
57 + */
58 +function _handleLinkedCheckChange(e) {
59 + /*jshint validthis:true */
60 + this.props.checkedLink.requestChange(e.target.checked === 'true');
61 +}
62 +
63 /**
64 * Provide a linked `value` attribute for controlled forms. You should not use
65 * this outside of the ReactDOM controlled form components.
@@ -65,6 +82,21 @@ var LinkedValueUtils = {
82 );
83 }
84 }
85 + },
86 + checked: function(props, propName, componentName) {
87 + if (__DEV__) {
88 + if (props[propName] &&
89 + !props.onChange &&
90 + !props.readOnly &&
91 + !props.disabled) {
92 + console.warn(
93 + 'You provided a `checked` prop to a form field without an ' +
94 + '`onChange` handler. This will render a read-only field. If ' +
95 + 'the field should be mutable use `defaultChecked`. Otherwise, ' +
96 + 'set either `onChange` or `readOnly`.'
97 + );
98 + }
99 + }
100 }
101 }
102 },
@@ -75,20 +107,36 @@ var LinkedValueUtils = {
107 */
108 getValue: function(input) {
109 if (input.props.valueLink) {
78 - _assertLink(input);
110 + _assertValueLink(input);
111 return input.props.valueLink.value;
112 }
113 return input.props.value;
114 },
115
116 + /**
117 + * @param {ReactComponent} input Form component
118 + * @return {*} current checked status of the input either from checked prop
119 + * or link.
120 + */
121 + getChecked: function(input) {
122 + if (input.props.checkedLink) {
123 + _assertCheckedLink(input);
124 + return input.props.checkedLink.value;
125 + }
126 + return input.props.checked;
127 + },
128 +
129 /**
130 * @param {ReactComponent} input Form component
131 * @return {function} change callback either from onChange prop or link.
132 */
133 getOnChange: function(input) {
134 if (input.props.valueLink) {
90 - _assertLink(input);
135 + _assertValueLink(input);
136 return _handleLinkedValueChange;
137 + } else if (input.props.checkedLink) {
138 + _assertCheckedLink(input);
139 + return _handleLinkedCheckChange;
140 }
141 return input.props.onChange;
142 }
src/dom/components/ReactDOMInput.js
+3 -2
@@ -73,12 +73,13 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
73
74 props.defaultChecked = null;
75 props.defaultValue = null;
76 - props.checked =
77 - this.props.checked != null ? this.props.checked : this.state.checked;
76
77 var value = LinkedValueUtils.getValue(this);
78 props.value = value != null ? value : this.state.value;
79
80 + var checked = LinkedValueUtils.getChecked(this);
81 + props.checked = checked != null ? checked : this.state.checked;
82 +
83 props.onChange = this._handleChange;
84
85 return input(props, this.props.children);
src/dom/components/__tests__/ReactDOMInput-test.js
+73
@@ -252,4 +252,77 @@ describe('ReactDOMInput', function() {
252 expect(React.renderComponent.bind(React, instance, node)).toThrow();
253
254 });
255 +
256 + it('should support checkedLink', function() {
257 + var container = document.createElement('div');
258 + var link = new ReactLink(true, mocks.getMockFunction());
259 + var instance = <input type="checked" checkedLink={link} />;
260 +
261 + React.renderComponent(instance, container);
262 +
263 + expect(instance.getDOMNode().checked).toBe(true);
264 + expect(link.value).toBe(true);
265 + expect(link.requestChange.mock.calls.length).toBe(0);
266 +
267 + instance.getDOMNode().checked = false;
268 + ReactTestUtils.Simulate.input(instance.getDOMNode());
269 +
270 + expect(link.requestChange.mock.calls.length).toBe(1);
271 + expect(link.requestChange.mock.calls[0][0]).toEqual(false);
272 + });
273 +
274 + it('should warn with checked and no onChange handler', function() {
275 + var oldWarn = console.warn;
276 + try {
277 + console.warn = mocks.getMockFunction();
278 +
279 + var node = document.createElement('div');
280 + var link = new ReactLink(true, mocks.getMockFunction());
281 + React.renderComponent(<input type="checkbox" checkedLink={link} />, node);
282 + expect(console.warn.mock.calls.length).toBe(0);
283 +
284 + React.renderComponent(
285 + <input type="checkbox" checked="false" onChange={mocks.getMockFunction()} />,
286 + node
287 + );
288 + expect(console.warn.mock.calls.length).toBe(0);
289 +
290 + React.renderComponent(
291 + <input type="checkbox" checked="false" readOnly={true} />,
292 + node
293 + );
294 + expect(console.warn.mock.calls.length).toBe(0);
295 +
296 + React.renderComponent(<input type="checkbox" checked="false" />, node);
297 + expect(console.warn.mock.calls.length).toBe(1);
298 +
299 + React.renderComponent(
300 + <input type="checkbox" checked="false" readOnly={false} />,
301 + node
302 + );
303 + expect(console.warn.mock.calls.length).toBe(2);
304 + } finally {
305 + console.warn = oldWarn;
306 + }
307 + });
308 +
309 + it('should throw if both checked and checkedLink are provided', function() {
310 + // Silences console.error messages
311 + // ReactErrorUtils.guard is applied to all methods of a React component
312 + // and calls console.error in __DEV__ (true for test environment)
313 + spyOn(console, 'error');
314 +
315 + var node = document.createElement('div');
316 + var link = new ReactLink(true, mocks.getMockFunction());
317 + var instance = <input type="checkbox" checkedLink={link} />;
318 +
319 + expect(React.renderComponent.bind(React, instance, node)).not.toThrow();
320 +
321 + instance = <input type="checkbox" checkedLink={link} checked="false" />;
322 + expect(React.renderComponent.bind(React, instance, node)).toThrow();
323 +
324 + instance = <input type="checkbox" checkedLink={link} onChange={emptyFunction} />;
325 + expect(React.renderComponent.bind(React, instance, node)).toThrow();
326 +
327 + });
328 });