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.
184 lines
7.0 KiB
184 lines
7.0 KiB
10 years ago
|
/**
|
||
|
* 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 Danger
|
||
|
* @typechecks static-only
|
||
|
*/
|
||
|
|
||
|
/*jslint evil: true, sub: true */
|
||
|
|
||
|
'use strict';
|
||
|
|
||
|
var ExecutionEnvironment = require("./ExecutionEnvironment");
|
||
|
|
||
|
var createNodesFromMarkup = require("./createNodesFromMarkup");
|
||
|
var emptyFunction = require("./emptyFunction");
|
||
|
var getMarkupWrap = require("./getMarkupWrap");
|
||
|
var invariant = require("./invariant");
|
||
|
|
||
|
var OPEN_TAG_NAME_EXP = /^(<[^ \/>]+)/;
|
||
|
var RESULT_INDEX_ATTR = 'data-danger-index';
|
||
|
|
||
|
/**
|
||
|
* Extracts the `nodeName` from a string of markup.
|
||
|
*
|
||
|
* NOTE: Extracting the `nodeName` does not require a regular expression match
|
||
|
* because we make assumptions about React-generated markup (i.e. there are no
|
||
|
* spaces surrounding the opening tag and there is at least one attribute).
|
||
|
*
|
||
|
* @param {string} markup String of markup.
|
||
|
* @return {string} Node name of the supplied markup.
|
||
|
* @see http://jsperf.com/extract-nodename
|
||
|
*/
|
||
|
function getNodeName(markup) {
|
||
|
return markup.substring(1, markup.indexOf(' '));
|
||
|
}
|
||
|
|
||
|
var Danger = {
|
||
|
|
||
|
/**
|
||
|
* Renders markup into an array of nodes. The markup is expected to render
|
||
|
* into a list of root nodes. Also, the length of `resultList` and
|
||
|
* `markupList` should be the same.
|
||
|
*
|
||
|
* @param {array<string>} markupList List of markup strings to render.
|
||
|
* @return {array<DOMElement>} List of rendered nodes.
|
||
|
* @internal
|
||
|
*/
|
||
|
dangerouslyRenderMarkup: function(markupList) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
ExecutionEnvironment.canUseDOM,
|
||
|
'dangerouslyRenderMarkup(...): Cannot render markup in a worker ' +
|
||
|
'thread. Make sure `window` and `document` are available globally ' +
|
||
|
'before requiring React when unit testing or use ' +
|
||
|
'React.renderToString for server rendering.'
|
||
|
) : invariant(ExecutionEnvironment.canUseDOM));
|
||
|
var nodeName;
|
||
|
var markupByNodeName = {};
|
||
|
// Group markup by `nodeName` if a wrap is necessary, else by '*'.
|
||
|
for (var i = 0; i < markupList.length; i++) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
markupList[i],
|
||
|
'dangerouslyRenderMarkup(...): Missing markup.'
|
||
|
) : invariant(markupList[i]));
|
||
|
nodeName = getNodeName(markupList[i]);
|
||
|
nodeName = getMarkupWrap(nodeName) ? nodeName : '*';
|
||
|
markupByNodeName[nodeName] = markupByNodeName[nodeName] || [];
|
||
|
markupByNodeName[nodeName][i] = markupList[i];
|
||
|
}
|
||
|
var resultList = [];
|
||
|
var resultListAssignmentCount = 0;
|
||
|
for (nodeName in markupByNodeName) {
|
||
|
if (!markupByNodeName.hasOwnProperty(nodeName)) {
|
||
|
continue;
|
||
|
}
|
||
|
var markupListByNodeName = markupByNodeName[nodeName];
|
||
|
|
||
|
// This for-in loop skips the holes of the sparse array. The order of
|
||
|
// iteration should follow the order of assignment, which happens to match
|
||
|
// numerical index order, but we don't rely on that.
|
||
|
var resultIndex;
|
||
|
for (resultIndex in markupListByNodeName) {
|
||
|
if (markupListByNodeName.hasOwnProperty(resultIndex)) {
|
||
|
var markup = markupListByNodeName[resultIndex];
|
||
|
|
||
|
// Push the requested markup with an additional RESULT_INDEX_ATTR
|
||
|
// attribute. If the markup does not start with a < character, it
|
||
|
// will be discarded below (with an appropriate console.error).
|
||
|
markupListByNodeName[resultIndex] = markup.replace(
|
||
|
OPEN_TAG_NAME_EXP,
|
||
|
// This index will be parsed back out below.
|
||
|
'$1 ' + RESULT_INDEX_ATTR + '="' + resultIndex + '" '
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Render each group of markup with similar wrapping `nodeName`.
|
||
|
var renderNodes = createNodesFromMarkup(
|
||
|
markupListByNodeName.join(''),
|
||
|
emptyFunction // Do nothing special with <script> tags.
|
||
|
);
|
||
|
|
||
|
for (var j = 0; j < renderNodes.length; ++j) {
|
||
|
var renderNode = renderNodes[j];
|
||
|
if (renderNode.hasAttribute &&
|
||
|
renderNode.hasAttribute(RESULT_INDEX_ATTR)) {
|
||
|
|
||
|
resultIndex = +renderNode.getAttribute(RESULT_INDEX_ATTR);
|
||
|
renderNode.removeAttribute(RESULT_INDEX_ATTR);
|
||
|
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
!resultList.hasOwnProperty(resultIndex),
|
||
|
'Danger: Assigning to an already-occupied result index.'
|
||
|
) : invariant(!resultList.hasOwnProperty(resultIndex)));
|
||
|
|
||
|
resultList[resultIndex] = renderNode;
|
||
|
|
||
|
// This should match resultList.length and markupList.length when
|
||
|
// we're done.
|
||
|
resultListAssignmentCount += 1;
|
||
|
|
||
|
} else if ("production" !== process.env.NODE_ENV) {
|
||
|
console.error(
|
||
|
'Danger: Discarding unexpected node:',
|
||
|
renderNode
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Although resultList was populated out of order, it should now be a dense
|
||
|
// array.
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
resultListAssignmentCount === resultList.length,
|
||
|
'Danger: Did not assign to every index of resultList.'
|
||
|
) : invariant(resultListAssignmentCount === resultList.length));
|
||
|
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
resultList.length === markupList.length,
|
||
|
'Danger: Expected markup to render %s nodes, but rendered %s.',
|
||
|
markupList.length,
|
||
|
resultList.length
|
||
|
) : invariant(resultList.length === markupList.length));
|
||
|
|
||
|
return resultList;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Replaces a node with a string of markup at its current position within its
|
||
|
* parent. The markup must render into a single root node.
|
||
|
*
|
||
|
* @param {DOMElement} oldChild Child node to replace.
|
||
|
* @param {string} markup Markup to render in place of the child node.
|
||
|
* @internal
|
||
|
*/
|
||
|
dangerouslyReplaceNodeWithMarkup: function(oldChild, markup) {
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
ExecutionEnvironment.canUseDOM,
|
||
|
'dangerouslyReplaceNodeWithMarkup(...): Cannot render markup in a ' +
|
||
|
'worker thread. Make sure `window` and `document` are available ' +
|
||
|
'globally before requiring React when unit testing or use ' +
|
||
|
'React.renderToString for server rendering.'
|
||
|
) : invariant(ExecutionEnvironment.canUseDOM));
|
||
|
("production" !== process.env.NODE_ENV ? invariant(markup, 'dangerouslyReplaceNodeWithMarkup(...): Missing markup.') : invariant(markup));
|
||
|
("production" !== process.env.NODE_ENV ? invariant(
|
||
|
oldChild.tagName.toLowerCase() !== 'html',
|
||
|
'dangerouslyReplaceNodeWithMarkup(...): Cannot replace markup of the ' +
|
||
|
'<html> node. This is because browser quirks make this unreliable ' +
|
||
|
'and/or slow. If you want to render to the root you must use ' +
|
||
|
'server rendering. See React.renderToString().'
|
||
|
) : invariant(oldChild.tagName.toLowerCase() !== 'html'));
|
||
|
|
||
|
var newChild = createNodesFromMarkup(markup, emptyFunction)[0];
|
||
|
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||
|
}
|
||
|
|
||
|
};
|
||
|
|
||
|
module.exports = Danger;
|