UNPKG

react-transclusion

Version:

Render arbitrary components into outlets for use in dynamic layouts.

211 lines (183 loc) 6.21 kB
'use strict'; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var React = require('react'); var PropTypes = React.PropTypes; var _React$PropTypes = React.PropTypes, string = _React$PropTypes.string, object = _React$PropTypes.object, node = _React$PropTypes.node, bool = _React$PropTypes.bool, func = _React$PropTypes.func; /** * @namespace UI.Components * * An outlet is a container element that allows you to render other components * inside of it. These elements may be registered at boot-time, and they * will be rendered in the correct place in the UI at the correct time. */ module.exports = React.createClass({ displayName: 'Outlet', contextTypes: { outletManager: PropTypes.object.isRequired }, propTypes: { /** * @property {String} * A unique name for this outlet. Elements will use this name to * plug into it. */ name: string.isRequired, /** * @property {String} [tagName="div"] * The HTML tag to use for the outlet root node. */ tagName: string, /** * @property {Object} [tagProps={}] */ tagProps: object, /** * @property {Object} [elementProps={}] * The props to inject into the rendered elements, if any. */ elementProps: object, options: object, /** * @property {React.Component} * * Children passed to the outlet are handled in a special way based on * the flags you specify. Generally, if children were rendered, they will * be placed *after* the outlet elements. * * The default behavior is to render the children only if no elements * were rendered (either none were defined, or none matched), but: * * - when [[@forwardChildren]] is turned on, the outlet will simply pass * those children to the elements. * * - when [[@alwaysRenderChildren]] is turned on, the outlet will insert * the children after any rendered elements */ children: node, /** * @property {Boolean} [firstMatchingElement=false] * * Render only a single element at all times and that is the first one * that matches (ie, yields true in a `match()` routine it defined when it * was registered.) */ firstMatchingElement: bool, /** * @property {Boolean} [alwaysRenderChildren=false] * * Whether we should unconditionally render the children you pass to the * outlet. */ alwaysRenderChildren: bool, /** * @property {Boolean} [forwardChildren=false] * * Whether we should not render the children ourselves, and instead pass * them on to the outlet elements to render for themselves. * * This likely assumes you're expecting a single element and that it's * responsible for rendering those children, which is usually the case for * layout components. */ forwardChildren: bool, /** * @property {Outlet~fnRenderElementCallback} * * Override the routine that renders a single element. * * @callback Outlet~fnRenderElementCallback * * @param {String} key * The key to use for the rendered element. This *MUST* be placed. * * @param {Object} elementProps * The props to render the element with. * * @param {React.Component} Component * The element component type. */ fnRenderElement: func }, getDefaultProps: function getDefaultProps() { return { children: null, tagName: 'div', tagProps: {}, elementProps: {}, options: {}, alwaysRenderChildren: false, forwardChildren: false, fnRenderElement: null }; }, render: function render() { var children = []; var elementProps = ElementProps(this.props); var elementInstances = this.renderElements(elementProps); var hasElements = [].concat(elementInstances).filter(truthy).length > 0; if (hasElements) { children.push(elementInstances); } if (!hasElements || this.props.alwaysRenderChildren) { children.push(this.props.children); } return React.createElement( this.props.tagName, this.props.tagProps, children ); }, renderElements: function renderElements(elementProps) { var elements = this.context.outletManager.getElements(this.props.name); if (this.props.firstMatchingElement) { return this.renderFirstMatchingElement(elements, elementProps); } if (elements.length === 0) { return null; } else if (elements.length === 1) { return this.renderElement(elements[0], elementProps); } else { return elements.map(this.renderElementWithProps(elementProps)); } }, renderElement: function renderElement(element, elementProps) { if (element.match && !element.match(elementProps)) { return null; } var Component = element.component; if (this.props.fnRenderElement) { return this.props.fnRenderElement(element.key, elementProps, Component, this.props.options); } else { return React.createElement(Component, _extends({ key: element.key, $outletOptions: this.props.options }, elementProps)); } }, renderElementWithProps: function renderElementWithProps(elementProps) { var _this = this; return function (element) { return _this.renderElement(element, elementProps); }; }, renderFirstMatchingElement: function renderFirstMatchingElement(elements, elementProps) { for (var i = 0; i < elements.length; ++i) { var element = elements[i]; if (element.match && element.match(elementProps)) { return this.renderElement(element, elementProps); } } return null; } }); function ElementProps(props) { return props.forwardChildren ? Object.assign({}, props.elementProps, { children: props.children }) : props.elementProps; } function truthy(x) { return !!x; }