google-react-maps
Version: 
A more powerfully custom version of the Google Maps Javascript API built for React. Multiple Datalayer support. GEOJSON Enabled.
399 lines (342 loc) • 16 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
    value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _reactDom = require('react-dom');
var _reactDom2 = _interopRequireDefault(_reactDom);
var _utils = require('../utils/utils');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* See [Google Maps Javascript API]{@link https://developers.google.com/maps/documentation/javascript/3.exp/reference}
* @namespace google.maps
* @memberof google
*/
/**
* See [LatLngLiteral object specification]{@link https://developers.google.com/maps/documentation/javascript/3.exp/reference#LatLngLiteral}
* @class google.maps.LatLngLiteral
* @memberof google.maps
*
* @property {number} lat
* @property {number} lng
*/
/**
* The Map Component in the root component for the google maps library. It handles the interface between the google maps javascript api and the implementation of the other components.
* @class Map
*
* @property {string} api-key Required. The javascript api key from your [google console]{@link http://console.developer.google.com}.
* @property {object} mapOptions Optional. A google.maps.MapOptions object.
*
* @property {object} props
* @property {function} props.onMount callback(map, maps) Get's called right after the component is done it's initial render. (Key for triggering outside events that require google maps api to be instantiated.)
* @property {number} props.zoom
* @property {google.maps.LatLngLiteral} props.center
* @property {object} props.latLngBounds
* @property {google.maps.LatLngLiteral} props.latLngBounds.sw
* @property {google.maps.LatLngLiteral} props.latLngBounds.ne
*/
var Map = function (_React$Component) {
    _inherits(Map, _React$Component);
    function Map(props) {
        _classCallCheck(this, Map);
        var _this = _possibleConstructorReturn(this, (Map.__proto__ || Object.getPrototypeOf(Map)).call(this, props));
        _this.displayName = 'Map';
        var _div_id = "map_div_" + Math.floor(Date.now() * Math.random()).toString();
        /**
        *   @property {object} state The Map component's internal state.
        *   @property {object} state.maps A google maps javascript api reference.
        *   @property {object} state._div_id The div id of this map.
        */
        _this.state = {
            maps: null,
            map: null,
            _div_id: _div_id
        };
        _this.listeners = [];
        _this.ref = _this.ref.bind(_this);
        _this.getGeocoder = _this.getGeocoder.bind(_this);
        _this.getGoogleMapsApi = _this.getGoogleMapsApi.bind(_this);
        _this.getGoogleMap = _this.getGoogleMap.bind(_this);
        _this.getOptions = _this.getOptions.bind(_this);
        _this.refreshComponentFromProps = _utils.refreshComponentFromProps.bind(_this);
        _this.centerPropDidChange = _this.centerPropDidChange.bind(_this);
        _this.boundsPropDidChange = _this.boundsPropDidChange.bind(_this);
        _this.zoomPropDidChange = _this.zoomPropDidChange.bind(_this);
        _this.addListener = _this.addListener.bind(_this);
        _this.removeListeners = _this.removeListeners.bind(_this);
        _this.setupMapListenerHooks = _this.setupMapListenerHooks.bind(_this);
        return _this;
    }
    /** Gets the instance of the geocoder tied to this google map. */
    _createClass(Map, [{
        key: 'getGeocoder',
        value: function getGeocoder() {
            return this.state.geocoder;
        }
        /** Gets the google maps api reference from within the component. (Could be used to do google maps api stuff outside of the component) */
    }, {
        key: 'getGoogleMapsApi',
        value: function getGoogleMapsApi() {
            return this.state.maps;
        }
        /** Gets the google maps instance created by `new maps.Map()` keyword. */
    }, {
        key: 'getGoogleMap',
        value: function getGoogleMap() {
            return this.state.map;
        }
    }, {
        key: 'getOptions',
        value: function getOptions(maps) {
            var mapOptions = {
                zoom: 4,
                mapTypeId: maps.MapTypeId[!this.props.mapType ? "ROADMAP" : this.props.mapType],
                data: null
            };
            if (this.props.optionsConstructor) mapOptions = Object.assign(mapOptions, new this.props.optionsConstructor(maps));
            return mapOptions;
        }
    }, {
        key: 'centerPropDidChange',
        value: function centerPropDidChange() {
            var _state = this.state,
                maps = _state.maps,
                map = _state.map;
            var center = this.props.center;
            if (center) return !new maps.LatLng(center.lat, center.lng).equals(map.getCenter());else return false;
        }
    }, {
        key: 'centerHandleChange',
        value: function centerHandleChange() {
            var center = this.state.map.getCenter();
            if (this.props.center.lat != center.lat && this.props.center.lng != center.lng) {
                this.state.map.setCenter(this.props.center);
            }
        }
    }, {
        key: 'boundsPropDidChange',
        value: function boundsPropDidChange() {
            var bounds = this.props.bounds;
            if (bounds && bounds.sw && bounds.ne) {
                bounds = new this.state.maps.LatLngBounds(bounds.sw, bounds.ne);
            }
            var mapBounds = this.state.map.getBounds();
            return bounds && mapBounds ? !mapBounds.equals(bounds) : false;
        }
    }, {
        key: 'boundsHandleChange',
        value: function boundsHandleChange() {
            var bounds = this.props.bounds;
            if (bounds && bounds.sw && bounds.ne) {
                bounds = new this.state.maps.LatLngBounds(bounds.sw, bounds.ne);
                this.state.map.panToBounds(bounds);
            }
            //TODO: Handle bounds change.
        }
    }, {
        key: 'zoomPropDidChange',
        value: function zoomPropDidChange() {
            var map = this.state.map;
            var zoom = this.props.zoom;
            return typeof zoom !== 'undefined' ? zoom != map.getZoom() : false;
        }
    }, {
        key: 'zoomHandleChange',
        value: function zoomHandleChange() {
            var map = this.state.map;
            var zoom = this.props.zoom;
            try {
                map.setZoom(zoom);
            } catch (e) {
                console.error(e);
            }
        }
    }, {
        key: 'addListener',
        value: function addListener(listener) {
            this.listeners.push(listener);
        }
    }, {
        key: 'removeListeners',
        value: function removeListeners() {
            while (this.listeners.length > 0) {
                this.listeners.pop().remove();
            }
        }
    }, {
        key: 'setupMapListenerHooks',
        value: function setupMapListenerHooks() {
            var _this2 = this;
            var _state2 = this.state,
                maps = _state2.maps,
                map = _state2.map;
            if (maps && map) {
                this.removeListeners();
                var assemble = function assemble(name, callback) {
                    return _this2.addListener(maps.event.addListener(map, name, callback));
                };
                var props = Object.getOwnPropertyNames(this.props);
                props.forEach(function (prop) {
                    if (/^on.*$/.test(prop)) {
                        var action = prop.slice(2, prop.length); //Remove the 'on' in front of the prop-name.
                        if ((0, _utils.isValidMapListener)(action)) {
                            switch (action.toLowerCase()) {
                                case 'bounds_changed':case 'boundschanged':
                                    assemble('bounds_changed', function (event) {
                                        var bounds = map.getBounds() ? { ne: map.getBounds().getNorthEast().toJSON(), sw: map.getBounds().getSouthWest().toJSON() } : null;
                                        _this2.props[prop](bounds, event);
                                    });
                                    break;
                                case 'center_changed':case 'centerchanged':
                                    assemble('center_changed', function (event) {
                                        var center = map.getCenter();
                                        _this2.props[prop](center, event);
                                    });
                                    break;
                                case 'zoom_changed':case 'zoomchanged':
                                    assemble('zoom_changed', function (event) {
                                        var zoom = map.getZoom();
                                        _this2.props[prop](zoom, event);
                                    });
                                default:
                                    assemble(action.toLowerCase(), _this2.props[prop]);
                                    break;
                            }
                        } else {
                            if (action.toLowerCase() !== 'mount') {
                                console.warn(new Error("You tried adding " + prop + " which is not a valid action for a <Map /> component."));
                            }
                        }
                    }
                });
            }
        }
    }, {
        key: 'ref',
        value: function ref(name) {
            var _this3 = this;
            return function (item) {
                _this3[name] = item;
            };
        }
    }, {
        key: 'componentDidMount',
        value: function componentDidMount() {
            var _this4 = this;
            var initMapComponentWithLibrary = function initMapComponentWithLibrary(maps) {
                // window.maps = maps;
                var mapOptions = _this4.getOptions(maps);
                try {
                    var geocoder = new maps.Geocoder();
                    var map = new maps.Map(_reactDom2.default.findDOMNode(_this4.mapDiv), mapOptions);
                    map.setCenter(!_this4.props.center ? new maps.LatLng(39.5, -98.35) : new maps.LatLng(_this4.props.center.lat, _this4.props.center.lng));
                    if (_this4.props.bounds && _this4.props.bounds.sw && _this4.props.bounds.ne) {
                        var bounds = new maps.LatLngBounds(_this4.props.bounds.sw, _this4.props.bounds.ne);
                        map.panToBounds(bounds);
                    }
                } catch (e) {
                    console.error(e);
                }
                _this4.setState({
                    map: map,
                    maps: maps,
                    geocoder: geocoder
                }, function () {
                    _this4.refreshComponentFromProps();
                    if (typeof _this4.props.onMount === 'function') {
                        _this4.props.onMount(_this4.getGoogleMap(), _this4.getGoogleMapsApi());
                    }
                });
                _this4.setupMapListenerHooks();
            };
            if (this.props["api-key"]) {
                var mapsapi = require('google-maps-api');
                // if(!window.maps)
                mapsapi(this.props["api-key"], ['drawing', 'geometry', 'places'])().then(initMapComponentWithLibrary);
                // else
                // 	initMapComponentWithLibrary(window.maps);
            }
        }
    }, {
        key: 'componentDidUpdate',
        value: function componentDidUpdate() {
            if (this.state.map) {
                this.refreshComponentFromProps();
                this.setupMapListenerHooks(); // ?? This may not be necessary. Only on didMount.
            }
        }
    }, {
        key: 'componentWillUnmount',
        value: function componentWillUnmount() {
            this.removeListeners();
        }
    }, {
        key: 'render',
        value: function render() {
            var _this5 = this;
            var children = [];
            var controls = [];
            if (this.state.maps && this.state.map && this.props.children) children = _react2.default.Children.map(this.props.children, function (child) {
                return child ? _react2.default.cloneElement(child, {
                    maps: _this5.state.maps,
                    map: _this5.state.map
                }) : undefined;
            });
            if (this.state.maps && this.state.map && this.props.controls && this.props.controls.constructor.name === 'Array') {
                controls = _react2.default.Children.map(this.props.controls, function (control) {
                    return control ? _react2.default.cloneElement(control, {
                        maps: _this5.state.maps,
                        map: _this5.state.map
                    }) : undefined;
                });
            }
            return _react2.default.createElement(
                'div',
                { ref: this.ref('mapDiv'), id: this.state._div_id, style: this.props.style },
                _react2.default.createElement(
                    'div',
                    null,
                    children
                ),
                _react2.default.createElement(
                    'div',
                    null,
                    controls
                )
            );
        }
    }]);
    return Map;
}(_react2.default.Component);
Map.propTypes = {
    didMount: _propTypes2.default.func,
    optionsConstructor: _propTypes2.default.func,
    "api-key": _propTypes2.default.string.isRequired,
    style: _propTypes2.default.object,
    mapType: _propTypes2.default.string,
    zoom: _propTypes2.default.number,
    center: _propTypes2.default.shape({
        lat: _propTypes2.default.number,
        lng: _propTypes2.default.number
    }),
    bounds: _propTypes2.default.shape({
        sw: _propTypes2.default.shape({
            lat: _propTypes2.default.number,
            lng: _propTypes2.default.number
        }),
        ne: _propTypes2.default.shape({
            lat: _propTypes2.default.number,
            lng: _propTypes2.default.number
        })
    })
};
exports.default = Map;
module.exports = exports['default'];
//# sourceMappingURL=map.js.map