@liquidautumn/react-google-maps
Version:
React.js Google Maps integration component
196 lines (167 loc) • 6.26 kB
JavaScript
import {
default as React,
PropTypes,
Component,
Children,
} from "react";
import {
render,
unmountComponentAtNode,
} from "react-dom";
import { default as invariant } from "invariant";
import { default as defaultPropsCreator } from "../utils/defaultPropsCreator";
import { default as composeOptions } from "../utils/composeOptions";
import { default as GoogleMapHolder } from "./GoogleMapHolder";
export const overlayViewControlledPropTypes = {
// CustomProps
mapPaneName: PropTypes.string,
getPixelPositionOffset: PropTypes.func,
position: PropTypes.object,
children: PropTypes.node,
bounds: PropTypes.object,
// NOTICE!!!!!!
//
// Only expose those with getters & setters in the table as controlled props.
//
// [].map.call($0.querySelectorAll("tr>td>code"), function(it){ return it.textContent; }).filter(function(it){ return it.match(/^set/) && !it.match(/^setMap/); })
//
// https://developers.google.com/maps/documentation/javascript/3.exp/reference
};
export const overlayViewDefaultPropTypes = defaultPropsCreator(overlayViewControlledPropTypes);
export default class OverlayViewCreator extends Component {
static propTypes = {
mapHolderRef: PropTypes.instanceOf(GoogleMapHolder).isRequired,
mapPaneName: PropTypes.string,
overlayView: PropTypes.object.isRequired,
}
static _createOverlayView(overlayViewProps) {
// https://developers.google.com/maps/documentation/javascript/3.exp/reference#OverlayView
const overlayView = new google.maps.OverlayView();
overlayView.setValues(composeOptions(overlayViewProps, overlayViewControlledPropTypes));
overlayView.onAdd = function onAdd() {
this._containerElement = document.createElement(`div`);
this._containerElement.style.position = `absolute`;
};
overlayView.draw = function draw() {
this._mountContainerToPane();
this._renderContent();
};
overlayView.onRemove = function onRemove() {
unmountComponentAtNode(this._containerElement);
this._unmountContainerFromPane();
this._containerElement = null;
};
overlayView._redraw = function _redraw(mapPaneNameChanged) {
if (mapPaneNameChanged) {
this._unmountContainerFromPane();
this._mountContainerToPane();
}
this._renderContent();
};
overlayView._renderContent = function _renderContent() {
if (this._containerElement) {
render(
Children.only(this.get(`children`)),
this._containerElement,
this._positionContainerElement.bind(this)
);
}
};
overlayView._mountContainerToPane = function _mountContainerToPane() {
const mapPaneName = this.get(`mapPaneName`);
invariant(!!mapPaneName, `OverlayView requires a mapPaneName/defaultMapPaneName in your props instead of %s`, mapPaneName);
this.getPanes()[mapPaneName].appendChild(this._containerElement);
};
overlayView._unmountContainerFromPane = function _unmountContainerFromPane() {
this._containerElement.parentNode.removeChild(this._containerElement);
};
overlayView._positionContainerElement = function _positionContainerElement() {
let left;
let top;
const offset = this._getOffset();
if (this.get(`bounds`)) {
const bounds = this._getPixelBounds();
if (bounds) {
const { sw, ne } = bounds;
if (offset) {
sw.x += offset.x;
ne.y += offset.y;
}
left = sw.x + `px`;
top = ne.y + `px`;
this._containerElement.style.width = (ne.x - sw.x) + `px`;
this._containerElement.style.height = (sw.y - ne.y) + `px`;
}
} else {
const position = this._getPixelPosition();
if (position) {
let { x, y } = position;
if (offset) {
x += offset.x;
y += offset.y;
}
left = x + `px`;
top = y + `px`;
}
}
this._containerElement.style.left = left;
this._containerElement.style.top = top;
};
overlayView._getPixelPosition = function _getPixelPosition() {
const projection = this.getProjection();
let position = this.get(`position`);
invariant(!!position, `OverlayView requires a position/defaultPosition in your props instead of %s`, position);
if (projection && position) {
if (!(position instanceof google.maps.LatLng)) {
position = new google.maps.LatLng(position.lat, position.lng);
}
return projection.fromLatLngToDivPixel(position);
}
};
overlayView._getPixelBounds = function _getPixelBounds() {
const projection = this.getProjection();
let bounds = this.get(`bounds`);
invariant(!!bounds, `OverlayView requires a bounds in your props instead of %s`, bounds);
if (projection && bounds) {
if (!(bounds instanceof google.maps.LatLngBounds)) {
bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(bounds.ne.lat, bounds.ne.lng),
new google.maps.LatLng(bounds.sw.lat, bounds.sw.lng)
);
}
return {
sw: projection.fromLatLngToDivPixel(this.bounds.getSouthWest()),
ne: projection.fromLatLngToDivPixel(this.bounds.getNorthEast()),
};
}
};
overlayView._getOffset = function _getOffset() {
// Allows the component to control the visual position of the OverlayView
// relative to the LatLng pixel position.
const getPixelPositionOffset = this.get(`getPixelPositionOffset`);
if (getPixelPositionOffset) {
return getPixelPositionOffset(
this._containerElement.offsetWidth,
this._containerElement.offsetHeight
);
}
};
return overlayView;
}
getOverlayView() {
return this.props.overlayView;
}
componentDidMount() {
this.getOverlayView().setMap(this.props.mapHolderRef.getMap());
}
componentDidUpdate(prevProps) {
this.getOverlayView().setValues(this.props);
this.getOverlayView()._redraw(this.props.mapPaneName !== prevProps.mapPaneName);
}
componentWillUnmount() {
this.getOverlayView().setMap(null);
}
render() {
return (<noscript />);
}
}