@samitouri / QOS-React-2 / commits / 55176116a2
Fork 0

The library for web and native user interfaces.

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).

CommitSyncScript committed Jun 28, 2013 at 16:33 UTC 55176116a225c7664d082580faa6dde12a3bb00e
5 files changed +224 -162
src/core/ReactDOM.js
+2 -4
@@ -80,8 +80,7 @@ var ReactDOM = objMapKeyVal({
80 embed: true,
81 fieldset: false,
82 footer: false,
83 - // Danger: this gets monkeypatched! See ReactDOMForm for more info.
84 - form: false,
83 + form: false, // NOTE: Injected, see `ReactDOMForm`.
84 h1: false,
85 h2: false,
86 h3: false,
@@ -116,8 +115,7 @@ var ReactDOM = objMapKeyVal({
115 table: false,
116 tbody: false,
117 td: false,
119 - // Danger: this gets monkeypatched! See ReactDOMTextarea for more info.
120 - textarea: false,
118 + textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
119 tfoot: false,
120 th: false,
121 thead: false,
src/core/ReactDOMTextarea.js deleted
-92
@@ -1,92 +0,0 @@
1 -/**
2 - * Copyright 2013 Facebook, Inc.
3 - *
4 - * Licensed under the Apache License, Version 2.0 (the "License");
5 - * you may not use this file except in compliance with the License.
6 - * You may obtain a copy of the License at
7 - *
8 - * http://www.apache.org/licenses/LICENSE-2.0
9 - *
10 - * Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS,
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 - * See the License for the specific language governing permissions and
14 - * limitations under the License.
15 - *
16 - * @providesModule ReactDOMTextarea
17 - */
18 -
19 -"use strict";
20 -
21 -var ReactCompositeComponent = require('ReactCompositeComponent');
22 -var ReactDOM = require('ReactDOM');
23 -
24 -var invariant = require('invariant');
25 -
26 -// Store a reference to the <textarea> `ReactNativeComponent`.
27 -var textarea = ReactDOM.textarea;
28 -
29 -// For quickly matching children type, to test if can be treated as content.
30 -var CONTENT_TYPES = {'string': true, 'number': true};
31 -
32 -var getTextContent = function(props) {
33 - if (!props) {
34 - return '';
35 - }
36 -
37 - invariant(
38 - props.dangerouslySetInnerHTML == null,
39 - '`dangerouslySetInnerHTML` does not make sense on textarea.'
40 - );
41 -
42 - var content;
43 - if (Array.isArray(props.children)) {
44 - invariant(
45 - props.children.length <= 1,
46 - 'textarea can have at most one child'
47 - );
48 - content = props.children[0];
49 - } else if (props.children != null) {
50 - content = props.children;
51 - } else {
52 - content = props.content;
53 - }
54 -
55 - invariant(
56 - content == null || CONTENT_TYPES[typeof content],
57 - 'textarea must contain a single string or number, not an array or ' +
58 - 'object.'
59 - );
60 - return content != null ? '' + content : '';
61 -};
62 -
63 -/**
64 - * Since setting .textContent on a dirty <textarea> doesn't update its value,
65 - * we intercept prop changes here to make sure that value is updated.
66 - */
67 -var ReactDOMTextarea = ReactCompositeComponent.createClass({
68 - getInitialState: function() {
69 - // We keep the original value of content or children here so that
70 - // ReactNativeComponent doesn't update textContent (unnecessary since we
71 - // update value).
72 - return {
73 - initialContent: getTextContent(this.props)
74 - };
75 - },
76 -
77 - render: function() {
78 - return this.transferPropsTo(textarea({
79 - content: this.state.initialContent
80 - }));
81 - },
82 -
83 - componentDidUpdate: function(prevProps, prevState, rootNode) {
84 - var oldContent = getTextContent(prevProps);
85 - var newContent = getTextContent(this.props);
86 - if (oldContent !== newContent && rootNode.value !== newContent) {
87 - rootNode.value = newContent;
88 - }
89 - }
90 -});
91 -
92 -module.exports = ReactDOMTextarea;
src/core/__tests__/ReactDOMTextarea-test.js deleted
-66
@@ -1,66 +0,0 @@
1 -/**
2 - * Copyright 2013 Facebook, Inc.
3 - *
4 - * Licensed under the Apache License, Version 2.0 (the "License");
5 - * you may not use this file except in compliance with the License.
6 - * You may obtain a copy of the License at
7 - *
8 - * http://www.apache.org/licenses/LICENSE-2.0
9 - *
10 - * Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS,
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 - * See the License for the specific language governing permissions and
14 - * limitations under the License.
15 - *
16 - * @jsx React.DOM
17 - * @emails react-core
18 - */
19 -
20 -"use strict";
21 -
22 -describe('ReactDOMTextarea', function() {
23 - var React;
24 - var ReactTestUtils;
25 -
26 - beforeEach(function() {
27 - React = require('React');
28 - ReactTestUtils = require('ReactTestUtils');
29 - });
30 -
31 - it("should update value", function() {
32 - var stub = ReactTestUtils.renderIntoDocument(<textarea>giraffe</textarea>);
33 - var node = stub.getDOMNode();
34 -
35 - expect(node.value).toEqual('giraffe');
36 -
37 - stub.replaceProps({ children: 'gorilla' });
38 - expect(node.value).toEqual('gorilla');
39 -
40 - stub.replaceProps({ children: 17 });
41 - expect(node.value).toEqual('17');
42 -
43 - stub.replaceProps({ children: [42] });
44 - expect(node.value).toEqual('42');
45 -
46 - stub.replaceProps({ children: null });
47 - expect(node.value).toEqual('');
48 -
49 - stub.replaceProps({ content: 'eggplant' });
50 - expect(node.value).toEqual('eggplant');
51 - });
52 -
53 - it("should throw with multiple or invalid children", function() {
54 - expect(function() {
55 - ReactTestUtils.renderIntoDocument(
56 - <textarea>{'hello'}{'there'}</textarea>
57 - );
58 - }).toThrow();
59 -
60 - expect(function() {
61 - ReactTestUtils.renderIntoDocument(
62 - <textarea><strong /></textarea>
63 - );
64 - }).toThrow();
65 - });
66 -});
src/dom/components/ReactDOMTextarea.js new
+129
@@ -0,0 +1,129 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @providesModule ReactDOMTextarea
17 + */
18 +
19 +"use strict";
20 +
21 +var DOMPropertyOperations = require('DOMPropertyOperations');
22 +var ReactCompositeComponent = require('ReactCompositeComponent');
23 +var ReactDOM = require('ReactDOM');
24 +
25 +var invariant = require('invariant');
26 +var merge = require('merge');
27 +
28 +// Store a reference to the <textarea> `ReactNativeComponent`.
29 +var textarea = ReactDOM.textarea;
30 +
31 +// For quickly matching children type, to test if can be treated as content.
32 +var CONTENT_TYPES = {'string': true, 'number': true};
33 +
34 +/**
35 + * Implements a <textarea> native component that allows setting `value`, and
36 + * `defaultValue`. This differs from the traditional DOM API because value is
37 + * usually set as PCDATA children.
38 + *
39 + * If `value` is not supplied (or null/undefined), user actions that affect the
40 + * value will trigger updates to the element.
41 + *
42 + * If `value` is supplied (and not null/undefined), the rendered element will
43 + * not trigger updates to the element. Instead, the `value` prop must change in
44 + * order for the rendered element to be updated.
45 + *
46 + * The rendered element will be initialized with an empty value, the prop
47 + * `defaultValue` if specified, or the children content (deprecated).
48 + */
49 +var ReactDOMTextarea = ReactCompositeComponent.createClass({
50 +
51 + getInitialState: function() {
52 + var defaultValue = this.props.defaultValue;
53 + // TODO (yungsters): Remove support for children content in <textarea>.
54 + var children = this.props.children;
55 + if (children != null) {
56 + global.console && console.warn && console.warn(
57 + 'Use the `defaultValue` or `value` props instead of setting children ' +
58 + 'on <textarea>.'
59 + );
60 + invariant(
61 + defaultValue == null,
62 + 'If you supply `defaultValue` on a <textarea>, do not pass children.'
63 + );
64 + if (Array.isArray(children)) {
65 + invariant(
66 + children.length <= 1,
67 + '<textarea> can only have at most one child.'
68 + );
69 + children = children[0];
70 + }
71 + invariant(
72 + CONTENT_TYPES[typeof children],
73 + 'If you specify children to <textarea>, it must be a single string ' +
74 + 'or number., not an array or object.'
75 + );
76 + defaultValue = '' + children;
77 + }
78 + defaultValue = defaultValue || '';
79 + return {
80 + // We save the initial value so that `ReactNativeComponent` doesn't update
81 + // `textContent` (unnecessary since we update value).
82 + initialValue: this.props.value != null ? this.props.value : defaultValue,
83 + value: defaultValue
84 + };
85 + },
86 +
87 + getValue: function() {
88 + return this.props.value != null ? this.props.value : this.state.value;
89 + },
90 +
91 + render: function() {
92 + // Clone `this.props` so we don't mutate the input.
93 + var props = merge(this.props);
94 +
95 + invariant(
96 + props.dangerouslySetInnerHTML == null,
97 + '`dangerouslySetInnerHTML` does not make sense on <textarea>.'
98 + );
99 +
100 + props.value = this.getValue();
101 + props.onChange = this.handleChange;
102 +
103 + // Always set children to the same thing. In IE9, the selection range will
104 + // get reset if `textContent` is mutated.
105 + return textarea(props, this.state.initialValue);
106 + },
107 +
108 + componentDidUpdate: function(prevProps, prevState, rootNode) {
109 + if (this.props.value != null) {
110 + DOMPropertyOperations.setValueForProperty(
111 + rootNode,
112 + 'value',
113 + this.props.value || ''
114 + );
115 + }
116 + },
117 +
118 + handleChange: ReactCompositeComponent.autoBind(function(event) {
119 + var returnValue;
120 + if (this.props.onChange) {
121 + returnValue = this.props.onChange(event);
122 + }
123 + this.setState({value: event.target.value});
124 + return returnValue;
125 + })
126 +
127 +});
128 +
129 +module.exports = ReactDOMTextarea;
src/dom/components/__tests__/ReactDOMTextarea-test.js new
+93
@@ -0,0 +1,93 @@
1 +/**
2 + * Copyright 2013 Facebook, Inc.
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + *
16 + * @jsx React.DOM
17 + * @emails react-core
18 + */
19 +
20 +"use strict";
21 +
22 +/*jshint evil:true */
23 +
24 +describe('ReactDOMTextarea', function() {
25 + var React;
26 + var ReactTestUtils;
27 +
28 + var renderTextarea;
29 +
30 + beforeEach(function() {
31 + React = require('React');
32 + ReactTestUtils = require('ReactTestUtils');
33 +
34 + renderTextarea = function(component) {
35 + var stub = ReactTestUtils.renderIntoDocument(component);
36 + var node = stub.getDOMNode();
37 + // Polyfilling the browser's quirky behavior.
38 + node.value = node.innerHTML;
39 + return node;
40 + };
41 + });
42 +
43 + it('should allow setting `defaultValue`', function() {
44 + var stub = <textarea defaultValue="giraffe" />;
45 + var node = renderTextarea(stub);
46 +
47 + expect(node.value).toBe('giraffe');
48 +
49 + // Changing `defaultValue` should do nothing.
50 + stub.replaceProps({defaultValue: 'gorilla'});
51 + expect(node.value).toEqual('giraffe');
52 + });
53 +
54 + it('should allow setting `value`', function() {
55 + var stub = <textarea value="giraffe" />;
56 + var node = renderTextarea(stub);
57 +
58 + expect(node.value).toBe('giraffe');
59 +
60 + stub.replaceProps({value: 'gorilla'});
61 + expect(node.value).toEqual('gorilla');
62 + });
63 +
64 + it('should treat children like `defaultValue`', function() {
65 + var stub = <textarea>giraffe</textarea>;
66 + var node = renderTextarea(stub);
67 +
68 + expect(node.value).toBe('giraffe');
69 +
70 + // Changing children should do nothing, it functions like `defaultValue`.
71 + stub.replaceProps({children: 'gorilla'});
72 + expect(node.value).toEqual('giraffe');
73 + });
74 +
75 + it('should allow numbers as children', function() {
76 + var node = renderTextarea(<textarea>{17}</textarea>);
77 + expect(node.value).toBe('17');
78 + });
79 +
80 + it("should throw with multiple or invalid children", function() {
81 + expect(function() {
82 + ReactTestUtils.renderIntoDocument(
83 + <textarea>{'hello'}{'there'}</textarea>
84 + );
85 + }).toThrow();
86 +
87 + expect(function() {
88 + ReactTestUtils.renderIntoDocument(
89 + <textarea><strong /></textarea>
90 + );
91 + }).toThrow();
92 + });
93 +});