mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use createNodesFromMarkup
Pulled out markup rendering logic for better reuse.
This commit is contained in:
committed by
Paul O’Shannessy
parent
dc06704ec7
commit
808e625d9d
@@ -52,9 +52,7 @@ describe('ReactMultiChild', function() {
|
||||
</div>,
|
||||
container
|
||||
);
|
||||
expect(setInnerHTML).toHaveBeenCalled();
|
||||
var callCountOnMount = setInnerHTML.callCount;
|
||||
|
||||
// Warm the cache used by `getMarkupWrap`.
|
||||
React.renderComponent(
|
||||
<div>
|
||||
<p><span /><span /></p>
|
||||
@@ -63,6 +61,17 @@ describe('ReactMultiChild', function() {
|
||||
</div>,
|
||||
container
|
||||
);
|
||||
expect(setInnerHTML).toHaveBeenCalled();
|
||||
var callCountOnMount = setInnerHTML.callCount;
|
||||
|
||||
React.renderComponent(
|
||||
<div>
|
||||
<p><span /><span /><span /></p>
|
||||
<p><span /><span /><span /></p>
|
||||
<p><span /><span /><span /></p>
|
||||
</div>,
|
||||
container
|
||||
);
|
||||
expect(setInnerHTML.callCount).toBe(callCountOnMount + 1);
|
||||
});
|
||||
});
|
||||
|
||||
+24
-93
@@ -23,65 +23,13 @@
|
||||
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
|
||||
var createNodesFromMarkup = require('createNodesFromMarkup');
|
||||
var emptyFunction = require('emptyFunction');
|
||||
var getMarkupWrap = require('getMarkupWrap');
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Dummy container used to render all markup.
|
||||
*/
|
||||
var dummyNode = ExecutionEnvironment.canUseDOM ?
|
||||
document.createElement('div') :
|
||||
null;
|
||||
|
||||
/**
|
||||
* Some browsers cannot use `innerHTML` to render certain elements standalone,
|
||||
* so we wrap them, render the wrapped nodes, then extract the desired node.
|
||||
*/
|
||||
var markupWrap = {
|
||||
'option': [1, '<select multiple="true">', '</select>'],
|
||||
'legend': [1, '<fieldset>', '</fieldset>'],
|
||||
'area': [1, '<map>', '</map>'],
|
||||
'param': [1, '<object>', '</object>'],
|
||||
'thead': [1, '<table>', '</table>'],
|
||||
'tr': [2, '<table><tbody>', '</tbody></table>'],
|
||||
'col': [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
|
||||
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>']
|
||||
};
|
||||
markupWrap['optgroup'] = markupWrap['option'];
|
||||
markupWrap['tbody'] = markupWrap['thead'];
|
||||
markupWrap['tfoot'] = markupWrap['thead'];
|
||||
markupWrap['colgroup'] = markupWrap['thead'];
|
||||
markupWrap['caption'] = markupWrap['thead'];
|
||||
markupWrap['th'] = markupWrap['td'];
|
||||
|
||||
/**
|
||||
* In IE8, certain elements cannot render alone, so wrap all elements.
|
||||
*/
|
||||
var defaultWrap = [1, '?<div>', '</div>'];
|
||||
|
||||
/**
|
||||
* Feature detection, remove wraps that are unnecessary for the current browser.
|
||||
*/
|
||||
if (dummyNode) {
|
||||
for (var nodeName in markupWrap) {
|
||||
if (!markupWrap.hasOwnProperty(nodeName)) {
|
||||
continue;
|
||||
}
|
||||
dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';
|
||||
if (dummyNode.firstChild) {
|
||||
markupWrap[nodeName] = null;
|
||||
}
|
||||
}
|
||||
dummyNode.innerHTML = '<link />';
|
||||
if (dummyNode.firstChild) {
|
||||
defaultWrap = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the `nodeName` from a string of markup. This 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 an ID attribute).
|
||||
* 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
|
||||
@@ -95,31 +43,6 @@ function getNodeName(markup) {
|
||||
return markup.substring(1, markup.indexOf(' '));
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders markup into nodes. The returned HTMLCollection is live and should be
|
||||
* used immediately (or at least before the next invocation to `renderMarkup`).
|
||||
*
|
||||
* @param {string} markup Markup for one or more nodes with the same `nodeName`.
|
||||
* @param {?string} nodeName Optional, the lowercase node name of the markup.
|
||||
* @return {*} An HTMLCollection.
|
||||
*/
|
||||
function renderMarkup(markup, nodeName) {
|
||||
nodeName = nodeName || getNodeName(markup);
|
||||
var node = dummyNode;
|
||||
var wrap = markupWrap[nodeName] || defaultWrap;
|
||||
if (wrap) {
|
||||
node.innerHTML = wrap[1] + markup + wrap[2];
|
||||
|
||||
var wrapDepth = wrap[0];
|
||||
while (wrapDepth--) {
|
||||
node = node.lastChild;
|
||||
}
|
||||
} else {
|
||||
node.innerHTML = markup;
|
||||
}
|
||||
return node.childNodes;
|
||||
}
|
||||
|
||||
var Danger = {
|
||||
|
||||
/**
|
||||
@@ -147,31 +70,39 @@ var Danger = {
|
||||
'dangerouslyRenderMarkup(...): Missing markup.'
|
||||
);
|
||||
nodeName = getNodeName(markupList[i]);
|
||||
nodeName = markupWrap[nodeName] ? nodeName : '*';
|
||||
nodeName = getMarkupWrap(nodeName) ? nodeName : '*';
|
||||
markupByNodeName[nodeName] = markupByNodeName[nodeName] || [];
|
||||
markupByNodeName[nodeName][i] = markupList[i];
|
||||
}
|
||||
var renderedMarkup = [];
|
||||
var resultList = [];
|
||||
for (nodeName in markupByNodeName) {
|
||||
if (!markupByNodeName.hasOwnProperty(nodeName)) {
|
||||
continue;
|
||||
}
|
||||
var markupListByNodeName = markupByNodeName[nodeName];
|
||||
var markup = markupListByNodeName.join('');
|
||||
// Render each group of markup.
|
||||
var childNode = renderMarkup(markup, nodeName)[0];
|
||||
// Render each group of markup with similar `nodeName`.
|
||||
var renderNodes = createNodesFromMarkup(markup, emptyFunction);
|
||||
// Restore the initial ordering.
|
||||
for (var j = 0; j < markupListByNodeName.length; j++) {
|
||||
var renderIndex = 0;
|
||||
var resultIndex = 0;
|
||||
while (resultIndex < markupListByNodeName.length) {
|
||||
// `markupListByNodeName` may be a sparse array.
|
||||
if (markupListByNodeName[j]) {
|
||||
invariant(childNode, 'dangerouslyRenderMarkup(...): Missing node.');
|
||||
renderedMarkup[j] = childNode;
|
||||
childNode = childNode.nextSibling;
|
||||
if (markupListByNodeName[resultIndex]) {
|
||||
invariant(
|
||||
resultList[resultIndex] = renderNodes[renderIndex],
|
||||
'dangerouslyRenderMarkup(...): Missing node.'
|
||||
);
|
||||
renderIndex++;
|
||||
}
|
||||
resultIndex++;
|
||||
}
|
||||
invariant(!childNode, 'dangerouslyRenderMarkupO(...): Unexpected nodes.');
|
||||
invariant(
|
||||
renderIndex === renderNodes.length,
|
||||
'dangerouslyRenderMarkupO(...): Unexpected nodes.'
|
||||
);
|
||||
}
|
||||
return renderedMarkup;
|
||||
return resultList;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -190,7 +121,7 @@ var Danger = {
|
||||
'immediately.'
|
||||
);
|
||||
invariant(markup, 'dangerouslyReplaceNodeWithMarkup(...): Missing markup.');
|
||||
var newChild = renderMarkup(markup)[0];
|
||||
var newChild = createNodesFromMarkup(markup, emptyFunction)[0];
|
||||
oldChild.parentNode.replaceChild(newChild, oldChild);
|
||||
}
|
||||
|
||||
|
||||
Vendored
+56
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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 createArrayFrom
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
var hasArrayNature = require('hasArrayNature');
|
||||
|
||||
/**
|
||||
* Ensure that the argument is an array by wrapping it in an array if it is not.
|
||||
* Creates a copy of the argument if it is already an array.
|
||||
*
|
||||
* This is mostly useful idiomatically:
|
||||
*
|
||||
* var createArrayFrom = require('createArrayFrom');
|
||||
*
|
||||
* function takesOneOrMoreThings(things) {
|
||||
* things = createArrayFrom(things);
|
||||
* ...
|
||||
* }
|
||||
*
|
||||
* This allows you to treat `things' as an array, but accept scalars in the API.
|
||||
*
|
||||
* This is also good for converting certain pseudo-arrays, like `arguments` or
|
||||
* HTMLCollections, into arrays.
|
||||
*
|
||||
* @param {*} obj
|
||||
* @return {array}
|
||||
*/
|
||||
function createArrayFrom(obj) {
|
||||
if (!hasArrayNature(obj)) {
|
||||
return [obj];
|
||||
}
|
||||
if (obj.item) {
|
||||
// IE does not support Array#slice on HTMLCollections
|
||||
var l = obj.length, ret = new Array(l);
|
||||
while (l--) { ret[l] = obj[l]; }
|
||||
return ret;
|
||||
}
|
||||
return Array.prototype.slice.call(obj);
|
||||
}
|
||||
|
||||
module.exports = createArrayFrom;
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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 createNodesFromMarkup
|
||||
* @typechecks
|
||||
*/
|
||||
|
||||
/*jslint evil: true, sub: true */
|
||||
|
||||
var createArrayFrom = require('createArrayFrom');
|
||||
var getMarkupWrap = require('getMarkupWrap');
|
||||
var invariant = require('invariant');
|
||||
|
||||
/**
|
||||
* Dummy container used to render all markup.
|
||||
*/
|
||||
var dummyNode = document.createElement('div');
|
||||
|
||||
/**
|
||||
* Pattern used by `getNodeName`.
|
||||
*/
|
||||
var nodeNamePattern = /^\s*<(\w+)/;
|
||||
|
||||
/**
|
||||
* Extracts the `nodeName` of the first element in a string of markup.
|
||||
*
|
||||
* @param {string} markup String of markup.
|
||||
* @return {?string} Node name of the supplied markup.
|
||||
*/
|
||||
function getNodeName(markup) {
|
||||
var nodeNameMatch = markup.match(nodeNamePattern);
|
||||
return nodeNameMatch && nodeNameMatch[1].toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array containing the nodes rendered from the supplied markup. The
|
||||
* optionally supplied `handleScript` function will be invoked once for each
|
||||
* <script> element that is rendered. If no `handleScript` function is supplied,
|
||||
* an exception is thrown if any <script> elements are rendered.
|
||||
*
|
||||
* @param {string} markup A string of valid HTML markup.
|
||||
* @param {?function} handleScript Invoked once for each rendered <script>.
|
||||
* @return {array<DOMElement|DOMTextNode>} An array of rendered nodes.
|
||||
*/
|
||||
function createNodesFromMarkup(markup, handleScript) {
|
||||
var node = dummyNode;
|
||||
var nodeName = getNodeName(markup);
|
||||
|
||||
var wrap = nodeName && getMarkupWrap(nodeName);
|
||||
if (wrap) {
|
||||
node.innerHTML = wrap[1] + markup + wrap[2];
|
||||
|
||||
var wrapDepth = wrap[0];
|
||||
while (wrapDepth--) {
|
||||
node = node.lastChild;
|
||||
}
|
||||
} else {
|
||||
node.innerHTML = markup;
|
||||
}
|
||||
|
||||
var scripts = node.getElementsByTagName('script');
|
||||
if (scripts.length) {
|
||||
invariant(
|
||||
handleScript,
|
||||
'createNodesFromMarkup(...): Unexpected <script> element rendered.'
|
||||
);
|
||||
createArrayFrom(scripts).forEach(handleScript);
|
||||
}
|
||||
|
||||
var nodes = createArrayFrom(node.childNodes);
|
||||
while (node.lastChild) {
|
||||
node.removeChild(node.lastChild);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
module.exports = createNodesFromMarkup;
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* 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 getMarkupWrap
|
||||
*/
|
||||
|
||||
/**
|
||||
* Dummy container used to detect which wraps are necessary.
|
||||
*/
|
||||
var dummyNode = document.createElement('div');
|
||||
|
||||
/**
|
||||
* Some browsers cannot use `innerHTML` to render certain elements standalone,
|
||||
* so we wrap them, render the wrapped nodes, then extract the desired node.
|
||||
*
|
||||
* In IE8, certain elements cannot render alone, so wrap all elements ('*').
|
||||
*/
|
||||
var shouldWrap = {};
|
||||
var markupWrap = {
|
||||
'area': [1, '<map>', '</map>'],
|
||||
'caption': [1, '<table>', '</table>'],
|
||||
'col': [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
|
||||
'colgroup': [1, '<table>', '</table>'],
|
||||
'legend': [1, '<fieldset>', '</fieldset>'],
|
||||
'optgroup': [1, '<select multiple="true">', '</select>'],
|
||||
'option': [1, '<select multiple="true">', '</select>'],
|
||||
'param': [1, '<object>', '</object>'],
|
||||
'tbody': [1, '<table>', '</table>'],
|
||||
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
|
||||
'tfoot': [1, '<table>', '</table>'],
|
||||
'th': [3, '<table><tbody><tr>', '</tr></tbody></table>'],
|
||||
'thead': [1, '<table>', '</table>'],
|
||||
'tr': [2, '<table><tbody>', '</tbody></table>'],
|
||||
'*': [1, '?<div>', '</div>']
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the markup wrap configuration for the supplied `nodeName`.
|
||||
*
|
||||
* NOTE: This lazily detects which wraps are necessary for the current browser.
|
||||
*
|
||||
* @param {string} nodeName Lowercase `nodeName`.
|
||||
* @return {?array} Markup wrap configuration, if applicable.
|
||||
*/
|
||||
function getMarkupWrap(nodeName) {
|
||||
if (!markupWrap.hasOwnProperty(nodeName)) {
|
||||
nodeName = '*';
|
||||
}
|
||||
if (!shouldWrap.hasOwnProperty(nodeName)) {
|
||||
if (nodeName === '*') {
|
||||
dummyNode.innerHTML = '<link />';
|
||||
} else {
|
||||
dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';
|
||||
}
|
||||
shouldWrap[nodeName] = !dummyNode.firstChild;
|
||||
}
|
||||
return shouldWrap[nodeName] ? markupWrap[nodeName] : null;
|
||||
}
|
||||
|
||||
|
||||
module.exports = getMarkupWrap;
|
||||
Reference in New Issue
Block a user