mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
d5c2d5f291
Fixes #473.
170 lines
5.5 KiB
JavaScript
170 lines
5.5 KiB
JavaScript
/**
|
|
* 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 ReactDOMInput
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
var DOMPropertyOperations = require('DOMPropertyOperations');
|
|
var LinkedValueMixin = require('LinkedValueMixin');
|
|
var ReactCompositeComponent = require('ReactCompositeComponent');
|
|
var ReactDOM = require('ReactDOM');
|
|
var ReactMount = require('ReactMount');
|
|
|
|
var invariant = require('invariant');
|
|
var merge = require('merge');
|
|
|
|
// Store a reference to the <input> `ReactDOMComponent`.
|
|
var input = ReactDOM.input;
|
|
|
|
var instancesByReactID = {};
|
|
|
|
/**
|
|
* Implements an <input> native component that allows setting these optional
|
|
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
|
|
*
|
|
* If `checked` or `value` are not supplied (or null/undefined), user actions
|
|
* that affect the checked state or value will trigger updates to the element.
|
|
*
|
|
* If they are supplied (and not null/undefined), the rendered element will not
|
|
* trigger updates to the element. Instead, the props must change in order for
|
|
* the rendered element to be updated.
|
|
*
|
|
* The rendered element will be initialized as unchecked (or `defaultChecked`)
|
|
* with an empty value (or `defaultValue`).
|
|
*
|
|
* @see http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
|
|
*/
|
|
var ReactDOMInput = ReactCompositeComponent.createClass({
|
|
mixins: [LinkedValueMixin],
|
|
|
|
getInitialState: function() {
|
|
var defaultValue = this.props.defaultValue;
|
|
return {
|
|
checked: this.props.defaultChecked || false,
|
|
value: defaultValue != null ? defaultValue : null
|
|
};
|
|
},
|
|
|
|
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.defaultChecked = null;
|
|
props.defaultValue = null;
|
|
props.checked =
|
|
this.props.checked != null ? this.props.checked : this.state.checked;
|
|
|
|
var value = this.getValue();
|
|
props.value = value != null ? value : this.state.value;
|
|
|
|
props.onChange = this._handleChange;
|
|
|
|
return input(props, this.props.children);
|
|
},
|
|
|
|
componentDidMount: function(rootNode) {
|
|
var id = ReactMount.getID(rootNode);
|
|
instancesByReactID[id] = this;
|
|
},
|
|
|
|
componentWillUnmount: function() {
|
|
var rootNode = this.getDOMNode();
|
|
var id = ReactMount.getID(rootNode);
|
|
delete instancesByReactID[id];
|
|
},
|
|
|
|
componentDidUpdate: function(prevProps, prevState, rootNode) {
|
|
if (this.props.checked != null) {
|
|
DOMPropertyOperations.setValueForProperty(
|
|
rootNode,
|
|
'checked',
|
|
this.props.checked || false
|
|
);
|
|
}
|
|
|
|
var value = this.getValue();
|
|
if (value != null) {
|
|
// Cast `value` to a string to ensure the value is set correctly. While
|
|
// browsers typically do this as necessary, jsdom doesn't.
|
|
DOMPropertyOperations.setValueForProperty(rootNode, 'value', '' + value);
|
|
}
|
|
},
|
|
|
|
_handleChange: function(event) {
|
|
var returnValue;
|
|
var onChange = this.getOnChange();
|
|
if (onChange) {
|
|
this._isChanging = true;
|
|
returnValue = onChange(event);
|
|
this._isChanging = false;
|
|
}
|
|
this.setState({
|
|
checked: event.target.checked,
|
|
value: event.target.value
|
|
});
|
|
|
|
var name = this.props.name;
|
|
if (this.props.type === 'radio' && name != null) {
|
|
var rootNode = this.getDOMNode();
|
|
// If `rootNode.form` was non-null, then we could try `form.elements`,
|
|
// but that sometimes behaves strangely in IE8. We could also try using
|
|
// `form.getElementsByName`, but that will only return direct children
|
|
// and won't include inputs that use the HTML5 `form=` attribute. Since
|
|
// the input might not even be in a form, let's just use the global
|
|
// `getElementsByName` to ensure we don't miss anything.
|
|
var group = document.getElementsByName(name);
|
|
for (var i = 0, groupLen = group.length; i < groupLen; i++) {
|
|
var otherNode = group[i];
|
|
if (otherNode === rootNode ||
|
|
otherNode.nodeName !== 'INPUT' || otherNode.type !== 'radio' ||
|
|
otherNode.form !== rootNode.form) {
|
|
continue;
|
|
}
|
|
var otherID = ReactMount.getID(otherNode);
|
|
invariant(
|
|
otherID,
|
|
'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
|
|
'same `name` is not supported.'
|
|
);
|
|
var otherInstance = instancesByReactID[otherID];
|
|
invariant(
|
|
otherInstance,
|
|
'ReactDOMInput: Unknown radio button ID %s.',
|
|
otherID
|
|
);
|
|
// In some cases, this will actually change the `checked` state value.
|
|
// In other cases, there's no change but this forces a reconcile upon
|
|
// which componentDidUpdate will reset the DOM property to whatever it
|
|
// should be.
|
|
otherInstance.setState({
|
|
checked: false
|
|
});
|
|
}
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
});
|
|
|
|
module.exports = ReactDOMInput;
|