@samitouri / QOS-React-2 / commits / ed54fff204
Fork 0

The library for web and native user interfaces.

Controlled <select> and <option> Components

This implements a `<select>` component that supports `value` and `defaultValue`. It also changes `<option>` to warn when the `selected` prop is supplied.

Tim Yung committed Jul 16, 2013 at 11:33 UTC ed54fff2046fe6093078f625280725fcb19b9b33
3 files changed +210
src/core/ReactDefaultInjection.js
+4
@@ -21,6 +21,8 @@
21 var ReactDOM = require('ReactDOM');
22 var ReactDOMForm = require('ReactDOMForm');
23 var ReactDOMInput = require('ReactDOMInput');
24 +var ReactDOMOption = require('ReactDOMOption');
25 +var ReactDOMSelect = require('ReactDOMSelect');
26 var ReactDOMTextarea = require('ReactDOMTextarea');
27
28 var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
@@ -53,6 +55,8 @@ function inject() {
55 ReactDOM.injection.injectComponentClasses({
56 form: ReactDOMForm,
57 input: ReactDOMInput,
58 + option: ReactDOMOption,
59 + select: ReactDOMSelect,
60 textarea: ReactDOMTextarea
61 });
62
src/dom/components/ReactDOMOption.js new
+50
@@ -0,0 +1,50 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMOption
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactCompositeComponent = require('ReactCompositeComponent');
22 +var ReactDOM = require('ReactDOM');
23 +
24 +// Store a reference to the <option> `ReactNativeComponent`.
25 +var option = ReactDOM.option;
26 +
27 +/**
28 + * Implements an <option> native component that warns when `selected` is set.
29 + */
30 +var ReactDOMOption = ReactCompositeComponent.createClass({
31 +
32 + componentWillMount: function() {
33 + // TODO (yungsters): Remove support for `selected` in <option>.
34 + if (this.props.selected != null) {
35 + if (__DEV__) {
36 + console.warn(
37 + 'Use the `defaultValue` or `value` props on <select> instead of ' +
38 + 'setting `selected` on <option>.'
39 + );
40 + }
41 + }
42 + },
43 +
44 + render: function() {
45 + return option(this.props, this.props.children);
46 + }
47 +
48 +});
49 +
50 +module.exports = ReactDOMOption;
src/dom/components/ReactDOMSelect.js new
+156
@@ -0,0 +1,156 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMSelect
17 + */
18 +
19 +"use strict";
20 +
21 +var ReactCompositeComponent = require('ReactCompositeComponent');
22 +var ReactDOM = require('ReactDOM');
23 +
24 +var invariant = require('invariant');
25 +var merge = require('merge');
26 +
27 +// Store a reference to the <select> `ReactNativeComponent`.
28 +var select = ReactDOM.select;
29 +
30 +/**
31 + * Validation function for `value` and `defaultValue`.
32 + * @private
33 + */
34 +function selectValueType(props, propName, componentName) {
35 + if (props[propName] == null) {
36 + return;
37 + }
38 + if (props.multiple) {
39 + invariant(
40 + Array.isArray(props[propName]),
41 + 'The `%s` prop supplied to <select> must be an array if `multiple` is ' +
42 + 'true.'
43 + );
44 + } else {
45 + invariant(
46 + !Array.isArray(props[propName]),
47 + 'The `%s` prop supplied to <select> must be a scalar value if ' +
48 + '`multiple` is false.'
49 + );
50 + }
51 +}
52 +
53 +/**
54 + * If `value` is supplied, updates <option> elements on mount and update.
55 + * @private
56 + */
57 +function updateOptions() {
58 + /*jshint validthis:true */
59 + if (this.props.value == null) {
60 + return;
61 + }
62 + var options = this.getDOMNode().options;
63 + var selectedValue = '' + this.props.value;
64 +
65 + for (var i = 0, l = options.length; i < l; i++) {
66 + var selected = this.props.multiple ?
67 + selectedValue.indexOf(options[i].value) >= 0 :
68 + selected = options[i].value === selectedValue;
69 +
70 + if (selected !== options[i].selected) {
71 + options[i].selected = selected;
72 + }
73 + }
74 +}
75 +
76 +/**
77 + * Implements a <select> native component that allows optionally setting the
78 + * props `value` and `defaultValue`. If `multiple` is false, the prop must be a
79 + * string. If `multiple` is true, the prop must be an array of strings.
80 + *
81 + * If `value` is not supplied (or null/undefined), user actions that change the
82 + * selected option will trigger updates to the rendered options.
83 + *
84 + * If it is supplied (and not null/undefined), the rendered options will not
85 + * update in response to user actions. Instead, the `value` prop must change in
86 + * order for the rendered options to update.
87 + *
88 + * If `defaultValue` is provided, any options with the supplied values will be
89 + * selected.
90 + */
91 +var ReactDOMSelect = ReactCompositeComponent.createClass({
92 +
93 + propTypes: {
94 + defaultValue: selectValueType,
95 + value: selectValueType
96 + },
97 +
98 + getInitialState: function() {
99 + return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
100 + },
101 +
102 + componentWillReceiveProps: function(nextProps) {
103 + if (!this.props.multiple && nextProps.multiple) {
104 + this.setState({value: [this.state.value]});
105 + } else if (this.props.multiple && !nextProps.multiple) {
106 + this.setState({value: this.state.value[0]});
107 + }
108 + },
109 +
110 + shouldComponentUpdate: function() {
111 + // Defer any updates to this component during the `onChange` handler.
112 + return !this._isChanging;
113 + },
114 +
115 + render: function() {
116 + // Clone `this.props` so we don't mutate the input.
117 + var props = merge(this.props);
118 +
119 + props.onChange = this.handleChange;
120 + props.value = null;
121 +
122 + return select(props, this.props.children);
123 + },
124 +
125 + componentDidMount: updateOptions,
126 +
127 + componentDidUpdate: updateOptions,
128 +
129 + handleChange: function(event) {
130 + var returnValue;
131 + if (this.props.onChange) {
132 + this._isChanging = true;
133 + returnValue = this.props.onChange(event);
134 + this._isChanging = false;
135 + }
136 +
137 + var selectedValue;
138 + if (this.props.multiple) {
139 + selectedValue = [];
140 + var options = event.target.options;
141 + for (var i = 0, l = options.length; i < l; i++) {
142 + if (options[i].selected) {
143 + selectedValue.push(options[i].value);
144 + }
145 + }
146 + } else {
147 + selectedValue = event.target.value;
148 + }
149 +
150 + this.setState({value: selectedValue});
151 + return returnValue;
152 + }
153 +
154 +});
155 +
156 +module.exports = ReactDOMSelect;