UNPKG

react-addons

Version:

Simple packaging of react addons to avoid fiddly 'react/addons' npm module.

182 lines (154 loc) 6.08 kB
/** * 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;