UNPKG

5.81 kBJavaScriptView Raw
1/**
2 * Copyright 2013-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree. An additional grant
7 * of patent rights can be found in the PATENTS file in the same directory.
8 *
9 */
10
11'use strict';
12
13var _prodInvariant = require('./reactProdInvariant'),
14 _assign = require('object-assign');
15
16var DOMChildrenOperations = require('./DOMChildrenOperations');
17var DOMLazyTree = require('./DOMLazyTree');
18var ReactDOMComponentTree = require('./ReactDOMComponentTree');
19
20var escapeTextContentForBrowser = require('./escapeTextContentForBrowser');
21var invariant = require('fbjs/lib/invariant');
22var validateDOMNesting = require('./validateDOMNesting');
23
24/**
25 * Text nodes violate a couple assumptions that React makes about components:
26 *
27 * - When mounting text into the DOM, adjacent text nodes are merged.
28 * - Text nodes cannot be assigned a React root ID.
29 *
30 * This component is used to wrap strings between comment nodes so that they
31 * can undergo the same reconciliation that is applied to elements.
32 *
33 * TODO: Investigate representing React components in the DOM with text nodes.
34 *
35 * @class ReactDOMTextComponent
36 * @extends ReactComponent
37 * @internal
38 */
39var ReactDOMTextComponent = function (text) {
40 // TODO: This is really a ReactText (ReactNode), not a ReactElement
41 this._currentElement = text;
42 this._stringText = '' + text;
43 // ReactDOMComponentTree uses these:
44 this._hostNode = null;
45 this._hostParent = null;
46
47 // Properties
48 this._domID = 0;
49 this._mountIndex = 0;
50 this._closingComment = null;
51 this._commentNodes = null;
52};
53
54_assign(ReactDOMTextComponent.prototype, {
55 /**
56 * Creates the markup for this text node. This node is not intended to have
57 * any features besides containing text content.
58 *
59 * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
60 * @return {string} Markup for this text node.
61 * @internal
62 */
63 mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
64 if (process.env.NODE_ENV !== 'production') {
65 var parentInfo;
66 if (hostParent != null) {
67 parentInfo = hostParent._ancestorInfo;
68 } else if (hostContainerInfo != null) {
69 parentInfo = hostContainerInfo._ancestorInfo;
70 }
71 if (parentInfo) {
72 // parentInfo should always be present except for the top-level
73 // component when server rendering
74 validateDOMNesting(null, this._stringText, this, parentInfo);
75 }
76 }
77
78 var domID = hostContainerInfo._idCounter++;
79 var openingValue = ' react-text: ' + domID + ' ';
80 var closingValue = ' /react-text ';
81 this._domID = domID;
82 this._hostParent = hostParent;
83 if (transaction.useCreateElement) {
84 var ownerDocument = hostContainerInfo._ownerDocument;
85 var openingComment = ownerDocument.createComment(openingValue);
86 var closingComment = ownerDocument.createComment(closingValue);
87 var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
88 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
89 if (this._stringText) {
90 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
91 }
92 DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
93 ReactDOMComponentTree.precacheNode(this, openingComment);
94 this._closingComment = closingComment;
95 return lazyTree;
96 } else {
97 var escapedText = escapeTextContentForBrowser(this._stringText);
98
99 if (transaction.renderToStaticMarkup) {
100 // Normally we'd wrap this between comment nodes for the reasons stated
101 // above, but since this is a situation where React won't take over
102 // (static pages), we can simply return the text as it is.
103 return escapedText;
104 }
105
106 return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
107 }
108 },
109
110 /**
111 * Updates this component by updating the text content.
112 *
113 * @param {ReactText} nextText The next text content
114 * @param {ReactReconcileTransaction} transaction
115 * @internal
116 */
117 receiveComponent: function (nextText, transaction) {
118 if (nextText !== this._currentElement) {
119 this._currentElement = nextText;
120 var nextStringText = '' + nextText;
121 if (nextStringText !== this._stringText) {
122 // TODO: Save this as pending props and use performUpdateIfNecessary
123 // and/or updateComponent to do the actual update for consistency with
124 // other component types?
125 this._stringText = nextStringText;
126 var commentNodes = this.getHostNode();
127 DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
128 }
129 }
130 },
131
132 getHostNode: function () {
133 var hostNode = this._commentNodes;
134 if (hostNode) {
135 return hostNode;
136 }
137 if (!this._closingComment) {
138 var openingComment = ReactDOMComponentTree.getNodeFromInstance(this);
139 var node = openingComment.nextSibling;
140 while (true) {
141 !(node != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing closing comment for text component %s', this._domID) : _prodInvariant('67', this._domID) : void 0;
142 if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
143 this._closingComment = node;
144 break;
145 }
146 node = node.nextSibling;
147 }
148 }
149 hostNode = [this._hostNode, this._closingComment];
150 this._commentNodes = hostNode;
151 return hostNode;
152 },
153
154 unmountComponent: function () {
155 this._closingComment = null;
156 this._commentNodes = null;
157 ReactDOMComponentTree.uncacheNode(this);
158 }
159});
160
161module.exports = ReactDOMTextComponent;
\No newline at end of file