161 lines
5.7 KiB
JavaScript
161 lines
5.7 KiB
JavaScript
|
/**
|
||
|
* Copyright 2013-present, 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.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var _prodInvariant = require('./reactProdInvariant'),
|
||
|
_assign = require('object-assign');
|
||
|
|
||
|
var DOMChildrenOperations = require('./DOMChildrenOperations');
|
||
|
var DOMLazyTree = require('./DOMLazyTree');
|
||
|
var ReactDOMComponentTree = require('./ReactDOMComponentTree');
|
||
|
|
||
|
var escapeTextContentForBrowser = require('./escapeTextContentForBrowser');
|
||
|
var invariant = require('fbjs/lib/invariant');
|
||
|
var validateDOMNesting = require('./validateDOMNesting');
|
||
|
|
||
|
/**
|
||
|
* Text nodes violate a couple assumptions that React makes about components:
|
||
|
*
|
||
|
* - When mounting text into the DOM, adjacent text nodes are merged.
|
||
|
* - Text nodes cannot be assigned a React root ID.
|
||
|
*
|
||
|
* This component is used to wrap strings between comment nodes so that they
|
||
|
* can undergo the same reconciliation that is applied to elements.
|
||
|
*
|
||
|
* TODO: Investigate representing React components in the DOM with text nodes.
|
||
|
*
|
||
|
* @class ReactDOMTextComponent
|
||
|
* @extends ReactComponent
|
||
|
* @internal
|
||
|
*/
|
||
|
var ReactDOMTextComponent = function (text) {
|
||
|
// TODO: This is really a ReactText (ReactNode), not a ReactElement
|
||
|
this._currentElement = text;
|
||
|
this._stringText = '' + text;
|
||
|
// ReactDOMComponentTree uses these:
|
||
|
this._hostNode = null;
|
||
|
this._hostParent = null;
|
||
|
|
||
|
// Properties
|
||
|
this._domID = 0;
|
||
|
this._mountIndex = 0;
|
||
|
this._closingComment = null;
|
||
|
this._commentNodes = null;
|
||
|
};
|
||
|
|
||
|
_assign(ReactDOMTextComponent.prototype, {
|
||
|
/**
|
||
|
* Creates the markup for this text node. This node is not intended to have
|
||
|
* any features besides containing text content.
|
||
|
*
|
||
|
* @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction
|
||
|
* @return {string} Markup for this text node.
|
||
|
* @internal
|
||
|
*/
|
||
|
mountComponent: function (transaction, hostParent, hostContainerInfo, context) {
|
||
|
if (process.env.NODE_ENV !== 'production') {
|
||
|
var parentInfo;
|
||
|
if (hostParent != null) {
|
||
|
parentInfo = hostParent._ancestorInfo;
|
||
|
} else if (hostContainerInfo != null) {
|
||
|
parentInfo = hostContainerInfo._ancestorInfo;
|
||
|
}
|
||
|
if (parentInfo) {
|
||
|
// parentInfo should always be present except for the top-level
|
||
|
// component when server rendering
|
||
|
validateDOMNesting(null, this._stringText, this, parentInfo);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var domID = hostContainerInfo._idCounter++;
|
||
|
var openingValue = ' react-text: ' + domID + ' ';
|
||
|
var closingValue = ' /react-text ';
|
||
|
this._domID = domID;
|
||
|
this._hostParent = hostParent;
|
||
|
if (transaction.useCreateElement) {
|
||
|
var ownerDocument = hostContainerInfo._ownerDocument;
|
||
|
var openingComment = ownerDocument.createComment(openingValue);
|
||
|
var closingComment = ownerDocument.createComment(closingValue);
|
||
|
var lazyTree = DOMLazyTree(ownerDocument.createDocumentFragment());
|
||
|
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(openingComment));
|
||
|
if (this._stringText) {
|
||
|
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(ownerDocument.createTextNode(this._stringText)));
|
||
|
}
|
||
|
DOMLazyTree.queueChild(lazyTree, DOMLazyTree(closingComment));
|
||
|
ReactDOMComponentTree.precacheNode(this, openingComment);
|
||
|
this._closingComment = closingComment;
|
||
|
return lazyTree;
|
||
|
} else {
|
||
|
var escapedText = escapeTextContentForBrowser(this._stringText);
|
||
|
|
||
|
if (transaction.renderToStaticMarkup) {
|
||
|
// Normally we'd wrap this between comment nodes for the reasons stated
|
||
|
// above, but since this is a situation where React won't take over
|
||
|
// (static pages), we can simply return the text as it is.
|
||
|
return escapedText;
|
||
|
}
|
||
|
|
||
|
return '<!--' + openingValue + '-->' + escapedText + '<!--' + closingValue + '-->';
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Updates this component by updating the text content.
|
||
|
*
|
||
|
* @param {ReactText} nextText The next text content
|
||
|
* @param {ReactReconcileTransaction} transaction
|
||
|
* @internal
|
||
|
*/
|
||
|
receiveComponent: function (nextText, transaction) {
|
||
|
if (nextText !== this._currentElement) {
|
||
|
this._currentElement = nextText;
|
||
|
var nextStringText = '' + nextText;
|
||
|
if (nextStringText !== this._stringText) {
|
||
|
// TODO: Save this as pending props and use performUpdateIfNecessary
|
||
|
// and/or updateComponent to do the actual update for consistency with
|
||
|
// other component types?
|
||
|
this._stringText = nextStringText;
|
||
|
var commentNodes = this.getHostNode();
|
||
|
DOMChildrenOperations.replaceDelimitedText(commentNodes[0], commentNodes[1], nextStringText);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getHostNode: function () {
|
||
|
var hostNode = this._commentNodes;
|
||
|
if (hostNode) {
|
||
|
return hostNode;
|
||
|
}
|
||
|
if (!this._closingComment) {
|
||
|
var openingComment = ReactDOMComponentTree.getNodeFromInstance(this);
|
||
|
var node = openingComment.nextSibling;
|
||
|
while (true) {
|
||
|
!(node != null) ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Missing closing comment for text component %s', this._domID) : _prodInvariant('67', this._domID) : void 0;
|
||
|
if (node.nodeType === 8 && node.nodeValue === ' /react-text ') {
|
||
|
this._closingComment = node;
|
||
|
break;
|
||
|
}
|
||
|
node = node.nextSibling;
|
||
|
}
|
||
|
}
|
||
|
hostNode = [this._hostNode, this._closingComment];
|
||
|
this._commentNodes = hostNode;
|
||
|
return hostNode;
|
||
|
},
|
||
|
|
||
|
unmountComponent: function () {
|
||
|
this._closingComment = null;
|
||
|
this._commentNodes = null;
|
||
|
ReactDOMComponentTree.uncacheNode(this);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
module.exports = ReactDOMTextComponent;
|