You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
173 lines
5.8 KiB
173 lines
5.8 KiB
/** |
|
* 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 <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 = 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;
|
|
|