/** * Copyright 2013-2015, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @providesModule ReactDOMInput */ 'use strict'; var AutoFocusMixin = require("./AutoFocusMixin"); var DOMPropertyOperations = require("./DOMPropertyOperations"); var LinkedValueUtils = require("./LinkedValueUtils"); var ReactBrowserComponentMixin = require("./ReactBrowserComponentMixin"); var ReactClass = require("./ReactClass"); var ReactElement = require("./ReactElement"); var ReactMount = require("./ReactMount"); var ReactUpdates = require("./ReactUpdates"); var assign = require("./Object.assign"); var invariant = require("./invariant"); var input = ReactElement.createFactory('input'); var instancesByReactID = {}; function forceUpdateIfMounted() { /*jshint validthis:true */ if (this.isMounted()) { this.forceUpdate(); } } /** * Implements an 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 = ReactClass.createClass({ displayName: 'ReactDOMInput', tagName: 'INPUT', mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin], getInitialState: function() { var defaultValue = this.props.defaultValue; return { initialChecked: this.props.defaultChecked || false, initialValue: defaultValue != null ? defaultValue : null }; }, render: function() { // Clone `this.props` so we don't mutate the input. var props = assign({}, this.props); props.defaultChecked = null; props.defaultValue = null; var value = LinkedValueUtils.getValue(this); props.value = value != null ? value : this.state.initialValue; var checked = LinkedValueUtils.getChecked(this); props.checked = checked != null ? checked : this.state.initialChecked; 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) { returnValue = onChange.call(this, event); } // Here we use asap to wait until all updates have propagated, which // is important when using controlled components within layers: // https://github.com/facebook/react/issues/1698 ReactUpdates.asap(forceUpdateIfMounted, this); 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)); // If this is a controlled radio button group, forcing the input that // was previously checked to update will cause it to be come re-checked // as appropriate. ReactUpdates.asap(forceUpdateIfMounted, otherInstance); } } return returnValue; } }); module.exports = ReactDOMInput;