diff --git a/src/core/ReactNativeComponent.js b/src/core/ReactNativeComponent.js index 4e5cc25d8a..c5c7aa55fe 100644 --- a/src/core/ReactNativeComponent.js +++ b/src/core/ReactNativeComponent.js @@ -69,7 +69,7 @@ function assertValidProps(props) { * @extends ReactMultiChild */ function ReactNativeComponent(tag, omitClose) { - this._tagOpen = '<' + tag + ' '; + this._tagOpen = '<' + tag; this._tagClose = omitClose ? '' : ''; this.tagName = tag.toUpperCase(); } diff --git a/src/dom/Danger.js b/src/dom/Danger.js index 21fd331fa6..7ec63bfcd2 100644 --- a/src/dom/Danger.js +++ b/src/dom/Danger.js @@ -16,7 +16,7 @@ * @providesModule Danger */ -/*jslint evil: true */ +/*jslint evil: true, sub: true */ "use strict"; @@ -50,12 +50,86 @@ if (__DEV__) { }; } -var dummies = {}; +/** + * Dummy container used to render all markup. + */ +var dummyNode = ExecutionEnvironment.canUseDOM ? + document.createElement('div') : + null; -function getParentDummy(parent) { - var parentTag = parent.tagName; - return dummies[parentTag] || - (dummies[parentTag] = document.createElement(parentTag)); +/** + * 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, ''], + 'legend': [1, '
', '
'], + 'area': [1, '', ''], + 'param': [1, '', ''], + 'thead': [1, '', '
'], + 'tr': [2, '', '
'], + 'col': [2, '', '
'], + 'td': [3, '', '
'] +}; +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, '?
', '
']; + +/** + * 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 + '>'; + if (dummyNode.firstChild) { + markupWrap[nodeName] = null; + } + } + dummyNode.innerHTML = ''; + if (dummyNode.firstChild) { + defaultWrap = null; + } +} + +/** + * Renders markup into nodes. The returned HTMLCollection is live and should be + * used immediately (or at least before the next invocation to `renderMarkup`). + * + * 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). + * @see http://jsperf.com/extract-nodename + * + * @param {string} markup + * @return {*} An HTMLCollection. + */ +function renderMarkup(markup) { + var node = dummyNode; + var nodeName = markup.substring(1, markup.indexOf(' ')); + + var wrap = markupWrap[nodeName.toLowerCase()] || 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; } /** @@ -122,9 +196,7 @@ function dangerouslyInsertMarkupAt(parentNode, markup, index) { if (__DEV__) { validateMarkupParams(parentNode, markup); } - var parentDummy = getParentDummy(parentNode); - parentDummy.innerHTML = markup; - var htmlCollection = parentDummy.childNodes; + var htmlCollection = renderMarkup(markup); var afterNode = index ? parentNode.childNodes[index - 1] : null; inefficientlyInsertHTMLCollectionAfter(parentNode, htmlCollection, afterNode); } @@ -143,9 +215,7 @@ function dangerouslyReplaceNodeWithMarkup(childNode, markup) { if (__DEV__) { validateMarkupParams(parentNode, markup); } - var parentDummy = getParentDummy(parentNode); - parentDummy.innerHTML = markup; - var htmlCollection = parentDummy.childNodes; + var htmlCollection = renderMarkup(markup); if (__DEV__) { throwIf(htmlCollection.length !== 1, NO_MULTI_MARKUP); } diff --git a/src/dom/__tests__/Danger-test.js b/src/dom/__tests__/Danger-test.js new file mode 100644 index 0000000000..480c42f5d7 --- /dev/null +++ b/src/dom/__tests__/Danger-test.js @@ -0,0 +1,69 @@ +/** + * 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. + * + * @jsx React.DOM + * @emails react-core + */ + +/*jslint evil: true */ + +var React = require('React'); + +describe('Danger', function() { + + describe('dangerouslyInsertMarkupAt', function() { + var Danger; + var transaction; + + beforeEach(function() { + require('mock-modules').dumpCache(); + Danger = require('Danger'); + + var ReactReconcileTransaction = require('ReactReconcileTransaction'); + transaction = new ReactReconcileTransaction(); + }); + + it('should render markup', function() { + var markup = (
).mountComponent('.rX', transaction); + var parent = document.createElement('div'); + + Danger.dangerouslyInsertMarkupAt(parent, markup, 0); + + expect(parent.innerHTML).toBe('
'); + }); + + it('should render markup with props', function() { + var markup = (
).mountComponent('.rX', transaction); + var parent = document.createElement('div'); + + Danger.dangerouslyInsertMarkupAt(parent, markup, 0); + + expect(parent.innerHTML).toBe( + '
' + ); + }); + + it('should render wrapped markup', function() { + var markup = ().mountComponent('.rX', transaction); + var parent = document.createElement('div'); + + Danger.dangerouslyInsertMarkupAt(parent, markup, 0); + + expect(parent.innerHTML).toBe(''); + }); + + }); + +}); diff --git a/src/environment/__tests__/ReactServerRendering-test.js b/src/environment/__tests__/ReactServerRendering-test.js index c8cc58ebe5..7e0ddd3007 100644 --- a/src/environment/__tests__/ReactServerRendering-test.js +++ b/src/environment/__tests__/ReactServerRendering-test.js @@ -54,7 +54,7 @@ describe('ReactServerRendering', function() { } ); expect(response).toMatch( - 'hello world' + 'hello world' ); }); @@ -77,8 +77,8 @@ describe('ReactServerRendering', function() { } ); expect(response).toMatch( - '
' + - '' + + '
' + + '' + 'My name is ' + 'child' + '' + @@ -130,7 +130,7 @@ describe('ReactServerRendering', function() { ); expect(response).toMatch( - '' + + '' + 'Component name: ' + 'TestComponent' + ''