|
| 1 | +/** |
| 2 | + * Copyright 2004-present Facebook. All Rights Reserved. |
| 3 | + * |
| 4 | + * @providesModule Portal |
| 5 | + * @flow |
| 6 | + */ |
| 7 | +'use strict'; |
| 8 | + |
| 9 | +var React = require('React'); |
| 10 | +var StyleSheet = require('StyleSheet'); |
| 11 | +var View = require('View'); |
| 12 | + |
| 13 | +var _portalRef: any; |
| 14 | + |
| 15 | +// Unique identifiers for modals. |
| 16 | +var lastUsedTag = 0; |
| 17 | + |
| 18 | +/* |
| 19 | + * A container that renders all the modals on top of everything else in the application. |
| 20 | + * |
| 21 | + * Portal makes it possible for application code to pass modal views all the way up to |
| 22 | + * the root element created in `renderApplication`. |
| 23 | + * |
| 24 | + * Never use `<Portal>` in your code. There is only one Portal instance rendered |
| 25 | + * by the top-level `renderApplication`. |
| 26 | + */ |
| 27 | +var Portal = React.createClass({ |
| 28 | + statics: { |
| 29 | + /** |
| 30 | + * Use this to create a new unique tag for your component that renders |
| 31 | + * modals. A good place to allocate a tag is in `componentWillMount` |
| 32 | + * of your component. |
| 33 | + * See `showModal` and `closeModal`. |
| 34 | + */ |
| 35 | + allocateTag: function(): string { |
| 36 | + return '__modal_' + (++lastUsedTag); |
| 37 | + }, |
| 38 | + |
| 39 | + /** |
| 40 | + * Render a new modal. |
| 41 | + * @param tag A unique tag identifying the React component to render. |
| 42 | + * This tag can be later used in `closeModal`. |
| 43 | + * @param component A React component to be rendered. |
| 44 | + */ |
| 45 | + showModal: function(tag: string, component: any) { |
| 46 | + if (!_portalRef) { |
| 47 | + console.error('Calling showModal but no Portal has been rendered.'); |
| 48 | + return; |
| 49 | + } |
| 50 | + _portalRef._showModal(tag, component); |
| 51 | + }, |
| 52 | + |
| 53 | + /** |
| 54 | + * Remove a modal from the collection of modals to be rendered. |
| 55 | + * @param tag A unique tag identifying the React component to remove. |
| 56 | + * Must exactly match the tag previously passed to `showModal`. |
| 57 | + */ |
| 58 | + closeModal: function(tag: string) { |
| 59 | + if (!_portalRef) { |
| 60 | + console.error('Calling closeModal but no Portal has been rendered.'); |
| 61 | + return; |
| 62 | + } |
| 63 | + _portalRef._closeModal(tag); |
| 64 | + }, |
| 65 | + |
| 66 | + /** |
| 67 | + * Get an array of all the open modals, as identified by their tag string. |
| 68 | + */ |
| 69 | + getOpenModals: function(): Array<string> { |
| 70 | + if (!_portalRef) { |
| 71 | + console.error('Calling getOpenModals but no Portal has been rendered.'); |
| 72 | + return []; |
| 73 | + } |
| 74 | + return _portalRef._getOpenModals(); |
| 75 | + } |
| 76 | + }, |
| 77 | + |
| 78 | + getInitialState: function() { |
| 79 | + return {modals: {}}; |
| 80 | + }, |
| 81 | + |
| 82 | + _showModal: function(tag: string, component: any) { |
| 83 | + // This way state is chained through multiple calls to |
| 84 | + // _showModal, _closeModal correctly. |
| 85 | + this.setState((state) => { |
| 86 | + var modals = state.modals; |
| 87 | + modals[tag] = component; |
| 88 | + return {modals}; |
| 89 | + }); |
| 90 | + }, |
| 91 | + |
| 92 | + _closeModal: function(tag: string) { |
| 93 | + if (!this.state.modals.hasOwnProperty(tag)) { |
| 94 | + return; |
| 95 | + } |
| 96 | + // This way state is chained through multiple calls to |
| 97 | + // _showModal, _closeModal correctly. |
| 98 | + this.setState((state) => { |
| 99 | + var modals = state.modals; |
| 100 | + delete modals[tag]; |
| 101 | + return {modals}; |
| 102 | + }); |
| 103 | + }, |
| 104 | + |
| 105 | + _getOpenModals: function(): Array<string> { |
| 106 | + return Object.keys(this.state.modals); |
| 107 | + }, |
| 108 | + |
| 109 | + render: function() { |
| 110 | + _portalRef = this; |
| 111 | + if (!this.state.modals) { |
| 112 | + return null; |
| 113 | + } |
| 114 | + var modals = []; |
| 115 | + for (var tag in this.state.modals) { |
| 116 | + modals.push(this.state.modals[tag]); |
| 117 | + } |
| 118 | + if (modals.length === 0) { |
| 119 | + return null; |
| 120 | + } |
| 121 | + return ( |
| 122 | + <View style={styles.modalsContainer}> |
| 123 | + {modals} |
| 124 | + </View> |
| 125 | + ); |
| 126 | + } |
| 127 | +}); |
| 128 | + |
| 129 | +var styles = StyleSheet.create({ |
| 130 | + modalsContainer: { |
| 131 | + position: 'absolute', |
| 132 | + left: 0, |
| 133 | + top: 0, |
| 134 | + right: 0, |
| 135 | + bottom: 0, |
| 136 | + }, |
| 137 | +}); |
| 138 | + |
| 139 | +module.exports = Portal; |
0 commit comments