mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Implement ReactDOMTextarea
This changes `ReactDOMTextarea` to accept `defaultValue` and `value`. It will warn people about using children (but allow it and treat it as `defaultValue`, which is the current behavior).
This commit is contained in:
committed by
Paul O’Shannessy
parent
738de8cfa8
commit
55176116a2
@@ -80,8 +80,7 @@ var ReactDOM = objMapKeyVal({
|
||||
embed: true,
|
||||
fieldset: false,
|
||||
footer: false,
|
||||
// Danger: this gets monkeypatched! See ReactDOMForm for more info.
|
||||
form: false,
|
||||
form: false, // NOTE: Injected, see `ReactDOMForm`.
|
||||
h1: false,
|
||||
h2: false,
|
||||
h3: false,
|
||||
@@ -116,8 +115,7 @@ var ReactDOM = objMapKeyVal({
|
||||
table: false,
|
||||
tbody: false,
|
||||
td: false,
|
||||
// Danger: this gets monkeypatched! See ReactDOMTextarea for more info.
|
||||
textarea: false,
|
||||
textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
|
||||
tfoot: false,
|
||||
th: false,
|
||||
thead: false,
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* 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 ReactDOMTextarea
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
// Store a reference to the <textarea> `ReactNativeComponent`.
|
||||
var textarea = ReactDOM.textarea;
|
||||
|
||||
// For quickly matching children type, to test if can be treated as content.
|
||||
var CONTENT_TYPES = {'string': true, 'number': true};
|
||||
|
||||
var getTextContent = function(props) {
|
||||
if (!props) {
|
||||
return '';
|
||||
}
|
||||
|
||||
invariant(
|
||||
props.dangerouslySetInnerHTML == null,
|
||||
'`dangerouslySetInnerHTML` does not make sense on textarea.'
|
||||
);
|
||||
|
||||
var content;
|
||||
if (Array.isArray(props.children)) {
|
||||
invariant(
|
||||
props.children.length <= 1,
|
||||
'textarea can have at most one child'
|
||||
);
|
||||
content = props.children[0];
|
||||
} else if (props.children != null) {
|
||||
content = props.children;
|
||||
} else {
|
||||
content = props.content;
|
||||
}
|
||||
|
||||
invariant(
|
||||
content == null || CONTENT_TYPES[typeof content],
|
||||
'textarea must contain a single string or number, not an array or ' +
|
||||
'object.'
|
||||
);
|
||||
return content != null ? '' + content : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Since setting .textContent on a dirty <textarea> doesn't update its value,
|
||||
* we intercept prop changes here to make sure that value is updated.
|
||||
*/
|
||||
var ReactDOMTextarea = ReactCompositeComponent.createClass({
|
||||
getInitialState: function() {
|
||||
// We keep the original value of content or children here so that
|
||||
// ReactNativeComponent doesn't update textContent (unnecessary since we
|
||||
// update value).
|
||||
return {
|
||||
initialContent: getTextContent(this.props)
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return this.transferPropsTo(textarea({
|
||||
content: this.state.initialContent
|
||||
}));
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, rootNode) {
|
||||
var oldContent = getTextContent(prevProps);
|
||||
var newContent = getTextContent(this.props);
|
||||
if (oldContent !== newContent && rootNode.value !== newContent) {
|
||||
rootNode.value = newContent;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = ReactDOMTextarea;
|
||||
@@ -1,66 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @jsx React.DOM
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
describe('ReactDOMTextarea', function() {
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
});
|
||||
|
||||
it("should update value", function() {
|
||||
var stub = ReactTestUtils.renderIntoDocument(<textarea>giraffe</textarea>);
|
||||
var node = stub.getDOMNode();
|
||||
|
||||
expect(node.value).toEqual('giraffe');
|
||||
|
||||
stub.replaceProps({ children: 'gorilla' });
|
||||
expect(node.value).toEqual('gorilla');
|
||||
|
||||
stub.replaceProps({ children: 17 });
|
||||
expect(node.value).toEqual('17');
|
||||
|
||||
stub.replaceProps({ children: [42] });
|
||||
expect(node.value).toEqual('42');
|
||||
|
||||
stub.replaceProps({ children: null });
|
||||
expect(node.value).toEqual('');
|
||||
|
||||
stub.replaceProps({ content: 'eggplant' });
|
||||
expect(node.value).toEqual('eggplant');
|
||||
});
|
||||
|
||||
it("should throw with multiple or invalid children", function() {
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea>{'hello'}{'there'}</textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea><strong /></textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* 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 ReactDOMTextarea
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var merge = require('merge');
|
||||
|
||||
// Store a reference to the <textarea> `ReactNativeComponent`.
|
||||
var textarea = ReactDOM.textarea;
|
||||
|
||||
// For quickly matching children type, to test if can be treated as content.
|
||||
var CONTENT_TYPES = {'string': true, 'number': true};
|
||||
|
||||
/**
|
||||
* Implements a <textarea> native component that allows setting `value`, and
|
||||
* `defaultValue`. This differs from the traditional DOM API because value is
|
||||
* usually set as PCDATA children.
|
||||
*
|
||||
* If `value` is not supplied (or null/undefined), user actions that affect the
|
||||
* value will trigger updates to the element.
|
||||
*
|
||||
* If `value` is supplied (and not null/undefined), the rendered element will
|
||||
* not trigger updates to the element. Instead, the `value` prop must change in
|
||||
* order for the rendered element to be updated.
|
||||
*
|
||||
* The rendered element will be initialized with an empty value, the prop
|
||||
* `defaultValue` if specified, or the children content (deprecated).
|
||||
*/
|
||||
var ReactDOMTextarea = ReactCompositeComponent.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
var defaultValue = this.props.defaultValue;
|
||||
// TODO (yungsters): Remove support for children content in <textarea>.
|
||||
var children = this.props.children;
|
||||
if (children != null) {
|
||||
global.console && console.warn && console.warn(
|
||||
'Use the `defaultValue` or `value` props instead of setting children ' +
|
||||
'on <textarea>.'
|
||||
);
|
||||
invariant(
|
||||
defaultValue == null,
|
||||
'If you supply `defaultValue` on a <textarea>, do not pass children.'
|
||||
);
|
||||
if (Array.isArray(children)) {
|
||||
invariant(
|
||||
children.length <= 1,
|
||||
'<textarea> can only have at most one child.'
|
||||
);
|
||||
children = children[0];
|
||||
}
|
||||
invariant(
|
||||
CONTENT_TYPES[typeof children],
|
||||
'If you specify children to <textarea>, it must be a single string ' +
|
||||
'or number., not an array or object.'
|
||||
);
|
||||
defaultValue = '' + children;
|
||||
}
|
||||
defaultValue = defaultValue || '';
|
||||
return {
|
||||
// We save the initial value so that `ReactNativeComponent` doesn't update
|
||||
// `textContent` (unnecessary since we update value).
|
||||
initialValue: this.props.value != null ? this.props.value : defaultValue,
|
||||
value: defaultValue
|
||||
};
|
||||
},
|
||||
|
||||
getValue: function() {
|
||||
return this.props.value != null ? this.props.value : this.state.value;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
// Clone `this.props` so we don't mutate the input.
|
||||
var props = merge(this.props);
|
||||
|
||||
invariant(
|
||||
props.dangerouslySetInnerHTML == null,
|
||||
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
|
||||
);
|
||||
|
||||
props.value = this.getValue();
|
||||
props.onChange = this.handleChange;
|
||||
|
||||
// Always set children to the same thing. In IE9, the selection range will
|
||||
// get reset if `textContent` is mutated.
|
||||
return textarea(props, this.state.initialValue);
|
||||
},
|
||||
|
||||
componentDidUpdate: function(prevProps, prevState, rootNode) {
|
||||
if (this.props.value != null) {
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
rootNode,
|
||||
'value',
|
||||
this.props.value || ''
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
handleChange: ReactCompositeComponent.autoBind(function(event) {
|
||||
var returnValue;
|
||||
if (this.props.onChange) {
|
||||
returnValue = this.props.onChange(event);
|
||||
}
|
||||
this.setState({value: event.target.value});
|
||||
return returnValue;
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
module.exports = ReactDOMTextarea;
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @jsx React.DOM
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
/*jshint evil:true */
|
||||
|
||||
describe('ReactDOMTextarea', function() {
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
|
||||
var renderTextarea;
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
|
||||
renderTextarea = function(component) {
|
||||
var stub = ReactTestUtils.renderIntoDocument(component);
|
||||
var node = stub.getDOMNode();
|
||||
// Polyfilling the browser's quirky behavior.
|
||||
node.value = node.innerHTML;
|
||||
return node;
|
||||
};
|
||||
});
|
||||
|
||||
it('should allow setting `defaultValue`', function() {
|
||||
var stub = <textarea defaultValue="giraffe" />;
|
||||
var node = renderTextarea(stub);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
// Changing `defaultValue` should do nothing.
|
||||
stub.replaceProps({defaultValue: 'gorilla'});
|
||||
expect(node.value).toEqual('giraffe');
|
||||
});
|
||||
|
||||
it('should allow setting `value`', function() {
|
||||
var stub = <textarea value="giraffe" />;
|
||||
var node = renderTextarea(stub);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
stub.replaceProps({value: 'gorilla'});
|
||||
expect(node.value).toEqual('gorilla');
|
||||
});
|
||||
|
||||
it('should treat children like `defaultValue`', function() {
|
||||
var stub = <textarea>giraffe</textarea>;
|
||||
var node = renderTextarea(stub);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
// Changing children should do nothing, it functions like `defaultValue`.
|
||||
stub.replaceProps({children: 'gorilla'});
|
||||
expect(node.value).toEqual('giraffe');
|
||||
});
|
||||
|
||||
it('should allow numbers as children', function() {
|
||||
var node = renderTextarea(<textarea>{17}</textarea>);
|
||||
expect(node.value).toBe('17');
|
||||
});
|
||||
|
||||
it("should throw with multiple or invalid children", function() {
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea>{'hello'}{'there'}</textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<textarea><strong /></textarea>
|
||||
);
|
||||
}).toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user