├── .gitignore ├── examples ├── example1.html └── src │ └── example1.js ├── README.md ├── LICENSE ├── src ├── info-window.js ├── marker.js └── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | lib 2 | node_modules 3 | examples/*.js 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /examples/example1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-maps 2 | 3 | [![Version](http://img.shields.io/npm/v/react-maps.svg)](https://www.npmjs.org/package/react-maps) 4 | 5 | Read the code for an understanding 6 | 7 | 8 | ## Example 9 | 10 | ``` javascript 11 | // ... 12 | 13 | render: function() { 14 | // Melbourne 15 | var center = { 16 | lat: -37.8602828, 17 | lng: 145.079616 18 | } 19 | 20 | return ( 21 | 22 |

Melbourne

23 |
24 | 25 |
) 26 | } 27 | ``` 28 | 29 | ## LICENSE [MIT](LICENSE) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Daniel Cousens 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/info-window.js: -------------------------------------------------------------------------------- 1 | /* global google */ 2 | 3 | let React = require('react') 4 | let ReactDOMServer = require('react-dom/server') 5 | let blacklist = require('blacklist') 6 | 7 | module.exports = React.createClass({ 8 | getDefaultProps () { 9 | return { 10 | // WARNING: if autoPan is enabled, the parent maps props will be overridden 11 | disableAutoPan: true 12 | } 13 | }, 14 | 15 | componentWillUnmount () { 16 | if (!this.iw) return 17 | 18 | this.iw.close() 19 | }, 20 | 21 | render () { 22 | let props = this.props 23 | let options = blacklist(this.props, 'anchor', 'children', 'map', 'open') 24 | 25 | if (props.children) { 26 | options.content = ReactDOMServer.renderToStaticMarkup(React.createElement('span', {}, props.children)) 27 | } 28 | 29 | // new 30 | if (!this.iw) { 31 | this.iw = new google.maps.InfoWindow(options) 32 | 33 | // existing 34 | } else { 35 | this.iw.setOptions(options) 36 | } 37 | 38 | if (props.open) { 39 | if (!props.map) return null 40 | 41 | this.iw.open(props.map, props.anchor) 42 | 43 | } else { 44 | this.iw.close() 45 | } 46 | 47 | return null 48 | } 49 | }) 50 | -------------------------------------------------------------------------------- /src/marker.js: -------------------------------------------------------------------------------- 1 | /* global google */ 2 | 3 | let React = require('react') 4 | let blacklist = require('blacklist') 5 | 6 | module.exports = React.createClass({ 7 | propTypes: { 8 | onClick: React.PropTypes.func 9 | }, 10 | 11 | componentWillUnmount () { 12 | if (!this.marker) return 13 | 14 | this.marker.setMap(null) 15 | google.maps.event.clearInstanceListeners(this.marker) 16 | }, 17 | 18 | getMarker () { 19 | return this.marker 20 | }, 21 | 22 | onClick () { 23 | if (!this.props.onClick) return 24 | 25 | this.props.onClick() 26 | }, 27 | 28 | render () { 29 | let options = blacklist(this.props, {}) 30 | if (!options.map) return null 31 | 32 | // avoid superfluous map reset 33 | if (options.map === this.map) { 34 | options = blacklist(options, 'map') 35 | 36 | } else { 37 | this.map = options.map 38 | } 39 | 40 | // new 41 | if (!this.marker) { 42 | this.marker = new google.maps.Marker(options) 43 | google.maps.event.addListener(this.marker, 'click', this.onClick) 44 | 45 | // existing 46 | } else { 47 | this.marker.setOptions(options) 48 | } 49 | 50 | return null 51 | } 52 | }) 53 | 54 | module.exports.Animation = google.maps.Animation 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-maps", 3 | "version": "0.4.0", 4 | "description": "A Google map API wrapper for React", 5 | "author": "Daniel Cousens", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/dcousens/react-maps.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/dcousens/react-maps/issues" 13 | }, 14 | "homepage": "https://github.com/dcousens/react-maps", 15 | "keywords": [ 16 | "react-maps" 17 | ], 18 | "main": "lib/index.js", 19 | "files": [ 20 | "lib/" 21 | ], 22 | "scripts": { 23 | "build": "babel --out-dir lib/ src/", 24 | "build-examples": "find examples/src -name '*.js' -exec sh -c 'browserify -t babelify {} --outfile examples/`basename {}`' \\;", 25 | "prepublish": "npm run build", 26 | "standard": "standard", 27 | "start": "npm run watch-examples", 28 | "test": "npm run standard", 29 | "watch": "babel --watch --out-dir lib/ src/", 30 | "watch-examples": "find examples/src -name '*.js' -exec sh -c 'watchify -v -t babelify {} --outfile examples/`basename {}`' \\;" 31 | }, 32 | "standard": { 33 | "ignore": [ 34 | "lib/" 35 | ] 36 | }, 37 | "dependencies": { 38 | "blacklist": "^1.1.2" 39 | }, 40 | "devDependencies": { 41 | "babel": "*", 42 | "babelify": "*", 43 | "browserify": "*", 44 | "react": "^0.14.0", 45 | "react-dom": "^0.14.0", 46 | "standard": "*", 47 | "watchify": "*" 48 | }, 49 | "peerDependencies": { 50 | "react": "^0.14.0", 51 | "react-dom": "^0.14.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/src/example1.js: -------------------------------------------------------------------------------- 1 | let React = require('react') 2 | let ReactDOM = require('react-dom') 3 | let GoogleMap = require('../../src/index') 4 | 5 | let App = React.createClass({ 6 | getInitialState () { 7 | return { 8 | t: 0, 9 | center: { 10 | lat: -37.9, 11 | lng: 145.08 12 | }, 13 | marker: { 14 | lat: -37.9, 15 | lng: 145.08 16 | } 17 | } 18 | }, 19 | 20 | componentDidMount () { 21 | setInterval(() => { 22 | let dx = 0.6 * Math.cos(this.state.t) 23 | let dy = 0.6 * Math.sin(this.state.t) 24 | 25 | this.setState({ 26 | t: this.state.t + 0.01, 27 | marker: { 28 | lng: this.state.center.lng + dx, 29 | lat: this.state.center.lat + dy 30 | } 31 | }) 32 | }, 10) 33 | }, 34 | 35 | onClickMarker () { 36 | this.setState({ markerInfoOpen: !this.state.markerInfoOpen }) 37 | }, 38 | 39 | render () { 40 | let { center, marker, markerInfoOpen } = this.state 41 | let marker1 = this.refs.marker1 42 | 43 | return ( 44 | 45 | 46 | 47 |

Marker

48 |
49 | 50 | 51 |

Center

52 |
53 |
54 | ) 55 | } 56 | }) 57 | 58 | ReactDOM.render(React.createElement(App), document.getElementById('app')) 59 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* global google */ 2 | 3 | let React = require('react') 4 | let ReactDOM = require('react-dom') 5 | let blacklist = require('blacklist') 6 | 7 | module.exports = React.createClass({ 8 | getDefaultProps () { 9 | return { 10 | // WARNING: even if draggable/panControl is enable, props.center will still be authoritative 11 | draggable: false, 12 | panControl: false, 13 | 14 | // WARNING: even if rotateControl is enabled, props.heading will still be authoritative 15 | rotateControl: false, 16 | 17 | // WARNING: if scaleControl is enabled, ... something 18 | scaleControl: false, 19 | 20 | // WARNING: even if these are enabled, props.zoom will still be authoritative 21 | disableDoubleClickZoom: true, 22 | scrollwheel: false, 23 | zoomControl: false, 24 | 25 | // WARNING: even if enabled, props.mapTypeId will still be authoritative 26 | mapTypeControl: false, 27 | 28 | // WARNING: be careful with this 29 | keyboardShortcuts: false, 30 | streetViewControl: false, 31 | 32 | noClear: true 33 | } 34 | }, 35 | 36 | getInitialState () { 37 | return {} 38 | }, 39 | 40 | componentDidMount () { this.updateMap(this.props) }, 41 | componentWillReceiveProps: function (newProps) { this.updateMap(newProps) }, 42 | 43 | updateMap: function (newProps) { 44 | let domNode = ReactDOM.findDOMNode(this) 45 | let options = blacklist(newProps, 'children', 'pan', 'autofit') 46 | let map = this.state.map 47 | 48 | // create new map 49 | if (!map) { 50 | map = new google.maps.Map(domNode, options) 51 | 52 | // update existing map 53 | } else { 54 | map.setOptions(options) 55 | } 56 | 57 | // autofit the bounds to the children? 58 | if (newProps.autofit && newProps.children.length) { 59 | let bounds = new google.maps.LatLngBounds() 60 | 61 | React.Children.forEach(newProps.children, function (child) { 62 | let position = child.props.position 63 | if (!position) return 64 | 65 | bounds.extend(new google.maps.LatLng(position.lat, position.lng)) 66 | }) 67 | 68 | map.fitBounds(bounds) 69 | } 70 | 71 | this.setState({ map: map }, () => { 72 | if (!this.props.onUpdate) return 73 | 74 | this.props.onUpdate(map) 75 | }) 76 | }, 77 | 78 | getMap () { return this.state.map }, 79 | getBounds () { return this.state.map.getBounds() }, 80 | getCenter () { return this.state.map.getCenter() }, 81 | getHeading () { return this.state.map.getHeading() }, 82 | getProjection () { return this.state.map.getProjection() }, 83 | getTilt () { return this.state.map.getTilt() }, 84 | getZoom () { return this.state.map.getZoom() }, 85 | 86 | render () { 87 | let map = this.state.map 88 | let children = React.Children.map(this.props.children, function (child) { 89 | return React.cloneElement(child, { map: map }) 90 | }) 91 | 92 | return ( 93 |
94 | {children} 95 |
96 | ) 97 | } 98 | }) 99 | 100 | // expose the marker on the top level export 101 | module.exports.InfoWindow = require('./info-window') 102 | module.exports.Marker = require('./marker') 103 | --------------------------------------------------------------------------------