mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
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.
This commit is contained in:
committed by
Paul O’Shannessy
parent
5d4f903482
commit
ed54fff204
@@ -21,6 +21,8 @@
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactDOMForm = require('ReactDOMForm');
|
||||
var ReactDOMInput = require('ReactDOMInput');
|
||||
var ReactDOMOption = require('ReactDOMOption');
|
||||
var ReactDOMSelect = require('ReactDOMSelect');
|
||||
var ReactDOMTextarea = require('ReactDOMTextarea');
|
||||
|
||||
var DefaultDOMPropertyConfig = require('DefaultDOMPropertyConfig');
|
||||
@@ -53,6 +55,8 @@ function inject() {
|
||||
ReactDOM.injection.injectComponentClasses({
|
||||
form: ReactDOMForm,
|
||||
input: ReactDOMInput,
|
||||
option: ReactDOMOption,
|
||||
select: ReactDOMSelect,
|
||||
textarea: ReactDOMTextarea
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactDOMOption
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
|
||||
// Store a reference to the <option> `ReactNativeComponent`.
|
||||
var option = ReactDOM.option;
|
||||
|
||||
/**
|
||||
* Implements an <option> native component that warns when `selected` is set.
|
||||
*/
|
||||
var ReactDOMOption = ReactCompositeComponent.createClass({
|
||||
|
||||
componentWillMount: function() {
|
||||
// TODO (yungsters): Remove support for `selected` in <option>.
|
||||
if (this.props.selected != null) {
|
||||
if (__DEV__) {
|
||||
console.warn(
|
||||
'Use the `defaultValue` or `value` props on <select> instead of ' +
|
||||
'setting `selected` on <option>.'
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return option(this.props, this.props.children);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactDOMOption;
|
||||
@@ -0,0 +1,156 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactDOMSelect
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
|
||||
// Store a reference to the <select> `ReactNativeComponent`.
|
||||
var select = ReactDOM.select;
|
||||
|
||||
/**
|
||||
* Validation function for `value` and `defaultValue`.
|
||||
* @private
|
||||
*/
|
||||
function selectValueType(props, propName, componentName) {
|
||||
if (props[propName] == null) {
|
||||
return;
|
||||
}
|
||||
if (props.multiple) {
|
||||
invariant(
|
||||
Array.isArray(props[propName]),
|
||||
'The `%s` prop supplied to <select> must be an array if `multiple` is ' +
|
||||
'true.'
|
||||
);
|
||||
} else {
|
||||
invariant(
|
||||
!Array.isArray(props[propName]),
|
||||
'The `%s` prop supplied to <select> must be a scalar value if ' +
|
||||
'`multiple` is false.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If `value` is supplied, updates <option> elements on mount and update.
|
||||
* @private
|
||||
*/
|
||||
function updateOptions() {
|
||||
/*jshint validthis:true */
|
||||
if (this.props.value == null) {
|
||||
return;
|
||||
}
|
||||
var options = this.getDOMNode().options;
|
||||
var selectedValue = '' + this.props.value;
|
||||
|
||||
for (var i = 0, l = options.length; i < l; i++) {
|
||||
var selected = this.props.multiple ?
|
||||
selectedValue.indexOf(options[i].value) >= 0 :
|
||||
selected = options[i].value === selectedValue;
|
||||
|
||||
if (selected !== options[i].selected) {
|
||||
options[i].selected = selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a <select> native component that allows optionally setting the
|
||||
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a
|
||||
* string. If `multiple` is true, the prop must be an array of strings.
|
||||
*
|
||||
* If `value` is not supplied (or null/undefined), user actions that change the
|
||||
* selected option will trigger updates to the rendered options.
|
||||
*
|
||||
* If it is supplied (and not null/undefined), the rendered options will not
|
||||
* update in response to user actions. Instead, the `value` prop must change in
|
||||
* order for the rendered options to update.
|
||||
*
|
||||
* If `defaultValue` is provided, any options with the supplied values will be
|
||||
* selected.
|
||||
*/
|
||||
var ReactDOMSelect = ReactCompositeComponent.createClass({
|
||||
|
||||
propTypes: {
|
||||
defaultValue: selectValueType,
|
||||
value: selectValueType
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
|
||||
},
|
||||
|
||||
componentWillReceiveProps: function(nextProps) {
|
||||
if (!this.props.multiple && nextProps.multiple) {
|
||||
this.setState({value: [this.state.value]});
|
||||
} else if (this.props.multiple && !nextProps.multiple) {
|
||||
this.setState({value: this.state.value[0]});
|
||||
}
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function() {
|
||||
// Defer any updates to this component during the `onChange` handler.
|
||||
return !this._isChanging;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Clone `this.props` so we don't mutate the input.
|
||||
var props = merge(this.props);
|
||||
|
||||
props.onChange = this.handleChange;
|
||||
props.value = null;
|
||||
|
||||
return select(props, this.props.children);
|
||||
},
|
||||
|
||||
componentDidMount: updateOptions,
|
||||
|
||||
componentDidUpdate: updateOptions,
|
||||
|
||||
handleChange: function(event) {
|
||||
var returnValue;
|
||||
if (this.props.onChange) {
|
||||
this._isChanging = true;
|
||||
returnValue = this.props.onChange(event);
|
||||
this._isChanging = false;
|
||||
}
|
||||
|
||||
var selectedValue;
|
||||
if (this.props.multiple) {
|
||||
selectedValue = [];
|
||||
var options = event.target.options;
|
||||
for (var i = 0, l = options.length; i < l; i++) {
|
||||
if (options[i].selected) {
|
||||
selectedValue.push(options[i].value);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
selectedValue = event.target.value;
|
||||
}
|
||||
|
||||
this.setState({value: selectedValue});
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactDOMSelect;
|
||||
Reference in New Issue
Block a user