react-addons
Version:
Simple packaging of react addons to avoid fiddly 'react/addons' npm module.
182 lines (154 loc) • 6.08 kB
JavaScript
/**
* Copyright 2013-2014 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 AutoFocusMixin = require("./AutoFocusMixin");
var DOMPropertyOperations = require("./DOMPropertyOperations");
var LinkedValueUtils = require("./LinkedValueUtils");
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({
displayName: 'ReactDOMInput',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin],
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;
var value = LinkedValueUtils.getValue(this);
props.value = value != null ? value : this.state.value;
var checked = LinkedValueUtils.getChecked(this);
props.checked = checked != null ? checked : this.state.checked;
props.onChange = this._handleChange;
return input(props, this.props.children);
},
componentDidMount: function() {
var id = ReactMount.getID(this.getDOMNode());
instancesByReactID[id] = this;
},
componentWillUnmount: function() {
var rootNode = this.getDOMNode();
var id = ReactMount.getID(rootNode);
delete instancesByReactID[id];
},
componentDidUpdate: function(prevProps, prevState, prevContext) {
var rootNode = this.getDOMNode();
if (this.props.checked != null) {
DOMPropertyOperations.setValueForProperty(
rootNode,
'checked',
this.props.checked || false
);
}
var value = LinkedValueUtils.getValue(this);
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 = LinkedValueUtils.getOnChange(this);
if (onChange) {
this._isChanging = true;
returnValue = onChange.call(this, 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();
var queryRoot = rootNode;
while (queryRoot.parentNode) {
queryRoot = queryRoot.parentNode;
}
// 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
// `querySelectorAll` to ensure we don't miss anything.
var group = queryRoot.querySelectorAll(
'input[name=' + JSON.stringify('' + name) + '][type="radio"]');
for (var i = 0, groupLen = group.length; i < groupLen; i++) {
var otherNode = group[i];
if (otherNode === rootNode ||
otherNode.form !== rootNode.form) {
continue;
}
var otherID = ReactMount.getID(otherNode);
("production" !== process.env.NODE_ENV ? invariant(
otherID,
'ReactDOMInput: Mixing React and non-React radio inputs with the ' +
'same `name` is not supported.'
) : invariant(otherID));
var otherInstance = instancesByReactID[otherID];
("production" !== process.env.NODE_ENV ? invariant(
otherInstance,
'ReactDOMInput: Unknown radio button ID %s.',
otherID
) : invariant(otherInstance));
// 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;