UNPKG

react-pivot

Version:

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

440 lines (406 loc) 14.8 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 ReactComponent */ "use strict"; var ReactElement = require("./ReactElement"); var ReactOwner = require("./ReactOwner"); var ReactUpdates = require("./ReactUpdates"); var assign = require("./Object.assign"); var invariant = require("./invariant"); var keyMirror = require("./keyMirror"); /** * Every React component is in one of these life cycles. */ var ComponentLifeCycle = keyMirror({ /** * Mounted components have a DOM node representation and are capable of * receiving new props. */ MOUNTED: null, /** * Unmounted components are inactive and cannot receive new props. */ UNMOUNTED: null }); var injected = false; /** * Optionally injectable environment dependent cleanup hook. (server vs. * browser etc). Example: A browser system caches DOM nodes based on component * ID and must remove that cache entry when this instance is unmounted. * * @private */ var unmountIDFromEnvironment = null; /** * The "image" of a component tree, is the platform specific (typically * serialized) data that represents a tree of lower level UI building blocks. * On the web, this "image" is HTML markup which describes a construction of * low level `div` and `span` nodes. Other platforms may have different * encoding of this "image". This must be injected. * * @private */ var mountImageIntoNode = null; /** * Components are the basic units of composition in React. * * Every component accepts a set of keyed input parameters known as "props" that * are initialized by the constructor. Once a component is mounted, the props * can be mutated using `setProps` or `replaceProps`. * * Every component is capable of the following operations: * * `mountComponent` * Initializes the component, renders markup, and registers event listeners. * * `receiveComponent` * Updates the rendered DOM nodes to match the given component. * * `unmountComponent` * Releases any resources allocated by this component. * * Components can also be "owned" by other components. Being owned by another * component means being constructed by that component. This is different from * being the child of a component, which means having a DOM representation that * is a child of the DOM representation of that component. * * @class ReactComponent */ var ReactComponent = { injection: { injectEnvironment: function(ReactComponentEnvironment) { ("production" !== process.env.NODE_ENV ? invariant( !injected, 'ReactComponent: injectEnvironment() can only be called once.' ) : invariant(!injected)); mountImageIntoNode = ReactComponentEnvironment.mountImageIntoNode; unmountIDFromEnvironment = ReactComponentEnvironment.unmountIDFromEnvironment; ReactComponent.BackendIDOperations = ReactComponentEnvironment.BackendIDOperations; injected = true; } }, /** * @internal */ LifeCycle: ComponentLifeCycle, /** * Injected module that provides ability to mutate individual properties. * Injected into the base class because many different subclasses need access * to this. * * @internal */ BackendIDOperations: null, /** * Base functionality for every ReactComponent constructor. Mixed into the * `ReactComponent` prototype, but exposed statically for easy access. * * @lends {ReactComponent.prototype} */ Mixin: { /** * Checks whether or not this component is mounted. * * @return {boolean} True if mounted, false otherwise. * @final * @protected */ isMounted: function() { return this._lifeCycleState === ComponentLifeCycle.MOUNTED; }, /** * Sets a subset of the props. * * @param {object} partialProps Subset of the next props. * @param {?function} callback Called after props are updated. * @final * @public */ setProps: function(partialProps, callback) { // Merge with the pending element if it exists, otherwise with existing // element props. var element = this._pendingElement || this._currentElement; this.replaceProps( assign({}, element.props, partialProps), callback ); }, /** * Replaces all of the props. * * @param {object} props New props. * @param {?function} callback Called after props are updated. * @final * @public */ replaceProps: function(props, callback) { ("production" !== process.env.NODE_ENV ? invariant( this.isMounted(), 'replaceProps(...): Can only update a mounted component.' ) : invariant(this.isMounted())); ("production" !== process.env.NODE_ENV ? invariant( this._mountDepth === 0, 'replaceProps(...): You called `setProps` or `replaceProps` on a ' + 'component with a parent. This is an anti-pattern since props will ' + 'get reactively updated when rendered. Instead, change the owner\'s ' + '`render` method to pass the correct value as props to the component ' + 'where it is created.' ) : invariant(this._mountDepth === 0)); // This is a deoptimized path. We optimize for always having a element. // This creates an extra internal element. this._pendingElement = ReactElement.cloneAndReplaceProps( this._pendingElement || this._currentElement, props ); ReactUpdates.enqueueUpdate(this, callback); }, /** * Schedule a partial update to the props. Only used for internal testing. * * @param {object} partialProps Subset of the next props. * @param {?function} callback Called after props are updated. * @final * @internal */ _setPropsInternal: function(partialProps, callback) { // This is a deoptimized path. We optimize for always having a element. // This creates an extra internal element. var element = this._pendingElement || this._currentElement; this._pendingElement = ReactElement.cloneAndReplaceProps( element, assign({}, element.props, partialProps) ); ReactUpdates.enqueueUpdate(this, callback); }, /** * Base constructor for all React components. * * Subclasses that override this method should make sure to invoke * `ReactComponent.Mixin.construct.call(this, ...)`. * * @param {ReactElement} element * @internal */ construct: function(element) { // This is the public exposed props object after it has been processed // with default props. The element's props represents the true internal // state of the props. this.props = element.props; // Record the component responsible for creating this component. // This is accessible through the element but we maintain an extra // field for compatibility with devtools and as a way to make an // incremental update. TODO: Consider deprecating this field. this._owner = element._owner; // All components start unmounted. this._lifeCycleState = ComponentLifeCycle.UNMOUNTED; // See ReactUpdates. this._pendingCallbacks = null; // We keep the old element and a reference to the pending element // to track updates. this._currentElement = element; this._pendingElement = null; }, /** * Initializes the component, renders markup, and registers event listeners. * * NOTE: This does not insert any nodes into the DOM. * * Subclasses that override this method should make sure to invoke * `ReactComponent.Mixin.mountComponent.call(this, ...)`. * * @param {string} rootID DOM ID of the root node. * @param {ReactReconcileTransaction|ReactServerRenderingTransaction} transaction * @param {number} mountDepth number of components in the owner hierarchy. * @return {?string} Rendered markup to be inserted into the DOM. * @internal */ mountComponent: function(rootID, transaction, mountDepth) { ("production" !== process.env.NODE_ENV ? invariant( !this.isMounted(), 'mountComponent(%s, ...): Can only mount an unmounted component. ' + 'Make sure to avoid storing components between renders or reusing a ' + 'single component instance in multiple places.', rootID ) : invariant(!this.isMounted())); var ref = this._currentElement.ref; if (ref != null) { var owner = this._currentElement._owner; ReactOwner.addComponentAsRefTo(this, ref, owner); } this._rootNodeID = rootID; this._lifeCycleState = ComponentLifeCycle.MOUNTED; this._mountDepth = mountDepth; // Effectively: return ''; }, /** * Releases any resources allocated by `mountComponent`. * * NOTE: This does not remove any nodes from the DOM. * * Subclasses that override this method should make sure to invoke * `ReactComponent.Mixin.unmountComponent.call(this)`. * * @internal */ unmountComponent: function() { ("production" !== process.env.NODE_ENV ? invariant( this.isMounted(), 'unmountComponent(): Can only unmount a mounted component.' ) : invariant(this.isMounted())); var ref = this._currentElement.ref; if (ref != null) { ReactOwner.removeComponentAsRefFrom(this, ref, this._owner); } unmountIDFromEnvironment(this._rootNodeID); this._rootNodeID = null; this._lifeCycleState = ComponentLifeCycle.UNMOUNTED; }, /** * Given a new instance of this component, updates the rendered DOM nodes * as if that instance was rendered instead. * * Subclasses that override this method should make sure to invoke * `ReactComponent.Mixin.receiveComponent.call(this, ...)`. * * @param {object} nextComponent Next set of properties. * @param {ReactReconcileTransaction} transaction * @internal */ receiveComponent: function(nextElement, transaction) { ("production" !== process.env.NODE_ENV ? invariant( this.isMounted(), 'receiveComponent(...): Can only update a mounted component.' ) : invariant(this.isMounted())); this._pendingElement = nextElement; this.performUpdateIfNecessary(transaction); }, /** * If `_pendingElement` is set, update the component. * * @param {ReactReconcileTransaction} transaction * @internal */ performUpdateIfNecessary: function(transaction) { if (this._pendingElement == null) { return; } var prevElement = this._currentElement; var nextElement = this._pendingElement; this._currentElement = nextElement; this.props = nextElement.props; this._owner = nextElement._owner; this._pendingElement = null; this.updateComponent(transaction, prevElement); }, /** * Updates the component's currently mounted representation. * * @param {ReactReconcileTransaction} transaction * @param {object} prevElement * @internal */ updateComponent: function(transaction, prevElement) { var nextElement = this._currentElement; // If either the owner or a `ref` has changed, make sure the newest owner // has stored a reference to `this`, and the previous owner (if different) // has forgotten the reference to `this`. We use the element instead // of the public this.props because the post processing cannot determine // a ref. The ref conceptually lives on the element. // TODO: Should this even be possible? The owner cannot change because // it's forbidden by shouldUpdateReactComponent. The ref can change // if you swap the keys of but not the refs. Reconsider where this check // is made. It probably belongs where the key checking and // instantiateReactComponent is done. if (nextElement._owner !== prevElement._owner || nextElement.ref !== prevElement.ref) { if (prevElement.ref != null) { ReactOwner.removeComponentAsRefFrom( this, prevElement.ref, prevElement._owner ); } // Correct, even if the owner is the same, and only the ref has changed. if (nextElement.ref != null) { ReactOwner.addComponentAsRefTo( this, nextElement.ref, nextElement._owner ); } } }, /** * Mounts this component and inserts it into the DOM. * * @param {string} rootID DOM ID of the root node. * @param {DOMElement} container DOM element to mount into. * @param {boolean} shouldReuseMarkup If true, do not insert markup * @final * @internal * @see {ReactMount.render} */ mountComponentIntoNode: function(rootID, container, shouldReuseMarkup) { var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(); transaction.perform( this._mountComponentIntoNode, this, rootID, container, transaction, shouldReuseMarkup ); ReactUpdates.ReactReconcileTransaction.release(transaction); }, /** * @param {string} rootID DOM ID of the root node. * @param {DOMElement} container DOM element to mount into. * @param {ReactReconcileTransaction} transaction * @param {boolean} shouldReuseMarkup If true, do not insert markup * @final * @private */ _mountComponentIntoNode: function( rootID, container, transaction, shouldReuseMarkup) { var markup = this.mountComponent(rootID, transaction, 0); mountImageIntoNode(markup, container, shouldReuseMarkup); }, /** * Checks if this component is owned by the supplied `owner` component. * * @param {ReactComponent} owner Component to check. * @return {boolean} True if `owners` owns this component. * @final * @internal */ isOwnedBy: function(owner) { return this._owner === owner; }, /** * Gets another component, that shares the same owner as this one, by ref. * * @param {string} ref of a sibling Component. * @return {?ReactComponent} the actual sibling Component. * @final * @internal */ getSiblingByRef: function(ref) { var owner = this._owner; if (!owner || !owner.refs) { return null; } return owner.refs[ref]; } } }; module.exports = ReactComponent;