react / react-0.13.3 / examples / basic-commonjs / node_modules / reactify / node_modules / react-tools / src / core / ReactMultiChild.js
81152 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 ReactMultiChild9* @typechecks static-only10*/1112"use strict";1314var ReactComponent = require('ReactComponent');15var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');1617var flattenChildren = require('flattenChildren');18var instantiateReactComponent = require('instantiateReactComponent');19var shouldUpdateReactComponent = require('shouldUpdateReactComponent');2021/**22* Updating children of a component may trigger recursive updates. The depth is23* used to batch recursive updates to render markup more efficiently.24*25* @type {number}26* @private27*/28var updateDepth = 0;2930/**31* Queue of update configuration objects.32*33* Each object has a `type` property that is in `ReactMultiChildUpdateTypes`.34*35* @type {array<object>}36* @private37*/38var updateQueue = [];3940/**41* Queue of markup to be rendered.42*43* @type {array<string>}44* @private45*/46var markupQueue = [];4748/**49* Enqueues markup to be rendered and inserted at a supplied index.50*51* @param {string} parentID ID of the parent component.52* @param {string} markup Markup that renders into an element.53* @param {number} toIndex Destination index.54* @private55*/56function enqueueMarkup(parentID, markup, toIndex) {57// NOTE: Null values reduce hidden classes.58updateQueue.push({59parentID: parentID,60parentNode: null,61type: ReactMultiChildUpdateTypes.INSERT_MARKUP,62markupIndex: markupQueue.push(markup) - 1,63textContent: null,64fromIndex: null,65toIndex: toIndex66});67}6869/**70* Enqueues moving an existing element to another index.71*72* @param {string} parentID ID of the parent component.73* @param {number} fromIndex Source index of the existing element.74* @param {number} toIndex Destination index of the element.75* @private76*/77function enqueueMove(parentID, fromIndex, toIndex) {78// NOTE: Null values reduce hidden classes.79updateQueue.push({80parentID: parentID,81parentNode: null,82type: ReactMultiChildUpdateTypes.MOVE_EXISTING,83markupIndex: null,84textContent: null,85fromIndex: fromIndex,86toIndex: toIndex87});88}8990/**91* Enqueues removing an element at an index.92*93* @param {string} parentID ID of the parent component.94* @param {number} fromIndex Index of the element to remove.95* @private96*/97function enqueueRemove(parentID, fromIndex) {98// NOTE: Null values reduce hidden classes.99updateQueue.push({100parentID: parentID,101parentNode: null,102type: ReactMultiChildUpdateTypes.REMOVE_NODE,103markupIndex: null,104textContent: null,105fromIndex: fromIndex,106toIndex: null107});108}109110/**111* Enqueues setting the text content.112*113* @param {string} parentID ID of the parent component.114* @param {string} textContent Text content to set.115* @private116*/117function enqueueTextContent(parentID, textContent) {118// NOTE: Null values reduce hidden classes.119updateQueue.push({120parentID: parentID,121parentNode: null,122type: ReactMultiChildUpdateTypes.TEXT_CONTENT,123markupIndex: null,124textContent: textContent,125fromIndex: null,126toIndex: null127});128}129130/**131* Processes any enqueued updates.132*133* @private134*/135function processQueue() {136if (updateQueue.length) {137ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates(138updateQueue,139markupQueue140);141clearQueue();142}143}144145/**146* Clears any enqueued updates.147*148* @private149*/150function clearQueue() {151updateQueue.length = 0;152markupQueue.length = 0;153}154155/**156* ReactMultiChild are capable of reconciling multiple children.157*158* @class ReactMultiChild159* @internal160*/161var ReactMultiChild = {162163/**164* Provides common functionality for components that must reconcile multiple165* children. This is used by `ReactDOMComponent` to mount, update, and166* unmount child components.167*168* @lends {ReactMultiChild.prototype}169*/170Mixin: {171172/**173* Generates a "mount image" for each of the supplied children. In the case174* of `ReactDOMComponent`, a mount image is a string of markup.175*176* @param {?object} nestedChildren Nested child maps.177* @return {array} An array of mounted representations.178* @internal179*/180mountChildren: function(nestedChildren, transaction) {181var children = flattenChildren(nestedChildren);182var mountImages = [];183var index = 0;184this._renderedChildren = children;185for (var name in children) {186var child = children[name];187if (children.hasOwnProperty(name)) {188// The rendered children must be turned into instances as they're189// mounted.190var childInstance = instantiateReactComponent(child, null);191children[name] = childInstance;192// Inlined for performance, see `ReactInstanceHandles.createReactID`.193var rootID = this._rootNodeID + name;194var mountImage = childInstance.mountComponent(195rootID,196transaction,197this._mountDepth + 1198);199childInstance._mountIndex = index;200mountImages.push(mountImage);201index++;202}203}204return mountImages;205},206207/**208* Replaces any rendered children with a text content string.209*210* @param {string} nextContent String of content.211* @internal212*/213updateTextContent: function(nextContent) {214updateDepth++;215var errorThrown = true;216try {217var prevChildren = this._renderedChildren;218// Remove any rendered children.219for (var name in prevChildren) {220if (prevChildren.hasOwnProperty(name)) {221this._unmountChildByName(prevChildren[name], name);222}223}224// Set new text content.225this.setTextContent(nextContent);226errorThrown = false;227} finally {228updateDepth--;229if (!updateDepth) {230errorThrown ? clearQueue() : processQueue();231}232}233},234235/**236* Updates the rendered children with new children.237*238* @param {?object} nextNestedChildren Nested child maps.239* @param {ReactReconcileTransaction} transaction240* @internal241*/242updateChildren: function(nextNestedChildren, transaction) {243updateDepth++;244var errorThrown = true;245try {246this._updateChildren(nextNestedChildren, transaction);247errorThrown = false;248} finally {249updateDepth--;250if (!updateDepth) {251errorThrown ? clearQueue() : processQueue();252}253}254},255256/**257* Improve performance by isolating this hot code path from the try/catch258* block in `updateChildren`.259*260* @param {?object} nextNestedChildren Nested child maps.261* @param {ReactReconcileTransaction} transaction262* @final263* @protected264*/265_updateChildren: function(nextNestedChildren, transaction) {266var nextChildren = flattenChildren(nextNestedChildren);267var prevChildren = this._renderedChildren;268if (!nextChildren && !prevChildren) {269return;270}271var name;272// `nextIndex` will increment for each child in `nextChildren`, but273// `lastIndex` will be the last index visited in `prevChildren`.274var lastIndex = 0;275var nextIndex = 0;276for (name in nextChildren) {277if (!nextChildren.hasOwnProperty(name)) {278continue;279}280var prevChild = prevChildren && prevChildren[name];281var prevElement = prevChild && prevChild._currentElement;282var nextElement = nextChildren[name];283if (shouldUpdateReactComponent(prevElement, nextElement)) {284this.moveChild(prevChild, nextIndex, lastIndex);285lastIndex = Math.max(prevChild._mountIndex, lastIndex);286prevChild.receiveComponent(nextElement, transaction);287prevChild._mountIndex = nextIndex;288} else {289if (prevChild) {290// Update `lastIndex` before `_mountIndex` gets unset by unmounting.291lastIndex = Math.max(prevChild._mountIndex, lastIndex);292this._unmountChildByName(prevChild, name);293}294// The child must be instantiated before it's mounted.295var nextChildInstance = instantiateReactComponent(296nextElement,297null298);299this._mountChildByNameAtIndex(300nextChildInstance, name, nextIndex, transaction301);302}303nextIndex++;304}305// Remove children that are no longer present.306for (name in prevChildren) {307if (prevChildren.hasOwnProperty(name) &&308!(nextChildren && nextChildren[name])) {309this._unmountChildByName(prevChildren[name], name);310}311}312},313314/**315* Unmounts all rendered children. This should be used to clean up children316* when this component is unmounted.317*318* @internal319*/320unmountChildren: function() {321var renderedChildren = this._renderedChildren;322for (var name in renderedChildren) {323var renderedChild = renderedChildren[name];324// TODO: When is this not true?325if (renderedChild.unmountComponent) {326renderedChild.unmountComponent();327}328}329this._renderedChildren = null;330},331332/**333* Moves a child component to the supplied index.334*335* @param {ReactComponent} child Component to move.336* @param {number} toIndex Destination index of the element.337* @param {number} lastIndex Last index visited of the siblings of `child`.338* @protected339*/340moveChild: function(child, toIndex, lastIndex) {341// If the index of `child` is less than `lastIndex`, then it needs to342// be moved. Otherwise, we do not need to move it because a child will be343// inserted or moved before `child`.344if (child._mountIndex < lastIndex) {345enqueueMove(this._rootNodeID, child._mountIndex, toIndex);346}347},348349/**350* Creates a child component.351*352* @param {ReactComponent} child Component to create.353* @param {string} mountImage Markup to insert.354* @protected355*/356createChild: function(child, mountImage) {357enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex);358},359360/**361* Removes a child component.362*363* @param {ReactComponent} child Child to remove.364* @protected365*/366removeChild: function(child) {367enqueueRemove(this._rootNodeID, child._mountIndex);368},369370/**371* Sets this text content string.372*373* @param {string} textContent Text content to set.374* @protected375*/376setTextContent: function(textContent) {377enqueueTextContent(this._rootNodeID, textContent);378},379380/**381* Mounts a child with the supplied name.382*383* NOTE: This is part of `updateChildren` and is here for readability.384*385* @param {ReactComponent} child Component to mount.386* @param {string} name Name of the child.387* @param {number} index Index at which to insert the child.388* @param {ReactReconcileTransaction} transaction389* @private390*/391_mountChildByNameAtIndex: function(child, name, index, transaction) {392// Inlined for performance, see `ReactInstanceHandles.createReactID`.393var rootID = this._rootNodeID + name;394var mountImage = child.mountComponent(395rootID,396transaction,397this._mountDepth + 1398);399child._mountIndex = index;400this.createChild(child, mountImage);401this._renderedChildren = this._renderedChildren || {};402this._renderedChildren[name] = child;403},404405/**406* Unmounts a rendered child by name.407*408* NOTE: This is part of `updateChildren` and is here for readability.409*410* @param {ReactComponent} child Component to unmount.411* @param {string} name Name of the child in `this._renderedChildren`.412* @private413*/414_unmountChildByName: function(child, name) {415this.removeChild(child);416child._mountIndex = null;417child.unmountComponent();418delete this._renderedChildren[name];419}420421}422423};424425module.exports = ReactMultiChild;426427428