import React from 'react'; import PropTypes from 'prop-types'; import ReactDom from 'react-dom'; /** The component designed to implement the google.maps.InfoWindow class. This component can be the child of either the `<Map />` or `<Marker />` components, but if you decide to put it within the `<Map />` component you must set its coordinate property so that it has an anchor point. * @memberof Map * * @property {object} props * @property {google.maps} props.maps Required. * @property {google.maps.Map} props.map Required. * @property {google.maps.MVCObject} props.anchor Required if coordinates aren't provided. * @property {object} props.coords Required if anchor isn't provided. * @property {number} props.coords.lng * @property {number} props.coords.lat * @property {bool} props.disableAutopan * @property {number} props.maxWidth * @property {object} props.pixelOffset * @property {object} props.pixelOffset.width * @property {object} props.pixelOffset.height * @property {google.maps.InfoWindowOptions} props.options These will overwrite any of the convenience props above. See [google.maps.InfoWindowOptions]{@link https://developers.google.com/maps/documentation/javascript/3.exp/reference#InfoWindowOptions} documentation for all the options. * @property {bool} props.open Allows you to open and close a window without fully unmounting it. * @property {object} state * @property {google.maps.InfoWindow} state.infoWindow The internal instance of the infoWindow. * @property {function} props.onCloseClick Use this to listen for the close click event. When someone tries to close the infowindow. Implement closing. */ class InfoWindow extends React.Component { constructor(props) { super(props); this.displayName = 'InfoWindow'; this.state = { infoWindow : null, anchor : null } this.loadInfoWindowContent = this.loadInfoWindowContent.bind(this); this.node = null; } componentWillMount() { var {maps, map, anchor, coords} = this.props; if(maps && map) { var options = { position : anchor? undefined : coords } var infoWindow = new maps.InfoWindow(options) if(this.props.open) infoWindow.open(map, anchor); else infoWindow.close() //Don't let the infowindow do it's default thing when a user tries to close it. maps.event.addListener(infoWindow, 'closeclick', e => { if(typeof this.props.onCloseClick === 'function') this.props.onCloseClick(e); }); this.setState({infoWindow, anchor}) } else { console.error("InfoWindow must live inside of a <Map /> component context.") } } /** Load rendered children into infoWindow. * @return {undefined} */ loadInfoWindowContent() { if(this.state.infoWindow) { this.node = ReactDom.findDOMNode(this.refs.infoWindowChildren); this.state.infoWindow.setContent(this.node); //Set infowindow content } } /** Place rendered children back into their normal location to await their destruction. * @return {undefined} */ cleanInfoWindowContentForUnmount() { //Undo our previous dom manipulation. var parent = ReactDom.findDOMNode(this); var child = this.node; parent.appendChild(child); } componentDidMount() { this.loadInfoWindowContent() } componentWillUnmount() { if(this.state.infoWindow) this.state.infoWindow.open(null); this.setState({infoWindow : null}); this.cleanInfoWindowContentForUnmount(); } componentDidUpdate(prevProps, prevState) { if(this.state.infoWindow) { if(this.props.open && !prevProps.open) this.state.infoWindow.open(this.props.map, this.state.anchor); else if(!this.props.open && prevProps.open) this.state.infoWindow.close(); } if(!this.node) this.loadInfoWindowContent(); var {coords} = this.props; if(!prevProps.coords || (coords.lat != prevProps.coords.lat && coords.lng != prevProps.coords.lng)) this.state.infoWindow.setPosition(this.props.coords); } render() { return <div><div ref="infoWindowChildren">{this.props.children}</div></div>; } } InfoWindow.propTypes = { maps : PropTypes.object, map : PropTypes.object, coords : PropTypes.shape({ lat : PropTypes.number.isRequired, lng : PropTypes.number.isRequired }), onCloseClick : PropTypes.func } export default InfoWindow;