react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / browser / ui / ReactMount.js
81155 views/**1* Copyright 2013-2014, Facebook, Inc.2* All rights reserved.3*4* This source code is licensed under the BSD-style license found in the5* LICENSE file in the root directory of this source tree. An additional grant6* of patent rights can be found in the PATENTS file in the same directory.7*8* @providesModule ReactMount9*/1011"use strict";1213var DOMProperty = require('DOMProperty');14var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');15var ReactCurrentOwner = require('ReactCurrentOwner');16var ReactElement = require('ReactElement');17var ReactLegacyElement = require('ReactLegacyElement');18var ReactInstanceHandles = require('ReactInstanceHandles');19var ReactPerf = require('ReactPerf');2021var containsNode = require('containsNode');22var deprecated = require('deprecated');23var getReactRootElementInContainer = require('getReactRootElementInContainer');24var instantiateReactComponent = require('instantiateReactComponent');25var invariant = require('invariant');26var shouldUpdateReactComponent = require('shouldUpdateReactComponent');27var warning = require('warning');2829var createElement = ReactLegacyElement.wrapCreateElement(30ReactElement.createElement31);3233var SEPARATOR = ReactInstanceHandles.SEPARATOR;3435var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;36var nodeCache = {};3738var ELEMENT_NODE_TYPE = 1;39var DOC_NODE_TYPE = 9;4041/** Mapping from reactRootID to React component instance. */42var instancesByReactRootID = {};4344/** Mapping from reactRootID to `container` nodes. */45var containersByReactRootID = {};4647if (__DEV__) {48/** __DEV__-only mapping from reactRootID to root elements. */49var rootElementsByReactRootID = {};50}5152// Used to store breadth-first search state in findComponentRoot.53var findComponentRootReusableArray = [];5455/**56* @param {DOMElement} container DOM element that may contain a React component.57* @return {?string} A "reactRoot" ID, if a React component is rendered.58*/59function getReactRootID(container) {60var rootElement = getReactRootElementInContainer(container);61return rootElement && ReactMount.getID(rootElement);62}6364/**65* Accessing node[ATTR_NAME] or calling getAttribute(ATTR_NAME) on a form66* element can return its control whose name or ID equals ATTR_NAME. All67* DOM nodes support `getAttributeNode` but this can also get called on68* other objects so just return '' if we're given something other than a69* DOM node (such as window).70*71* @param {?DOMElement|DOMWindow|DOMDocument|DOMTextNode} node DOM node.72* @return {string} ID of the supplied `domNode`.73*/74function getID(node) {75var id = internalGetID(node);76if (id) {77if (nodeCache.hasOwnProperty(id)) {78var cached = nodeCache[id];79if (cached !== node) {80invariant(81!isValid(cached, id),82'ReactMount: Two valid but unequal nodes with the same `%s`: %s',83ATTR_NAME, id84);8586nodeCache[id] = node;87}88} else {89nodeCache[id] = node;90}91}9293return id;94}9596function internalGetID(node) {97// If node is something like a window, document, or text node, none of98// which support attributes or a .getAttribute method, gracefully return99// the empty string, as if the attribute were missing.100return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';101}102103/**104* Sets the React-specific ID of the given node.105*106* @param {DOMElement} node The DOM node whose ID will be set.107* @param {string} id The value of the ID attribute.108*/109function setID(node, id) {110var oldID = internalGetID(node);111if (oldID !== id) {112delete nodeCache[oldID];113}114node.setAttribute(ATTR_NAME, id);115nodeCache[id] = node;116}117118/**119* Finds the node with the supplied React-generated DOM ID.120*121* @param {string} id A React-generated DOM ID.122* @return {DOMElement} DOM node with the suppled `id`.123* @internal124*/125function getNode(id) {126if (!nodeCache.hasOwnProperty(id) || !isValid(nodeCache[id], id)) {127nodeCache[id] = ReactMount.findReactNodeByID(id);128}129return nodeCache[id];130}131132/**133* A node is "valid" if it is contained by a currently mounted container.134*135* This means that the node does not have to be contained by a document in136* order to be considered valid.137*138* @param {?DOMElement} node The candidate DOM node.139* @param {string} id The expected ID of the node.140* @return {boolean} Whether the node is contained by a mounted container.141*/142function isValid(node, id) {143if (node) {144invariant(145internalGetID(node) === id,146'ReactMount: Unexpected modification of `%s`',147ATTR_NAME148);149150var container = ReactMount.findReactContainerForID(id);151if (container && containsNode(container, node)) {152return true;153}154}155156return false;157}158159/**160* Causes the cache to forget about one React-specific ID.161*162* @param {string} id The ID to forget.163*/164function purgeID(id) {165delete nodeCache[id];166}167168var deepestNodeSoFar = null;169function findDeepestCachedAncestorImpl(ancestorID) {170var ancestor = nodeCache[ancestorID];171if (ancestor && isValid(ancestor, ancestorID)) {172deepestNodeSoFar = ancestor;173} else {174// This node isn't populated in the cache, so presumably none of its175// descendants are. Break out of the loop.176return false;177}178}179180/**181* Return the deepest cached node whose ID is a prefix of `targetID`.182*/183function findDeepestCachedAncestor(targetID) {184deepestNodeSoFar = null;185ReactInstanceHandles.traverseAncestors(186targetID,187findDeepestCachedAncestorImpl188);189190var foundNode = deepestNodeSoFar;191deepestNodeSoFar = null;192return foundNode;193}194195/**196* Mounting is the process of initializing a React component by creatings its197* representative DOM elements and inserting them into a supplied `container`.198* Any prior content inside `container` is destroyed in the process.199*200* ReactMount.render(201* component,202* document.getElementById('container')203* );204*205* <div id="container"> <-- Supplied `container`.206* <div data-reactid=".3"> <-- Rendered reactRoot of React207* // ... component.208* </div>209* </div>210*211* Inside of `container`, the first element rendered is the "reactRoot".212*/213var ReactMount = {214/** Exposed for debugging purposes **/215_instancesByReactRootID: instancesByReactRootID,216217/**218* This is a hook provided to support rendering React components while219* ensuring that the apparent scroll position of its `container` does not220* change.221*222* @param {DOMElement} container The `container` being rendered into.223* @param {function} renderCallback This must be called once to do the render.224*/225scrollMonitor: function(container, renderCallback) {226renderCallback();227},228229/**230* Take a component that's already mounted into the DOM and replace its props231* @param {ReactComponent} prevComponent component instance already in the DOM232* @param {ReactComponent} nextComponent component instance to render233* @param {DOMElement} container container to render into234* @param {?function} callback function triggered on completion235*/236_updateRootComponent: function(237prevComponent,238nextComponent,239container,240callback) {241var nextProps = nextComponent.props;242ReactMount.scrollMonitor(container, function() {243prevComponent.replaceProps(nextProps, callback);244});245246if (__DEV__) {247// Record the root element in case it later gets transplanted.248rootElementsByReactRootID[getReactRootID(container)] =249getReactRootElementInContainer(container);250}251252return prevComponent;253},254255/**256* Register a component into the instance map and starts scroll value257* monitoring258* @param {ReactComponent} nextComponent component instance to render259* @param {DOMElement} container container to render into260* @return {string} reactRoot ID prefix261*/262_registerComponent: function(nextComponent, container) {263invariant(264container && (265container.nodeType === ELEMENT_NODE_TYPE ||266container.nodeType === DOC_NODE_TYPE267),268'_registerComponent(...): Target container is not a DOM element.'269);270271ReactBrowserEventEmitter.ensureScrollValueMonitoring();272273var reactRootID = ReactMount.registerContainer(container);274instancesByReactRootID[reactRootID] = nextComponent;275return reactRootID;276},277278/**279* Render a new component into the DOM.280* @param {ReactComponent} nextComponent component instance to render281* @param {DOMElement} container container to render into282* @param {boolean} shouldReuseMarkup if we should skip the markup insertion283* @return {ReactComponent} nextComponent284*/285_renderNewRootComponent: ReactPerf.measure(286'ReactMount',287'_renderNewRootComponent',288function(289nextComponent,290container,291shouldReuseMarkup) {292// Various parts of our code (such as ReactCompositeComponent's293// _renderValidatedComponent) assume that calls to render aren't nested;294// verify that that's the case.295warning(296ReactCurrentOwner.current == null,297'_renderNewRootComponent(): Render methods should be a pure function ' +298'of props and state; triggering nested component updates from ' +299'render is not allowed. If necessary, trigger nested updates in ' +300'componentDidUpdate.'301);302303var componentInstance = instantiateReactComponent(nextComponent, null);304var reactRootID = ReactMount._registerComponent(305componentInstance,306container307);308componentInstance.mountComponentIntoNode(309reactRootID,310container,311shouldReuseMarkup312);313314if (__DEV__) {315// Record the root element in case it later gets transplanted.316rootElementsByReactRootID[reactRootID] =317getReactRootElementInContainer(container);318}319320return componentInstance;321}322),323324/**325* Renders a React component into the DOM in the supplied `container`.326*327* If the React component was previously rendered into `container`, this will328* perform an update on it and only mutate the DOM as necessary to reflect the329* latest React component.330*331* @param {ReactElement} nextElement Component element to render.332* @param {DOMElement} container DOM element to render into.333* @param {?function} callback function triggered on completion334* @return {ReactComponent} Component instance rendered in `container`.335*/336render: function(nextElement, container, callback) {337invariant(338ReactElement.isValidElement(nextElement),339'renderComponent(): Invalid component element.%s',340(341typeof nextElement === 'string' ?342' Instead of passing an element string, make sure to instantiate ' +343'it by passing it to React.createElement.' :344ReactLegacyElement.isValidFactory(nextElement) ?345' Instead of passing a component class, make sure to instantiate ' +346'it by passing it to React.createElement.' :347// Check if it quacks like a element348typeof nextElement.props !== "undefined" ?349' This may be caused by unintentionally loading two independent ' +350'copies of React.' :351''352)353);354355var prevComponent = instancesByReactRootID[getReactRootID(container)];356357if (prevComponent) {358var prevElement = prevComponent._currentElement;359if (shouldUpdateReactComponent(prevElement, nextElement)) {360return ReactMount._updateRootComponent(361prevComponent,362nextElement,363container,364callback365);366} else {367ReactMount.unmountComponentAtNode(container);368}369}370371var reactRootElement = getReactRootElementInContainer(container);372var containerHasReactMarkup =373reactRootElement && ReactMount.isRenderedByReact(reactRootElement);374375var shouldReuseMarkup = containerHasReactMarkup && !prevComponent;376377var component = ReactMount._renderNewRootComponent(378nextElement,379container,380shouldReuseMarkup381);382callback && callback.call(component);383return component;384},385386/**387* Constructs a component instance of `constructor` with `initialProps` and388* renders it into the supplied `container`.389*390* @param {function} constructor React component constructor.391* @param {?object} props Initial props of the component instance.392* @param {DOMElement} container DOM element to render into.393* @return {ReactComponent} Component instance rendered in `container`.394*/395constructAndRenderComponent: function(constructor, props, container) {396var element = createElement(constructor, props);397return ReactMount.render(element, container);398},399400/**401* Constructs a component instance of `constructor` with `initialProps` and402* renders it into a container node identified by supplied `id`.403*404* @param {function} componentConstructor React component constructor405* @param {?object} props Initial props of the component instance.406* @param {string} id ID of the DOM element to render into.407* @return {ReactComponent} Component instance rendered in the container node.408*/409constructAndRenderComponentByID: function(constructor, props, id) {410var domNode = document.getElementById(id);411invariant(412domNode,413'Tried to get element with id of "%s" but it is not present on the page.',414id415);416return ReactMount.constructAndRenderComponent(constructor, props, domNode);417},418419/**420* Registers a container node into which React components will be rendered.421* This also creates the "reactRoot" ID that will be assigned to the element422* rendered within.423*424* @param {DOMElement} container DOM element to register as a container.425* @return {string} The "reactRoot" ID of elements rendered within.426*/427registerContainer: function(container) {428var reactRootID = getReactRootID(container);429if (reactRootID) {430// If one exists, make sure it is a valid "reactRoot" ID.431reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID);432}433if (!reactRootID) {434// No valid "reactRoot" ID found, create one.435reactRootID = ReactInstanceHandles.createReactRootID();436}437containersByReactRootID[reactRootID] = container;438return reactRootID;439},440441/**442* Unmounts and destroys the React component rendered in the `container`.443*444* @param {DOMElement} container DOM element containing a React component.445* @return {boolean} True if a component was found in and unmounted from446* `container`447*/448unmountComponentAtNode: function(container) {449// Various parts of our code (such as ReactCompositeComponent's450// _renderValidatedComponent) assume that calls to render aren't nested;451// verify that that's the case. (Strictly speaking, unmounting won't cause a452// render but we still don't expect to be in a render call here.)453warning(454ReactCurrentOwner.current == null,455'unmountComponentAtNode(): Render methods should be a pure function of ' +456'props and state; triggering nested component updates from render is ' +457'not allowed. If necessary, trigger nested updates in ' +458'componentDidUpdate.'459);460461var reactRootID = getReactRootID(container);462var component = instancesByReactRootID[reactRootID];463if (!component) {464return false;465}466ReactMount.unmountComponentFromNode(component, container);467delete instancesByReactRootID[reactRootID];468delete containersByReactRootID[reactRootID];469if (__DEV__) {470delete rootElementsByReactRootID[reactRootID];471}472return true;473},474475/**476* Unmounts a component and removes it from the DOM.477*478* @param {ReactComponent} instance React component instance.479* @param {DOMElement} container DOM element to unmount from.480* @final481* @internal482* @see {ReactMount.unmountComponentAtNode}483*/484unmountComponentFromNode: function(instance, container) {485instance.unmountComponent();486487if (container.nodeType === DOC_NODE_TYPE) {488container = container.documentElement;489}490491// http://jsperf.com/emptying-a-node492while (container.lastChild) {493container.removeChild(container.lastChild);494}495},496497/**498* Finds the container DOM element that contains React component to which the499* supplied DOM `id` belongs.500*501* @param {string} id The ID of an element rendered by a React component.502* @return {?DOMElement} DOM element that contains the `id`.503*/504findReactContainerForID: function(id) {505var reactRootID = ReactInstanceHandles.getReactRootIDFromNodeID(id);506var container = containersByReactRootID[reactRootID];507508if (__DEV__) {509var rootElement = rootElementsByReactRootID[reactRootID];510if (rootElement && rootElement.parentNode !== container) {511invariant(512// Call internalGetID here because getID calls isValid which calls513// findReactContainerForID (this function).514internalGetID(rootElement) === reactRootID,515'ReactMount: Root element ID differed from reactRootID.'516);517518var containerChild = container.firstChild;519if (containerChild &&520reactRootID === internalGetID(containerChild)) {521// If the container has a new child with the same ID as the old522// root element, then rootElementsByReactRootID[reactRootID] is523// just stale and needs to be updated. The case that deserves a524// warning is when the container is empty.525rootElementsByReactRootID[reactRootID] = containerChild;526} else {527console.warn(528'ReactMount: Root element has been removed from its original ' +529'container. New container:', rootElement.parentNode530);531}532}533}534535return container;536},537538/**539* Finds an element rendered by React with the supplied ID.540*541* @param {string} id ID of a DOM node in the React component.542* @return {DOMElement} Root DOM node of the React component.543*/544findReactNodeByID: function(id) {545var reactRoot = ReactMount.findReactContainerForID(id);546return ReactMount.findComponentRoot(reactRoot, id);547},548549/**550* True if the supplied `node` is rendered by React.551*552* @param {*} node DOM Element to check.553* @return {boolean} True if the DOM Element appears to be rendered by React.554* @internal555*/556isRenderedByReact: function(node) {557if (node.nodeType !== 1) {558// Not a DOMElement, therefore not a React component559return false;560}561var id = ReactMount.getID(node);562return id ? id.charAt(0) === SEPARATOR : false;563},564565/**566* Traverses up the ancestors of the supplied node to find a node that is a567* DOM representation of a React component.568*569* @param {*} node570* @return {?DOMEventTarget}571* @internal572*/573getFirstReactDOM: function(node) {574var current = node;575while (current && current.parentNode !== current) {576if (ReactMount.isRenderedByReact(current)) {577return current;578}579current = current.parentNode;580}581return null;582},583584/**585* Finds a node with the supplied `targetID` inside of the supplied586* `ancestorNode`. Exploits the ID naming scheme to perform the search587* quickly.588*589* @param {DOMEventTarget} ancestorNode Search from this root.590* @pararm {string} targetID ID of the DOM representation of the component.591* @return {DOMEventTarget} DOM node with the supplied `targetID`.592* @internal593*/594findComponentRoot: function(ancestorNode, targetID) {595var firstChildren = findComponentRootReusableArray;596var childIndex = 0;597598var deepestAncestor = findDeepestCachedAncestor(targetID) || ancestorNode;599600firstChildren[0] = deepestAncestor.firstChild;601firstChildren.length = 1;602603while (childIndex < firstChildren.length) {604var child = firstChildren[childIndex++];605var targetChild;606607while (child) {608var childID = ReactMount.getID(child);609if (childID) {610// Even if we find the node we're looking for, we finish looping611// through its siblings to ensure they're cached so that we don't have612// to revisit this node again. Otherwise, we make n^2 calls to getID613// when visiting the many children of a single node in order.614615if (targetID === childID) {616targetChild = child;617} else if (ReactInstanceHandles.isAncestorIDOf(childID, targetID)) {618// If we find a child whose ID is an ancestor of the given ID,619// then we can be sure that we only want to search the subtree620// rooted at this child, so we can throw out the rest of the621// search state.622firstChildren.length = childIndex = 0;623firstChildren.push(child.firstChild);624}625626} else {627// If this child had no ID, then there's a chance that it was628// injected automatically by the browser, as when a `<table>`629// element sprouts an extra `<tbody>` child as a side effect of630// `.innerHTML` parsing. Optimistically continue down this631// branch, but not before examining the other siblings.632firstChildren.push(child.firstChild);633}634635child = child.nextSibling;636}637638if (targetChild) {639// Emptying firstChildren/findComponentRootReusableArray is640// not necessary for correctness, but it helps the GC reclaim641// any nodes that were left at the end of the search.642firstChildren.length = 0;643644return targetChild;645}646}647648firstChildren.length = 0;649650invariant(651false,652'findComponentRoot(..., %s): Unable to find element. This probably ' +653'means the DOM was unexpectedly mutated (e.g., by the browser), ' +654'usually due to forgetting a <tbody> when using tables, nesting tags ' +655'like <form>, <p>, or <a>, or using non-SVG elements in an <svg> ' +656'parent. ' +657'Try inspecting the child nodes of the element with React ID `%s`.',658targetID,659ReactMount.getID(ancestorNode)660);661},662663664/**665* React ID utilities.666*/667668getReactRootID: getReactRootID,669670getID: getID,671672setID: setID,673674getNode: getNode,675676purgeID: purgeID677};678679// Deprecations (remove for 0.13)680ReactMount.renderComponent = deprecated(681'ReactMount',682'renderComponent',683'render',684this,685ReactMount.render686);687688module.exports = ReactMount;689690691