UNPKG

react-pivot

Version:

React-Pivot is a data-grid component with pivot-table-like functionality for data display, filtering, and exploration.

427 lines (398 loc) 12.4 kB
/** * Copyright 2013-2014, 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 ReactMultiChild * @typechecks static-only */ "use strict"; var ReactComponent = require("./ReactComponent"); var ReactMultiChildUpdateTypes = require("./ReactMultiChildUpdateTypes"); var flattenChildren = require("./flattenChildren"); var instantiateReactComponent = require("./instantiateReactComponent"); var shouldUpdateReactComponent = require("./shouldUpdateReactComponent"); /** * Updating children of a component may trigger recursive updates. The depth is * used to batch recursive updates to render markup more efficiently. * * @type {number} * @private */ var updateDepth = 0; /** * Queue of update configuration objects. * * Each object has a `type` property that is in `ReactMultiChildUpdateTypes`. * * @type {array<object>} * @private */ var updateQueue = []; /** * Queue of markup to be rendered. * * @type {array<string>} * @private */ var markupQueue = []; /** * Enqueues markup to be rendered and inserted at a supplied index. * * @param {string} parentID ID of the parent component. * @param {string} markup Markup that renders into an element. * @param {number} toIndex Destination index. * @private */ function enqueueMarkup(parentID, markup, toIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.INSERT_MARKUP, markupIndex: markupQueue.push(markup) - 1, textContent: null, fromIndex: null, toIndex: toIndex }); } /** * Enqueues moving an existing element to another index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Source index of the existing element. * @param {number} toIndex Destination index of the element. * @private */ function enqueueMove(parentID, fromIndex, toIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.MOVE_EXISTING, markupIndex: null, textContent: null, fromIndex: fromIndex, toIndex: toIndex }); } /** * Enqueues removing an element at an index. * * @param {string} parentID ID of the parent component. * @param {number} fromIndex Index of the element to remove. * @private */ function enqueueRemove(parentID, fromIndex) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.REMOVE_NODE, markupIndex: null, textContent: null, fromIndex: fromIndex, toIndex: null }); } /** * Enqueues setting the text content. * * @param {string} parentID ID of the parent component. * @param {string} textContent Text content to set. * @private */ function enqueueTextContent(parentID, textContent) { // NOTE: Null values reduce hidden classes. updateQueue.push({ parentID: parentID, parentNode: null, type: ReactMultiChildUpdateTypes.TEXT_CONTENT, markupIndex: null, textContent: textContent, fromIndex: null, toIndex: null }); } /** * Processes any enqueued updates. * * @private */ function processQueue() { if (updateQueue.length) { ReactComponent.BackendIDOperations.dangerouslyProcessChildrenUpdates( updateQueue, markupQueue ); clearQueue(); } } /** * Clears any enqueued updates. * * @private */ function clearQueue() { updateQueue.length = 0; markupQueue.length = 0; } /** * ReactMultiChild are capable of reconciling multiple children. * * @class ReactMultiChild * @internal */ var ReactMultiChild = { /** * Provides common functionality for components that must reconcile multiple * children. This is used by `ReactDOMComponent` to mount, update, and * unmount child components. * * @lends {ReactMultiChild.prototype} */ Mixin: { /** * Generates a "mount image" for each of the supplied children. In the case * of `ReactDOMComponent`, a mount image is a string of markup. * * @param {?object} nestedChildren Nested child maps. * @return {array} An array of mounted representations. * @internal */ mountChildren: function(nestedChildren, transaction) { var children = flattenChildren(nestedChildren); var mountImages = []; var index = 0; this._renderedChildren = children; for (var name in children) { var child = children[name]; if (children.hasOwnProperty(name)) { // The rendered children must be turned into instances as they're // mounted. var childInstance = instantiateReactComponent(child, null); children[name] = childInstance; // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; var mountImage = childInstance.mountComponent( rootID, transaction, this._mountDepth + 1 ); childInstance._mountIndex = index; mountImages.push(mountImage); index++; } } return mountImages; }, /** * Replaces any rendered children with a text content string. * * @param {string} nextContent String of content. * @internal */ updateTextContent: function(nextContent) { updateDepth++; var errorThrown = true; try { var prevChildren = this._renderedChildren; // Remove any rendered children. for (var name in prevChildren) { if (prevChildren.hasOwnProperty(name)) { this._unmountChildByName(prevChildren[name], name); } } // Set new text content. this.setTextContent(nextContent); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { errorThrown ? clearQueue() : processQueue(); } } }, /** * Updates the rendered children with new children. * * @param {?object} nextNestedChildren Nested child maps. * @param {ReactReconcileTransaction} transaction * @internal */ updateChildren: function(nextNestedChildren, transaction) { updateDepth++; var errorThrown = true; try { this._updateChildren(nextNestedChildren, transaction); errorThrown = false; } finally { updateDepth--; if (!updateDepth) { errorThrown ? clearQueue() : processQueue(); } } }, /** * Improve performance by isolating this hot code path from the try/catch * block in `updateChildren`. * * @param {?object} nextNestedChildren Nested child maps. * @param {ReactReconcileTransaction} transaction * @final * @protected */ _updateChildren: function(nextNestedChildren, transaction) { var nextChildren = flattenChildren(nextNestedChildren); var prevChildren = this._renderedChildren; if (!nextChildren && !prevChildren) { return; } var name; // `nextIndex` will increment for each child in `nextChildren`, but // `lastIndex` will be the last index visited in `prevChildren`. var lastIndex = 0; var nextIndex = 0; for (name in nextChildren) { if (!nextChildren.hasOwnProperty(name)) { continue; } var prevChild = prevChildren && prevChildren[name]; var prevElement = prevChild && prevChild._currentElement; var nextElement = nextChildren[name]; if (shouldUpdateReactComponent(prevElement, nextElement)) { this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex); prevChild.receiveComponent(nextElement, transaction); prevChild._mountIndex = nextIndex; } else { if (prevChild) { // Update `lastIndex` before `_mountIndex` gets unset by unmounting. lastIndex = Math.max(prevChild._mountIndex, lastIndex); this._unmountChildByName(prevChild, name); } // The child must be instantiated before it's mounted. var nextChildInstance = instantiateReactComponent( nextElement, null ); this._mountChildByNameAtIndex( nextChildInstance, name, nextIndex, transaction ); } nextIndex++; } // Remove children that are no longer present. for (name in prevChildren) { if (prevChildren.hasOwnProperty(name) && !(nextChildren && nextChildren[name])) { this._unmountChildByName(prevChildren[name], name); } } }, /** * Unmounts all rendered children. This should be used to clean up children * when this component is unmounted. * * @internal */ unmountChildren: function() { var renderedChildren = this._renderedChildren; for (var name in renderedChildren) { var renderedChild = renderedChildren[name]; // TODO: When is this not true? if (renderedChild.unmountComponent) { renderedChild.unmountComponent(); } } this._renderedChildren = null; }, /** * Moves a child component to the supplied index. * * @param {ReactComponent} child Component to move. * @param {number} toIndex Destination index of the element. * @param {number} lastIndex Last index visited of the siblings of `child`. * @protected */ moveChild: function(child, toIndex, lastIndex) { // If the index of `child` is less than `lastIndex`, then it needs to // be moved. Otherwise, we do not need to move it because a child will be // inserted or moved before `child`. if (child._mountIndex < lastIndex) { enqueueMove(this._rootNodeID, child._mountIndex, toIndex); } }, /** * Creates a child component. * * @param {ReactComponent} child Component to create. * @param {string} mountImage Markup to insert. * @protected */ createChild: function(child, mountImage) { enqueueMarkup(this._rootNodeID, mountImage, child._mountIndex); }, /** * Removes a child component. * * @param {ReactComponent} child Child to remove. * @protected */ removeChild: function(child) { enqueueRemove(this._rootNodeID, child._mountIndex); }, /** * Sets this text content string. * * @param {string} textContent Text content to set. * @protected */ setTextContent: function(textContent) { enqueueTextContent(this._rootNodeID, textContent); }, /** * Mounts a child with the supplied name. * * NOTE: This is part of `updateChildren` and is here for readability. * * @param {ReactComponent} child Component to mount. * @param {string} name Name of the child. * @param {number} index Index at which to insert the child. * @param {ReactReconcileTransaction} transaction * @private */ _mountChildByNameAtIndex: function(child, name, index, transaction) { // Inlined for performance, see `ReactInstanceHandles.createReactID`. var rootID = this._rootNodeID + name; var mountImage = child.mountComponent( rootID, transaction, this._mountDepth + 1 ); child._mountIndex = index; this.createChild(child, mountImage); this._renderedChildren = this._renderedChildren || {}; this._renderedChildren[name] = child; }, /** * Unmounts a rendered child by name. * * NOTE: This is part of `updateChildren` and is here for readability. * * @param {ReactComponent} child Component to unmount. * @param {string} name Name of the child in `this._renderedChildren`. * @private */ _unmountChildByName: function(child, name) { this.removeChild(child); child._mountIndex = null; child.unmountComponent(); delete this._renderedChildren[name]; } } }; module.exports = ReactMultiChild;