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
JavaScript
/**
* 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;