├── .gitignore
├── .npmignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── docs
└── api.md
├── examples
├── README.md
├── frags
│ ├── app.js
│ └── index.html
├── global.css
├── markers
│ ├── app.js
│ └── index.html
├── overlayview-simple
│ ├── app.js
│ └── index.html
├── polygon-draggable
│ ├── app.js
│ └── index.html
└── polyline-complex
│ ├── app.js
│ └── index.html
├── index.js
├── package.json
├── preprocessor.js
├── src
├── GoogleMapsAPI.js
├── ReactMapComponents.js
├── __mocks__
│ └── GoogleMapsAPI.js
├── eventPlugins
│ ├── MouseEventPlugin.js
│ ├── SideEffectEventPlugin.js
│ └── SimpleEventPlugin.js
├── ui
│ ├── MapEvent.js
│ ├── MapOption.js
│ ├── MapOptionConfig.js
│ ├── MapPropTypes.js
│ ├── ReactDefaultInjection.js
│ ├── ReactMapComponent.js
│ ├── ReactMapComponentMixin.js
│ ├── __tests__
│ │ ├── ReactMapComponent-test.js
│ │ └── ReactMapComponentMixin-test.js
│ └── components
│ │ ├── ReactFrag.js
│ │ ├── ReactMap.js
│ │ ├── ReactOverlayView.js
│ │ └── __tests__
│ │ └── ReactFrag-test.js
└── utils
│ ├── PropTypeUtils.js
│ └── __tests__
│ └── PropTypeUtils-test.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | examples/_*
2 | examples/build
3 | node_modules
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | bower.json
2 | examples
3 | preprocessor.js
4 | __tests__
5 | webpack.config.js
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "0.10"
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.4.0
2 |
3 | - Support for React 0.12.0.
4 |
5 | ## 0.3.0
6 |
7 | - Removed `getNodeInterface` and added `getMapNode` which returns the real internal map class instance rather than an interface.
8 | - Added API docs and more detailed examples.
9 | - Added `Frag` component.
10 |
11 | ## 0.2.1
12 |
13 | - Added reactify to package.json for browserify support.
14 |
15 | ## 0.2.0
16 |
17 | - Expose Map PropType validators.
18 | - Added PropType validators to components.
19 | - Map options are no longer passed to the node constructor and are rather, set directly after construction.
20 | - Bugfix: Force noop handlers for events with side effects, e.g. if prop of `center` is set a handler for `onCenterChange` will internally set which allows the SideEffectEventPlugin to stop a value from being internally modified.
21 | - Added option for setting initial options, e.g. `center` can instead be set as `initialCenter`.
22 |
23 | ## 0.1.2
24 |
25 | - Bugfix: update `OverlayView` when new props are passed.
26 |
27 | ## 0.1.1
28 |
29 | - Added base required propType checks.
30 |
31 | ## 0.1.0
32 |
33 | - Initial release.
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Pieter Vanderwerff
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of
4 | this software and associated documentation files (the "Software"), to deal in
5 | the Software without restriction, including without limitation the rights to
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7 | of the Software, and to permit persons to whom the Software is furnished to do
8 | so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | React Google Maps [](https://travis-ci.org/pieterv/react-googlemaps)
2 | ============
3 |
4 | A declarative React interface to Google Maps.
5 |
6 | Check it out:
7 |
8 | * [Example usage](examples)
9 | * [API docs](docs/api.md)
10 | * [What's new](CHANGELOG.md)
11 |
12 | Important Notes
13 | ---------------
14 |
15 | This is an alpha release. The API and organizational structure are subject to
16 | change. Comments and contributions are much appreciated.
17 |
18 | Installation
19 | ------------
20 |
21 | ```sh
22 | npm install react-googlemaps --save
23 | ```
24 |
25 | This library is written with CommonJS modules. If you are using
26 | browserify, webpack, or similar, you can consume it like anything else
27 | installed from npm.
28 |
29 | Usage
30 | -----
31 |
32 | ```js
33 | var React = require('react');
34 | var ReactGoogleMaps = require('react-googlemaps');
35 | var GoogleMapsAPI = window.google.maps;
36 |
37 | var Map = ReactGoogleMaps.Map;
38 | var Marker = ReactGoogleMaps.Marker;
39 | var OverlayView = ReactGoogleMaps.OverlayView;
40 |
41 | function handleClick(e) {
42 | console.log('Clicked at position', e.latLng);
43 | }
44 |
45 | React.render(
46 |
49 |
50 |
53 |
54 |
57 | Some content
58 |
59 | ,
60 | mountNode
61 | );
62 | ```
63 |
64 | Checkout the [API docs](docs/api.md) or the [`examples`](examples) directory for more detailed usage.
65 |
66 | License
67 | -------
68 |
69 | Licensed under MIT. [Full license here »](LICENSE)
70 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | API
2 | ===
3 |
4 | - [Components](#components)
5 | - [`Map`](#map)
6 | - [`Marker`](#marker)
7 | - [`Polyline`](#polyline)
8 | - [`Polygon`](#polygon)
9 | - [`Circle`](#circle)
10 | - [`Rectangle`](#rectangle)
11 | - [`OverlayView`](#overlayview)
12 | - [`Frag`](#frag)
13 | - [PropTypes](#proptypes)
14 |
15 | # Components
16 |
17 | ```js
18 | var ReactGoogleMaps = require('react-googlemaps');
19 | var Map = ReactGoogleMaps.Map;
20 | var Marker = ReactGoogleMaps.Marker;
21 |
22 |
23 |
24 |
25 | ```
26 | All components can be referenced from the root `react-googlemaps` object.
27 |
28 | ```js
29 | var GoogleMapsAPI = window.google.maps;
30 | var LatLng = GoogleMapsAPI.LatLng;
31 |
32 | var myPosition = new LatLng(...);
33 | ```
34 | Components will often need native GoogleMaps classes as props. For example `LatLng` is often needed for setting the position of components.
35 |
36 | ### Props
37 | Components have 3 main types of props that can be set:
38 |
39 | #### Options
40 |
41 | ```js
42 |
45 | ```
46 |
47 | GoogleMaps options for each component can be set via props. Options available for a particular component can be found within the [GoogleMaps API docs](https://developers.google.com/maps/documentation/javascript/reference), the prop name and type will be the same as listed in the class options section. For example see the [MapOptions section](https://developers.google.com/maps/documentation/javascript/reference#MapOptions).
48 |
49 | As is typical with React, these props are immutable so cannot be modified by GoogleMaps or any of its controls, if you want to allow the value to be modified see [initial options](#initial-options) or bind to the associated [change event](#events) and update the option on change.
50 |
51 |
52 | #### Initial options
53 |
54 | ```js
55 |
58 | ```
59 |
60 | Initial options are options that are only applied during `componentDidMount`, this is useful when you want to allow a value to be changed by GoogleMaps controls over time. For example setting `initialZoom={10}` will allow a user to zoom in and out via the Google Maps zoom controls or double clicking but initialise with a value of `10`.
61 |
62 | All possible options can be set as an initial value, with the option names following the convention of: `initialOptionName`.
63 |
64 | #### Events
65 |
66 | ```js
67 | function handleCenterChange(mapNode) {
68 | var newCenter = mapNode.getCenter();
69 | }
70 |
71 |
74 | ```
75 |
76 | GoogleMaps events for each component can be set via props. Events available for a particular component can be found within the [GoogleMaps API docs](https://developers.google.com/maps/documentation/javascript/reference), the prop name listed in the class events section will follow the convention of `center_changed` becoming `onCenterChange`. For example see the [Map section](https://developers.google.com/maps/documentation/javascript/reference#Map).
77 |
78 | ### getMapNode()
79 |
80 | If this component has been mounted, this returns the corresponding internal GoogleMap class instance. This method is useful for reading values out of the map node, such as the current `LatLng` position. This is the equivalent of React's `getDOMNode()`.
81 |
82 | ## Map
83 |
84 | ```js
85 |
90 | {children}
91 |
92 | ```
93 |
94 | [GoogleMaps Map](https://developers.google.com/maps/documentation/javascript/reference#Map) docs.
95 |
96 | ## Marker
97 |
98 | ```js
99 |
102 | ```
103 |
104 | [GoogleMaps Marker](https://developers.google.com/maps/documentation/javascript/reference#Marker) docs.
105 |
106 | ## Polyline
107 |
108 | ```js
109 |
112 | ```
113 |
114 | [GoogleMaps Polyline](https://developers.google.com/maps/documentation/javascript/reference#Polyline) docs.
115 |
116 | ## Polygon
117 |
118 | ```js
119 |
122 | ```
123 |
124 | [GoogleMaps Polygon](https://developers.google.com/maps/documentation/javascript/reference#Polygon) docs.
125 |
126 | ## Circle
127 |
128 | ```js
129 |
133 | ```
134 |
135 | [GoogleMaps Circle](https://developers.google.com/maps/documentation/javascript/reference#Circle) docs.
136 |
137 | ## Rectangle
138 |
139 | ```js
140 |
143 | ```
144 |
145 | [GoogleMaps Rectangle](https://developers.google.com/maps/documentation/javascript/reference#Rectangle) docs.
146 |
147 | ## OverlayView
148 |
149 | ```js
150 |
154 | Title
155 | Some React content
156 |
157 | ```
158 |
159 | Uses the GoogleMaps OverlayView to render arbitrary React DOM elements into the map. This component has two special props with all other props being forward to the underlying map `div`.
160 | * `position` - A `LatLng` location at which the view should be positioned.
161 | * `mapPane` - Map pane layer to add the view to. One of the [`GoogleMapsAPI.MapPanes`](https://developers.google.com/maps/documentation/javascript/reference#MapPanes) types.
162 |
163 | [GoogleMaps OverlayView](https://developers.google.com/maps/documentation/javascript/reference#OverlayView) docs.
164 |
165 | ## Frag
166 |
167 | ```js
168 | var MarkerCollection = React.createClass({
169 | render: function() {
170 | return (
171 |
172 |
173 |
174 |
175 |
176 | );
177 | }
178 | });
179 | ```
180 |
181 | Frag is a helper component for creating reusable React GoogleMap components. This component has no functionality or output, it just allows you to return multiple components from a custom components `render` function.
182 |
183 | The only prop required is the `map` prop (the GoogleMap instance), this is automatically passed down to all direct children of the `Map` component but will need to be manually set if you use `Frag` inside a custom component. The `Frag` component will then pass the `map` prop down to all of its direct children.
184 |
185 | # PropTypes
186 |
187 | ```js
188 | var React = require('react');
189 | var ReactGoogleMaps = require('react-googlemaps');
190 |
191 | var MyMap = React.createClass({
192 | propTypes: {
193 | myLocation: ReactGoogleMaps.PropTypes.LatLng.isRequired,
194 | myRegion: ReactGoogleMaps.PropTypes.LatLngBounds,
195 | myName: React.PropTypes.string
196 | },
197 |
198 | ...
199 | });
200 | ```
201 | A collection of useful propTypes when creating your own GoogleMaps components.
202 |
--------------------------------------------------------------------------------
/examples/README.md:
--------------------------------------------------------------------------------
1 | ### React Google Maps Examples
2 |
3 | In order to try out the examples, you need to follow these steps:
4 |
5 | 1. Clone this repo
6 | 1. Run `npm install` from the repo's root directory
7 | 1. Run `npm run build-examples` from the repo's root directory
8 | 1. Open the examples `index.html` file in your browser
9 |
--------------------------------------------------------------------------------
/examples/frags/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // This example creates a reusable react component made
4 | // up of ReactGoogleMap components. You can click the map
5 | // to add a new instance or drag the component to move
6 | // it around.
7 |
8 | var React = require('react');
9 | var ReactDOM = require('react-dom');
10 | var ReactGoogleMaps = require('../../');
11 | var update = require('react-addons-update');
12 | var GoogleMapsAPI = window.google.maps;
13 | var Map = ReactGoogleMaps.Map;
14 | var Circle = ReactGoogleMaps.Circle;
15 | var Polyline = ReactGoogleMaps.Polyline;
16 | var Frag = ReactGoogleMaps.Frag;
17 | var LatLng = GoogleMapsAPI.LatLng;
18 |
19 | var DraggableFace = React.createClass({
20 | render: function() {
21 | var centerLat = this.props.center.lat();
22 | var centerLng = this.props.center.lng();
23 | return (
24 |
26 |
31 |
32 |
37 |
38 |
45 |
46 | );
47 | }
48 | });
49 |
50 | var GoogleMapFrags = React.createClass({
51 | getInitialState: function() {
52 | return {
53 | center: new LatLng(41.879535, -87.624333),
54 | zoom: 8,
55 | faces: [
56 | new LatLng(42.48374, -87.01171)
57 | ]
58 | };
59 | },
60 |
61 | render: function() {
62 | return (
63 |
69 | {this.state.faces.map(this.renderFace)}
70 |
71 | );
72 | },
73 |
74 | renderFace: function(position, i) {
75 | return (
76 |
80 | );
81 | },
82 |
83 | handleFaceDrag: function(i, e) {
84 | var faces = update(this.state.faces, {$splice: [[i, 1, e.latLng]]});
85 |
86 | this.setState({
87 | faces: faces
88 | });
89 | },
90 |
91 | handleFaceCreate: function(e) {
92 | var faces = React.addons
93 | .update(this.state.faces, {$push: [e.latLng]});
94 |
95 | this.setState({
96 | faces: faces
97 | });
98 | }
99 | });
100 |
101 | ReactDOM.render( , document.getElementById('example'));
102 |
--------------------------------------------------------------------------------
/examples/frags/index.html:
--------------------------------------------------------------------------------
1 |
2 |
Frags
3 |
4 |
5 | Frags
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/global.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: "Helvetica Neue", Arial;
3 | font-weight: 200;
4 | text-align: center;
5 | }
6 |
--------------------------------------------------------------------------------
/examples/markers/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var ReactDOM = require('react-dom');
5 | var update = require('react-addons-update');
6 | var ReactGoogleMaps = require('../../');
7 | var GoogleMapsAPI = window.google.maps;
8 | var Map = ReactGoogleMaps.Map;
9 | var Marker = ReactGoogleMaps.Marker;
10 | var LatLng = GoogleMapsAPI.LatLng;
11 |
12 | var GoogleMapMarkers = React.createClass({
13 | getInitialState: function() {
14 | return {
15 | center: new LatLng(-34.397, 150.644),
16 | zoom: 16,
17 | markers: [
18 | {position: new LatLng(-34.397, 150.644)}
19 | ]
20 | };
21 | },
22 |
23 | render: function() {
24 | return (
25 |
32 | {this.state.markers.map(this.renderMarkers)}
33 |
34 | );
35 | },
36 |
37 | renderMarkers: function(state, i) {
38 | return (
39 |
40 | );
41 | },
42 |
43 | handleMapClick: function(mapEvent) {
44 | var marker = {
45 | position: mapEvent.latLng
46 | };
47 |
48 | var markers = update(this.state.markers, {$push: [marker]});
49 |
50 | this.setState({
51 | markers: markers,
52 | center: mapEvent.latLng
53 | });
54 | },
55 |
56 | handleCenterChange: function(map) {
57 | this.setState({
58 | center: map.getCenter()
59 | });
60 | }
61 | });
62 |
63 | ReactDOM.render( , document.getElementById('example'));
64 |
--------------------------------------------------------------------------------
/examples/markers/index.html:
--------------------------------------------------------------------------------
1 |
2 | Markers
3 |
4 |
5 | Markers
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/overlayview-simple/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // This example creates an overlay view on the map.
4 | // This view can render any React DOM element and can have
5 | // its own state.
6 |
7 | var React = require('react');
8 | var ReactDOM = require('react-dom');
9 | var ReactGoogleMaps = require('../../');
10 | var GoogleMapsAPI = window.google.maps;
11 | var Map = ReactGoogleMaps.Map;
12 | var OverlayView = ReactGoogleMaps.OverlayView;
13 | var LatLng = GoogleMapsAPI.LatLng;
14 |
15 | var GoogleMapOverlayViewSimple = React.createClass({
16 | getInitialState: function() {
17 | return {
18 | center: new LatLng(-41.28646, 174.77623),
19 | count: 0
20 | };
21 | },
22 |
23 | render: function() {
24 | return (
25 |
30 |
31 |
35 | Simple overlay!
36 |
38 | I have been clicked {this.state.count} time{this.state.count === 1 ? '' : 's'}
39 |
40 |
41 |
42 | );
43 | },
44 |
45 | handleButtonClick: function() {
46 | this.setState({
47 | count: this.state.count + 1
48 | });
49 | }
50 | });
51 |
52 | ReactDOM.render( , document.getElementById('example'));
53 |
--------------------------------------------------------------------------------
/examples/overlayview-simple/index.html:
--------------------------------------------------------------------------------
1 |
2 | OverlayView Simple
3 |
4 |
5 | OverlayView Simple
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/polygon-draggable/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // This example creates draggable triangles on the map.
4 | // Note also that the red triangle is geodesic, so its shape changes
5 | // as you drag it north or south.
6 | // Based on: https://developers.google.com/maps/documentation/javascript/examples/polygon-draggable
7 |
8 | var React = require('react');
9 | var ReactDOM = require('react-dom');
10 | var ReactGoogleMaps = require('../../');
11 | var GoogleMapsAPI = window.google.maps;
12 | var Map = ReactGoogleMaps.Map;
13 | var Polygon = ReactGoogleMaps.Polygon;
14 | var LatLng = GoogleMapsAPI.LatLng;
15 |
16 | var GoogleMapPolygonDraggable = React.createClass({
17 | getInitialState: function() {
18 | return {
19 | center: new LatLng(24.886, -70.268),
20 | blueCoords: [
21 | new LatLng(25.774, -60.190),
22 | new LatLng(18.466, -46.118),
23 | new LatLng(32.321, -44.757)
24 | ],
25 | redCoords: [
26 | new LatLng(25.774, -80.190),
27 | new LatLng(18.466, -66.118),
28 | new LatLng(32.321, -64.757)
29 | ]
30 | };
31 | },
32 |
33 | render: function() {
34 | return (
35 |
40 |
41 |
50 |
51 |
60 |
61 | );
62 | }
63 | });
64 |
65 | ReactDOM.render( , document.getElementById('example'));
66 |
--------------------------------------------------------------------------------
/examples/polygon-draggable/index.html:
--------------------------------------------------------------------------------
1 |
2 | Draggable Polygons
3 |
4 |
5 | Draggable Polygons
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/examples/polyline-complex/app.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | // This example creates an interactive map which constructs a
4 | // polyline based on user clicks. Note that the polyline only appears
5 | // once its path property contains two LatLng coordinates.
6 | // Based on: https://developers.google.com/maps/documentation/javascript/examples/polyline-complex
7 |
8 | var React = require('react');
9 | var ReactDOM = require('react-dom');
10 | var ReactGoogleMaps = require('../../');
11 | var GoogleMapsAPI = window.google.maps;
12 | var Map = ReactGoogleMaps.Map;
13 | var Marker = ReactGoogleMaps.Marker;
14 | var Polyline = ReactGoogleMaps.Polyline;
15 | var LatLng = GoogleMapsAPI.LatLng;
16 |
17 | var GoogleMapPolylineComplex = React.createClass({
18 | getInitialState: function() {
19 | return {
20 | center: new LatLng(41.879535, -87.624333),
21 | zoom: 7,
22 | linePath: []
23 | };
24 | },
25 |
26 | render: function() {
27 | return (
28 |
34 | {this.renderPolyline()}
35 | {this.state.linePath.map(this.renderMarkers)}
36 |
37 | );
38 | },
39 |
40 | renderMarkers: function(position, i) {
41 | return (
42 |
45 | );
46 | },
47 |
48 | renderPolyline: function() {
49 | return (
50 |
55 | );
56 | },
57 |
58 | handleMapClick: function(mapEvent) {
59 | var linePath = React.addons
60 | .update(this.state.linePath, {
61 | $push: [mapEvent.latLng]
62 | });
63 |
64 | this.setState({
65 | linePath: linePath
66 | });
67 | }
68 | });
69 |
70 | ReactDOM.render( , document.getElementById('example'));
71 |
--------------------------------------------------------------------------------
/examples/polyline-complex/index.html:
--------------------------------------------------------------------------------
1 |
2 | Complex Polylines
3 |
4 |
5 | Complex Polylines
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var assign = require('react/lib/Object.assign');
4 | var ReactDefaultInjection = require('./src/ui/ReactDefaultInjection');
5 | var ReactMapComponents = require('./src/ReactMapComponents');
6 | var MapPropTypes = require('./src/ui/MapPropTypes');
7 |
8 | ReactDefaultInjection.inject();
9 |
10 | module.exports = assign(
11 | {},
12 | ReactMapComponents,
13 | {
14 | PropTypes: MapPropTypes
15 | }
16 | );
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-googlemaps",
3 | "version": "0.4.0",
4 | "description": "A declarative React interface to Google Maps",
5 | "keywords": [
6 | "react",
7 | "react-component",
8 | "google maps",
9 | "googlemaps",
10 | "maps"
11 | ],
12 | "main": "index.js",
13 | "scripts": {
14 | "test": "jest",
15 | "build-examples": "webpack --devtool inline-source-map",
16 | "watch-examples": "webpack --devtool inline-source-map --watch"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "https://github.com/pieterv/react-googlemaps.git"
21 | },
22 | "homepage": "https://github.com/pieterv/react-googlemaps",
23 | "bugs": "https://github.com/pieterv/react-googlemaps/issues",
24 | "author": "Pieter Vanderwerff",
25 | "license": "MIT",
26 | "dependencies": {
27 | "react": "~0.14.0",
28 | "invariant": "~2.1.1",
29 | "fbjs": "~0.3.1",
30 | "react-dom": "~0.14.0",
31 | "react-tools": "^0.12.2",
32 | "react-addons-update": "~0.14.0"
33 | },
34 | "devDependencies": {
35 | "jest-cli": "^0.1.18",
36 | "jsx-loader": "^0.12.2",
37 | "webpack": "^1.3.7"
38 | },
39 | "peerDependencies": {
40 | "react": "^0.14.0",
41 | "reactify": "^1.0.0"
42 | },
43 | "jest": {
44 | "scriptPreprocessor": "/preprocessor.js",
45 | "unmockedModulePathPatterns": [
46 | "/node_modules/react"
47 | ]
48 | },
49 | "browserify": {
50 | "transform": [
51 | "reactify"
52 | ]
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/preprocessor.js:
--------------------------------------------------------------------------------
1 | var ReactTools = require('react-tools');
2 |
3 | module.exports = {
4 | process: function(src) {
5 | return ReactTools.transform(src, {sourceMap: true});
6 | }
7 | };
--------------------------------------------------------------------------------
/src/GoogleMapsAPI.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var invariant = require('invariant');
4 |
5 | invariant(
6 | window.google && window.google.maps,
7 | '`google.maps` global object not found, make sure ' +
8 | 'Google maps in included before react-googlemaps is defined'
9 | );
10 |
11 | module.exports = window.google.maps;
12 |
--------------------------------------------------------------------------------
/src/ReactMapComponents.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var assign = require('react/lib/Object.assign');
4 | var mapObject = require('fbjs/lib/mapObject');
5 | var GoogleMapsAPI = require('./GoogleMapsAPI');
6 | var ReactMapComponent = require('./ui/ReactMapComponent');
7 |
8 | function createMapComponentClass(constructorFn, constructorName) {
9 | return ReactMapComponent.create(constructorName, constructorFn);
10 | }
11 |
12 | function constructGoogleMapsMapClass() {
13 | return new GoogleMapsAPI.Map(this.props.mapDiv);
14 | }
15 |
16 | /**
17 | * Creates a mapping from supported GoogleMap classes to `ReactMapComponent` classes.
18 | *
19 | * @public
20 | */
21 | var ReactMapComponents = mapObject({
22 | Map: constructGoogleMapsMapClass, // NOTE: Injected, see `ReactMap`.
23 | Marker: null,
24 | Polyline: null,
25 | Circle: null,
26 | Rectangle: null,
27 | Polygon: null
28 | // OverlayView: Note: Injected, see `ReactOverlayView`.
29 | // Frag: Note: Injected, see `ReactFrag`.
30 | }, createMapComponentClass);
31 |
32 | var injection = {
33 | injectComponentClasses: function(componentClasses) {
34 | assign(ReactMapComponents, componentClasses);
35 | }
36 | };
37 |
38 | ReactMapComponents.injection = injection;
39 |
40 | module.exports = ReactMapComponents;
41 |
--------------------------------------------------------------------------------
/src/__mocks__/GoogleMapsAPI.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var keyMirror = require('fbjs/lib/keyMirror');
4 |
5 | /**
6 | * Map
7 | */
8 | exports.Map = jest.genMockFn()
9 | .mockImpl(function() {
10 | return {
11 | setOptions: jest.genMockFn()
12 | };
13 | });
14 |
15 | exports.MapTypeId = keyMirror({
16 | HYBRID: null,
17 | ROADMAP: null,
18 | SATELLITE: null,
19 | TERRAIN: null
20 | });
21 |
22 | /**
23 | * Marker
24 | */
25 | exports.Marker = jest.genMockFn()
26 | .mockImpl(function() {
27 | return {
28 | setOptions: jest.genMockFn()
29 | };
30 | });
31 |
32 | /**
33 | * Animation
34 | */
35 | exports.Animation = keyMirror({
36 | BOUNCE: null,
37 | DROP: null
38 | });
39 |
40 | /**
41 | * Point
42 | */
43 | exports.Point = jest.genMockFn()
44 | .mockImpl(function() {
45 | return {
46 | equals: jest.genMockFn(),
47 | x: 0,
48 | y: 0
49 | };
50 | });
51 |
52 | /**
53 | * Icon
54 | */
55 | exports.Icon = {};
56 |
57 | /**
58 | * Symbol
59 | */
60 | exports.Symbol = {};
61 |
62 | /**
63 | * MarkerShape
64 | */
65 | exports.MarkerShape = {};
66 |
67 | /**
68 | * LatLngBounds
69 | */
70 | exports.LatLngBounds = jest.genMockFn()
71 | .mockImpl(function() {
72 | return {
73 | setOptions: jest.genMockFn(),
74 | getCenter: jest.genMockFn().mockReturnValue(new exports.LatLng(0, 0))
75 | };
76 | });
77 |
78 | /**
79 | * LatLng
80 | */
81 | exports.LatLng = jest.genMockFn()
82 | .mockImpl(function() {
83 | return {
84 | setOptions: jest.genMockFn()
85 | };
86 | });
87 |
88 | /**
89 | * Events
90 | *
91 | * https://developers.google.com/maps/documentation/javascript/reference?csw=1#MapsEventListener
92 | */
93 | exports.event = {
94 | addListener: jest.genMockFn().mockReturnThis(),
95 | addListenerOnce: jest.genMockFn().mockReturnThis(),
96 | removeListener: jest.genMockFn(),
97 | clearInstanceListeners: jest.genMockFn(),
98 | clearListeners: jest.genMockFn()
99 | };
100 |
--------------------------------------------------------------------------------
/src/eventPlugins/MouseEventPlugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var eventTypes = {
4 | onClick: {
5 | name: 'click'
6 | },
7 | onDoubleClick: {
8 | name: 'dblclick'
9 | },
10 | onDrag: {
11 | name: 'drag'
12 | },
13 | onDragEnd: {
14 | name: 'dragend'
15 | },
16 | onDragStart: {
17 | name: 'dragstart'
18 | },
19 | onMouseDown: {
20 | name: 'mousedown'
21 | },
22 | onMouseMove: {
23 | name: 'mousemove'
24 | },
25 | onMouseOut: {
26 | name: 'mouseout'
27 | },
28 | onMouseOver: {
29 | name: 'mouseover'
30 | },
31 | onMouseUp: {
32 | name: 'mouseup'
33 | },
34 | onRightClick: {
35 | name: 'rightclick'
36 | }
37 | };
38 |
39 | var MouseEventPlugin = {
40 |
41 | eventTypes: eventTypes,
42 |
43 | executeDispatch: function(event, eventName, instance) {
44 | var listener = instance.props[eventName];
45 |
46 | return listener(event, instance.getMapNode());
47 | }
48 | };
49 |
50 | module.exports = MouseEventPlugin;
51 |
--------------------------------------------------------------------------------
/src/eventPlugins/SideEffectEventPlugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var SimpleEventPlugin = require('./SimpleEventPlugin');
4 |
5 | var eventTypes = {
6 | onCenterChange: {
7 | name: 'center_changed',
8 | effects: 'center'
9 | },
10 | onZoomChange: {
11 | name: 'zoom_changed',
12 | effects: 'zoom'
13 | },
14 | onBoundsChange: {
15 | name: 'bounds_changed',
16 | effects: 'bounds'
17 | },
18 | onHeadingChange: {
19 | name: 'heading_changed',
20 | effects: 'heading'
21 | },
22 | onMapTypeIdChange: {
23 | name: 'maptypeid_changed',
24 | effects: 'mapTypeId'
25 | },
26 | onTiltChange: {
27 | name: 'tilt_changed',
28 | effects: 'tilt'
29 | },
30 | onAnimationChange: {
31 | name: 'animation_changed',
32 | effects: 'animation'
33 | },
34 | onClickableChange: {
35 | name: 'clickable_changed',
36 | effects: 'clickable'
37 | },
38 | onCursorChange: {
39 | name: 'cursor_changed',
40 | effects: 'cursor'
41 | },
42 | onDraggableChange: {
43 | name: 'draggable_changed',
44 | effects: 'draggable'
45 | },
46 | onIconChange: {
47 | name: 'icon_changed',
48 | effects: 'icon'
49 | },
50 | onPositionChange: {
51 | name: 'position_changed',
52 | effects: 'position'
53 | },
54 | onShapeChange: {
55 | name: 'shape_changed',
56 | effects: 'shape'
57 | },
58 | onTitleChange: {
59 | name: 'title_changed',
60 | effects: 'title'
61 | },
62 | onVisibleChange: {
63 | name: 'visible_changed',
64 | effects: 'visible'
65 | },
66 | onZIndexChange: {
67 | name: 'zindex_changed',
68 | effects: 'zindex'
69 | },
70 | onRadiusChange: {
71 | name: 'radius_changed',
72 | effects: 'radius'
73 | }
74 | };
75 |
76 | var SideEffectEventPlugin = {
77 |
78 | eventTypes: eventTypes,
79 |
80 | executeDispatch: function(event, eventName, instance) {
81 | var effects = eventTypes[eventName].effects;
82 |
83 | if (instance.props[effects] == null) {
84 | // The effected property is not set so this event listener
85 | // only needs to be passive.
86 | return SimpleEventPlugin.executeDispatch(event, eventName, instance)
87 | }
88 |
89 | var listener = instance.props[eventName];
90 | var returnVal;
91 | if (listener) {
92 | instance.queueDirtyCheck();
93 | returnVal = listener(instance.getMapNode());
94 | }
95 | instance.flushDirtyChangesTo(effects);
96 |
97 | return returnVal;
98 | }
99 | };
100 |
101 | module.exports = SideEffectEventPlugin;
102 |
--------------------------------------------------------------------------------
/src/eventPlugins/SimpleEventPlugin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var eventTypes = {
4 | onIdle: {
5 | name: 'idle'
6 | },
7 | onResize: {
8 | name: 'resize'
9 | },
10 | onTilesLoaded: {
11 | name: 'tilesloaded'
12 | },
13 | onProjectionChange: {
14 | name: 'projection_changed'
15 | },
16 | onFlatChange: {
17 | name: 'flat_changed'
18 | }
19 | };
20 |
21 | var SimpleEventPlugin = {
22 |
23 | eventTypes: eventTypes,
24 |
25 | executeDispatch: function(event, eventName, instance) {
26 | var listener = instance.props[eventName];
27 |
28 | return listener(instance.getMapNode());
29 | }
30 | };
31 |
32 | module.exports = SimpleEventPlugin;
33 |
--------------------------------------------------------------------------------
/src/ui/MapEvent.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var invariant = require('invariant');
4 |
5 | var MapEventInjection = {
6 |
7 | /**
8 | * Inject some specialized knowledge about the GoogleMaps Events. This takes a object
9 | * of config objects with the following properties:
10 | *
11 | * eventTypes: event config, requires a `name` property is set, this is the name of
12 | * the googlemaps event name. Also can have and `effects` property.
13 | *
14 | * executeDispatch: A function for handling the event dispatch logic.
15 | *
16 | * @param {object} injectedNamesToPlugins the config as described above.
17 | */
18 | injectEventPluginsByName: function(injectedNamesToPlugins) {
19 | for (var pluginName in injectedNamesToPlugins) {
20 | if (!injectedNamesToPlugins.hasOwnProperty(pluginName)) {
21 | continue;
22 | }
23 |
24 | var PluginModule = injectedNamesToPlugins[pluginName];
25 | for (var eventName in PluginModule.eventTypes) {
26 | if (!PluginModule.eventTypes.hasOwnProperty(eventName)) {
27 | continue;
28 | }
29 |
30 | invariant(
31 | !MapEvent.isStandardName[eventName],
32 | 'injectEventPluginsByName(...): Event `%s` has already been defined, ' +
33 | 'an event can only be handled once',
34 | eventName
35 | );
36 |
37 | var EventConfig = PluginModule.eventTypes[eventName];
38 |
39 | MapEvent.isStandardName[eventName] = true;
40 | MapEvent.getEventName[eventName] = EventConfig.name || eventName;
41 | MapEvent.getDispatcher[eventName] = PluginModule.executeDispatch;
42 | if (EventConfig.effects) {
43 | MapEvent.getOptionSideEffectEvent[EventConfig.effects] = eventName;
44 | }
45 | }
46 | }
47 | }
48 | };
49 |
50 | /**
51 | * MapEvent exports lookup objects that can be used like functions:
52 | *
53 | * > MapEvent.isValid['id']
54 | * true
55 | * > MapEvent.isValid['foobar']
56 | * undefined
57 | *
58 | * Although this may be confusing, it performs better in general.
59 | *
60 | * @see http://jsperf.com/key-exists
61 | * @see http://jsperf.com/key-missing
62 | */
63 | var MapEvent = {
64 |
65 | /**
66 | * Checks whether an event name is a standard event.
67 | * @type {Object}
68 | */
69 | isStandardName: {},
70 |
71 | /**
72 | * Mapping from side effect options to normalized event names.
73 | * @type {Object}
74 | */
75 | getOptionSideEffectEvent: {},
76 |
77 | /**
78 | * Mapping from normalized event names to GoogleMaps event name.
79 | * @type {Object}
80 | */
81 | getEventName: {},
82 |
83 | /**
84 | * Mapping over normalized event names to event dispatchers
85 | * @type {Object}
86 | */
87 | getDispatcher: {},
88 |
89 | createEventDispatcher: function(eventName, instance) {
90 | var executeDispatch = MapEvent.getDispatcher[eventName];
91 | return function listener(event) {
92 | if (!MapEvent.isEnabled) {
93 | return;
94 | }
95 |
96 | return executeDispatch(event, eventName, instance);
97 | }
98 | },
99 |
100 | isEnabled: true,
101 |
102 | setEnabled: function(enabled) {
103 | MapEvent.isEnabled = enabled;
104 | },
105 |
106 | injection: MapEventInjection
107 | };
108 |
109 | module.exports = MapEvent;
110 |
--------------------------------------------------------------------------------
/src/ui/MapOption.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var invariant = require('invariant');
4 |
5 | function createInitialOptionName(name) {
6 | return 'initial' + name.charAt(0).toUpperCase() + name.slice(1)
7 | }
8 |
9 | var MapOptionInjection = {
10 |
11 | /**
12 | * Inject some specialized knowledge about the GoogleMaps. This takes a config object
13 | * with the following properties:
14 | *
15 | * Options: object mapping Map option name to a React.PropType validator. If your option
16 | * isn't in here, it won't get passed to your Map class.
17 | *
18 | * MapOptionNames: object mapping React attribute name to the Map
19 | * option name.
20 | *
21 | * @param {object} mapOptionConfig the config as described above.
22 | */
23 | injectMapOptionConfig: function(mapOptionConfig) {
24 | var Options = mapOptionConfig.Options || {};
25 | var MapOptionNames = mapOptionConfig.MapOptionNames || {};
26 |
27 | for (var propName in Options) {
28 | invariant(
29 | !MapOption.isStandardName.hasOwnProperty(propName),
30 | 'injectMapOptionConfig(...): You\'re trying to inject DOM property ' +
31 | '\'%s\' which has already been injected. You may be accidentally ' +
32 | 'injecting the same DOM property config twice, or you may be ' +
33 | 'injecting two configs that have conflicting property names.',
34 | propName
35 | );
36 |
37 | MapOption.isStandardName[propName] = true;
38 |
39 | MapOption.getOptionName[propName] =
40 | MapOptionNames.hasOwnProperty(propName) ?
41 | MapOptionNames[propName] :
42 | propName;
43 |
44 | MapOption.getInitialOptionName[createInitialOptionName(propName)] =
45 | MapOption.getOptionName[propName];
46 | }
47 | }
48 | };
49 |
50 | /**
51 | * MapOption exports lookup objects that can be used like functions:
52 | *
53 | * > MapOption.isValid['id']
54 | * true
55 | * > MapOption.isValid['foobar']
56 | * undefined
57 | *
58 | * Although this may be confusing, it performs better in general.
59 | *
60 | * @see http://jsperf.com/key-exists
61 | * @see http://jsperf.com/key-missing
62 | */
63 | var MapOption = {
64 |
65 | /**
66 | * Checks whether a option name is a standard option.
67 | * @type {Object}
68 | */
69 | isStandardName: {},
70 |
71 | /**
72 | * Mapping from normalized names to option on Map class instances.
73 | * @type {Object}
74 | */
75 | getOptionName: {},
76 |
77 | /**
78 | * Mapping from normalized initial names to option on Map class instances.
79 | * @type {Object}
80 | */
81 | getInitialOptionName: {},
82 |
83 | injection: MapOptionInjection
84 | };
85 |
86 | module.exports = MapOption;
87 |
--------------------------------------------------------------------------------
/src/ui/MapOptionConfig.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var CustomPropTypes = require('./MapPropTypes');
5 |
6 | var MapOptionConfig = {
7 | Options: {
8 | backgroundColor: React.PropTypes.string,
9 | center: CustomPropTypes.LatLng,
10 | disableDefaultUI: React.PropTypes.bool,
11 | disableDoubleClickZoom: React.PropTypes.bool,
12 | draggable: React.PropTypes.bool,
13 | draggableCursor: React.PropTypes.string,
14 | draggingCursor: React.PropTypes.string,
15 | heading: React.PropTypes.number,
16 | keyboardShortcuts: React.PropTypes.bool,
17 | mapMaker: React.PropTypes.bool,
18 | mapTypeControl: React.PropTypes.bool,
19 | mapTypeControlOptions: React.PropTypes.object,
20 | mapTypeId: React.PropTypes.string,
21 | maxZoom: React.PropTypes.number,
22 | minZoom: React.PropTypes.number,
23 | noClear: React.PropTypes.bool,
24 | overviewMapControl: React.PropTypes.bool,
25 | overviewMapControlOptions: React.PropTypes.object,
26 | panControl: React.PropTypes.bool,
27 | panControlOptions: React.PropTypes.object,
28 | rotateControl: React.PropTypes.bool,
29 | rotateControlOptions: React.PropTypes.object,
30 | scaleControl: React.PropTypes.bool,
31 | scaleControlOptions: React.PropTypes.object,
32 | scrollwheel: React.PropTypes.bool,
33 | streetView: React.PropTypes.object,
34 | streetViewControl: React.PropTypes.bool,
35 | streetViewControlOptions: React.PropTypes.object,
36 | styles: React.PropTypes.array,
37 | tilt: React.PropTypes.number,
38 | zoom: React.PropTypes.number,
39 | zoomControl: React.PropTypes.bool,
40 | zoomControlOptions: React.PropTypes.object,
41 | anchorPoint: CustomPropTypes.Point,
42 | animation: CustomPropTypes.Animation,
43 | clickable: React.PropTypes.bool,
44 | crossOnDrag: React.PropTypes.bool,
45 | cursor: React.PropTypes.string,
46 | icon: React.PropTypes.oneOfType([
47 | React.PropTypes.string,
48 | CustomPropTypes.Icon,
49 | CustomPropTypes.Symbol
50 | ]),
51 | map: CustomPropTypes.Map,
52 | opacity: React.PropTypes.number,
53 | optimized: React.PropTypes.bool,
54 | position: CustomPropTypes.LatLng,
55 | shape: CustomPropTypes.MarkerShape,
56 | title: React.PropTypes.string,
57 | visible: React.PropTypes.bool,
58 | zIndex: React.PropTypes.number,
59 | editable: React.PropTypes.bool,
60 | geodesic: React.PropTypes.bool,
61 | icons: React.PropTypes.array,
62 | path: React.PropTypes.array,
63 | strokeColor: React.PropTypes.string,
64 | strokeOpacity: React.PropTypes.number,
65 | strokeWeight: React.PropTypes.number,
66 | fillColor: React.PropTypes.string,
67 | fillOpacity: React.PropTypes.number,
68 | paths: React.PropTypes.array,
69 | strokePosition: React.PropTypes.any,
70 | bounds: CustomPropTypes.LatLngBounds,
71 | radius: React.PropTypes.number
72 | },
73 |
74 | MapOptionNames: {
75 | // Format:
76 | // autoCapitalize: 'autocapitalize'
77 | }
78 | };
79 |
80 | module.exports = MapOptionConfig;
81 |
--------------------------------------------------------------------------------
/src/ui/MapPropTypes.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var GoogleMaps = require('../GoogleMapsAPI');
5 |
6 | /**
7 | * Checks whether a prop provides a `GoogleMaps.LatLng`
8 | */
9 | exports.LatLng = React.PropTypes.instanceOf(GoogleMaps.LatLng);
10 |
11 | /**
12 | * Checks whether a prop provides a `GoogleMaps.LatLngBounds`
13 | */
14 | exports.LatLngBounds = React.PropTypes.instanceOf(GoogleMaps.LatLngBounds);
15 |
16 | /**
17 | * Checks whether a prop provides a `GoogleMaps.Map`
18 | */
19 | exports.Map = React.PropTypes.instanceOf(GoogleMaps.Map);
20 |
21 | /**
22 | * Checks whether a prop provides a `GoogleMaps.Point`
23 | */
24 | exports.Point = React.PropTypes.instanceOf(GoogleMaps.Point);
25 |
26 | /**
27 | * Checks whether a prop provides a `GoogleMaps.Animation`
28 | */
29 | exports.Animation = React.PropTypes.oneOf(
30 | Object.keys(GoogleMaps.Animation)
31 | .map(function(key) {return GoogleMaps.Animation[key];})
32 | );
33 |
34 | /**
35 | * Checks whether a prop provides a `GoogleMaps.Icon`
36 | */
37 | exports.Icon = React.PropTypes.object;
38 |
39 | /**
40 | * Checks whether a prop provides a `GoogleMaps.Symbol`
41 | */
42 | exports.Symbol = React.PropTypes.object;
43 |
44 | /**
45 | * Checks whether a prop provides a `GoogleMaps.MarkerShape`
46 | */
47 | exports.MarkerShape = React.PropTypes.object;
48 |
49 | /**
50 | * Checks whether a prop provides a `GoogleMaps.MapPanes`
51 | */
52 | exports.MapPanes = React.PropTypes.oneOf(['floatPane', 'mapPane', 'markerLayer', 'overlayLayer', 'overlayMouseTarget']);
--------------------------------------------------------------------------------
/src/ui/ReactDefaultInjection.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var ReactMapComponents = require('../ReactMapComponents');
4 | var MapOption = require('./MapOption');
5 | var MapEvent = require('./MapEvent');
6 | var MapOptionConfig = require('./MapOptionConfig');
7 | var ReactMap = require('./components/ReactMap');
8 | var ReactOverlayView = require('./components/ReactOverlayView');
9 | var ReactFrag = require('./components/ReactFrag');
10 | var SimpleEventPlugin = require('../eventPlugins/SimpleEventPlugin');
11 | var MouseEventPlugin = require('../eventPlugins/MouseEventPlugin');
12 | var SideEffectEventPlugin = require('../eventPlugins/SideEffectEventPlugin');
13 |
14 | var ReactInjection = {
15 | EventEmitter: null,
16 | MapEvent: MapEvent.injection,
17 | MapOption: MapOption.injection,
18 | MapComponents: ReactMapComponents.injection
19 | };
20 |
21 | function inject() {
22 | ReactInjection.MapEvent.injectEventPluginsByName({
23 | SimpleEventPlugin: SimpleEventPlugin,
24 | MouseEventPlugin: MouseEventPlugin,
25 | SideEffectEventPlugin: SideEffectEventPlugin
26 | });
27 |
28 | ReactInjection.MapComponents.injectComponentClasses({
29 | Map: ReactMap,
30 | OverlayView: ReactOverlayView,
31 | Frag: ReactFrag
32 | });
33 |
34 | ReactInjection.MapOption.injectMapOptionConfig(MapOptionConfig);
35 | }
36 |
37 | module.exports = {
38 | inject: inject
39 | };
40 |
--------------------------------------------------------------------------------
/src/ui/ReactMapComponent.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var invariant = require('invariant');
5 | var GoogleMapsAPI = require('../GoogleMapsAPI');
6 | var ReactMapComponentMixin = require('./ReactMapComponentMixin');
7 |
8 | /**
9 | * React render implementation
10 | *
11 | * @returns {null}
12 | */
13 | function nullRenderer() {
14 | return null;
15 | }
16 |
17 | /**
18 | * Create base constructor for google map class
19 | *
20 | * @param {string} constructorName
21 | * @returns {Function}
22 | */
23 | function createGoogleMapClassConstructor(constructorName) {
24 | var Constructor = GoogleMapsAPI[constructorName];
25 |
26 | invariant(Constructor, 'Google Maps class of `%s` does not exist', constructorName);
27 |
28 | return function() {
29 | return new Constructor();
30 | }
31 | }
32 |
33 |
34 | var ReactMapComponent = {
35 |
36 | /**
37 | * Create base map component of type
38 | *
39 | * @param {string} constructorName
40 | * @param {function?} constructorFn
41 | * @return {ReactComponent}
42 | */
43 | create: function(constructorName, constructorFn) {
44 | return React.createClass({
45 | displayName: constructorName,
46 |
47 | mixins: [ReactMapComponentMixin],
48 |
49 | constructGoogleMapsClass: constructorFn || createGoogleMapClassConstructor(constructorName),
50 |
51 | render: nullRenderer
52 | });
53 | }
54 | };
55 |
56 | module.exports = ReactMapComponent;
57 |
--------------------------------------------------------------------------------
/src/ui/ReactMapComponentMixin.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var assign = require('react/lib/Object.assign');
4 | var invariant = require('invariant');
5 | var MapOption = require('./MapOption');
6 | var MapOptionConfig = require('./MapOptionConfig');
7 | var MapEvent = require('./MapEvent');
8 | var GoogleMapsAPI = require('../GoogleMapsAPI');
9 |
10 | /**
11 | * Cached reset map option object
12 | */
13 | var resetMapOptionObject = {map: null};
14 |
15 | /**
16 | * Empty props cache
17 | */
18 | var emptyPropsCache = {};
19 |
20 | /**
21 | * Empty function to be provided to event handlers
22 | */
23 | function noop() {}
24 |
25 | /**
26 | * GoogleMaps React component mixin
27 | */
28 | var ReactMapComponentMixin = {
29 | propTypes: assign({}, MapOptionConfig.Options),
30 |
31 | shouldComponentUpdate: function() {
32 | return this.__shouldComponentUpdate;
33 | },
34 |
35 | componentDidMount: function() {
36 | this.__shouldComponentUpdate = true;
37 | this.__dirtyOptions = {};
38 | this.__eventCache = {};
39 | this.__node = this.constructGoogleMapsClass();
40 | this._setInitialMapProperties();
41 | this._updateMapProperties(emptyPropsCache);
42 | },
43 |
44 | componentDidUpdate: function(prevProps) {
45 | this._updateMapProperties(prevProps);
46 | },
47 |
48 | _setInitialMapProperties: function() {
49 | var initialMapOptions = {};
50 | var props = this.props;
51 | for (var propKey in props) {
52 | var optionName = MapOption.getInitialOptionName[propKey];
53 | if (!props.hasOwnProperty(propKey) || !optionName) {
54 | continue;
55 | }
56 |
57 | initialMapOptions[optionName] = props[propKey];
58 | }
59 |
60 | this.flushOptionChanges(initialMapOptions);
61 | },
62 |
63 | _updateMapProperties: function(lastProps) {
64 | var nextProps = this.props;
65 | var mapOptionChanges = {};
66 | var mapEventChanges = {};
67 | var propKey;
68 |
69 | // Remove any options or events that no longer
70 | // exist in the new set of props.
71 | for (propKey in lastProps) {
72 | if (nextProps.hasOwnProperty(propKey) ||
73 | !lastProps.hasOwnProperty(propKey)) {
74 | continue;
75 | }
76 |
77 | if (MapEvent.isStandardName[propKey]) {
78 | mapEventChanges[propKey] = null;
79 | } else if (MapOption.isStandardName[propKey]) {
80 | mapOptionChanges[propKey] = null;
81 | }
82 | }
83 |
84 | // Add any changed options or new events.
85 | for (propKey in nextProps) {
86 | var nextProp = nextProps[propKey];
87 | var lastProp = lastProps[propKey];
88 | if (!nextProps.hasOwnProperty(propKey) ||
89 | (nextProp === lastProp && !this.__dirtyOptions[propKey])) {
90 | continue;
91 | }
92 |
93 | if (MapEvent.isStandardName[propKey] && !lastProp) {
94 | mapEventChanges[propKey] = nextProp;
95 | } else if (MapOption.isStandardName[propKey]) {
96 | mapOptionChanges[MapOption.getOptionName[propKey]] = nextProps[propKey];
97 | }
98 | }
99 |
100 | // Added check of changed options that have side effect events,
101 | // if they don't have a handler then add a noop one for this event
102 | // to trigger the side effect dirty checking.
103 | for (propKey in mapOptionChanges) {
104 | var sideEffectEvent = MapEvent.getOptionSideEffectEvent[propKey];
105 | if (!mapOptionChanges.hasOwnProperty(propKey) || !sideEffectEvent || nextProps[sideEffectEvent]) {
106 | continue;
107 | }
108 |
109 | var hasNextProp = nextProps[propKey] != null;
110 | var hasLastProp = lastProps[propKey] != null;
111 |
112 | if (hasLastProp && !hasNextProp) {
113 | mapEventChanges[sideEffectEvent] = null;
114 | } else if (!hasLastProp && hasNextProp) {
115 | mapEventChanges[sideEffectEvent] = noop;
116 | }
117 | }
118 |
119 | this.flushOptionChanges(mapOptionChanges);
120 | this.flushEventChanges(mapEventChanges);
121 | },
122 |
123 | flushOptionChanges: function(optionChanges) {
124 | MapEvent.setEnabled(false);
125 | this.__node.setOptions(optionChanges);
126 | MapEvent.setEnabled(true);
127 | },
128 |
129 | flushEventChanges: function(eventChanges) {
130 | for (var eventName in eventChanges) {
131 | if (!eventChanges.hasOwnProperty(eventName)) {
132 | continue;
133 | }
134 |
135 | if (eventChanges[eventName]) {
136 | this.putListener(eventName);
137 | } else {
138 | this.deleteListener(eventName);
139 | }
140 | }
141 | },
142 |
143 | componentWillUnmount: function() {
144 | this.deleteAllListeners();
145 |
146 | if (this.props.map) {
147 | // If we still have a map prop at this point we should unset it from the node
148 | this.flushOptionChanges(resetMapOptionObject);
149 | }
150 | this.__node = null;
151 | },
152 |
153 | putListener: function(eventName) {
154 | invariant(!this.__eventCache[eventName], 'Already has `%s` event bound', eventName);
155 |
156 | this.__eventCache[eventName] = GoogleMapsAPI.event.addListener(
157 | this.__node,
158 | MapEvent.getEventName[eventName],
159 | MapEvent.createEventDispatcher(eventName, this)
160 | );
161 | },
162 |
163 | deleteListener: function(eventName) {
164 | invariant(this.__eventCache[eventName], 'No event of `%s` bound to remove', eventName);
165 |
166 | GoogleMapsAPI.event.removeListener(this.__eventCache[eventName]);
167 | delete this.__eventCache[eventName];
168 | },
169 |
170 | deleteAllListeners: function() {
171 | for (var eventName in this.__eventCache) {
172 | if (this.__eventCache.hasOwnProperty(eventName)) {
173 | this.deleteListener(eventName);
174 | }
175 | }
176 | },
177 |
178 | queueDirtyCheck: function() {
179 | this.__shouldComponentUpdate = false;
180 | },
181 |
182 | flushDirtyChangesTo: function(effects) {
183 | this.__shouldComponentUpdate = true;
184 | this.__dirtyOptions[effects] = true;
185 | this.forceUpdate();
186 | this.__dirtyOptions = {};
187 | },
188 |
189 | getMapNode: function() {
190 | return this.__node;
191 | }
192 | };
193 |
194 | module.exports = ReactMapComponentMixin;
195 |
--------------------------------------------------------------------------------
/src/ui/__tests__/ReactMapComponent-test.js:
--------------------------------------------------------------------------------
1 |
2 | jest.dontMock('../ReactMapComponent');
3 | jest.mock('react');
4 |
5 | describe('ReactMapComponent', function() {
6 | describe('create', function() {
7 | it('should return result of React.createClass', function() {
8 | var ReactMapComponent = require('../ReactMapComponent');
9 | var ReactMapComponentMixin = require('../ReactMapComponentMixin');
10 | var React = require('react');
11 |
12 | var createReturn = {};
13 | React.createClass.mockReturnValue(createReturn);
14 |
15 | var component = ReactMapComponent.create('Map');
16 |
17 | expect(React.createClass).toBeCalled();
18 | expect(component).toBe(createReturn);
19 |
20 | var spec = React.createClass.mock.calls[0][0];
21 | expect(spec.mixins[0]).toBe(ReactMapComponentMixin);
22 | expect(spec.mixins.length).toBe(1);
23 | });
24 |
25 | it('should add default render function', function() {
26 | var ReactMapComponent = require('../ReactMapComponent');
27 | var React = require('react');
28 |
29 | ReactMapComponent.create('Map');
30 |
31 | var spec = React.createClass.mock.calls[0][0];
32 | expect(spec.render).toEqual(jasmine.any(Function));
33 | });
34 | });
35 | });
--------------------------------------------------------------------------------
/src/ui/__tests__/ReactMapComponentMixin-test.js:
--------------------------------------------------------------------------------
1 |
2 | jest.dontMock('../ReactMapComponentMixin');
3 |
4 | describe('ReactMapComponentMixin', function() {
5 | describe('Map Options', function() {
6 | var Component;
7 | var node;
8 |
9 | beforeEach(function() {
10 | var ReactMapComponentMixin = require('../ReactMapComponentMixin');
11 | var MapOption = require('../MapOption');
12 | var MapEvent = require('../MapEvent');
13 | var React = require('react');
14 |
15 | node = {
16 | setOptions: jest.genMockFn()
17 | };
18 |
19 | MapOption.isStandardName = {
20 | option1: true,
21 | option2: true,
22 | option3: true,
23 | map: true
24 | };
25 |
26 | MapOption.getOptionName = {
27 | option1: 'option1',
28 | option2: 'option2',
29 | option3: 'option3',
30 | map: 'map'
31 | };
32 |
33 | MapOption.getInitialOptionName = {
34 | initialOption1: 'option1',
35 | initialOption2: 'option2',
36 | initialOption3: 'option3'
37 | };
38 |
39 | Component = React.createClass({
40 | mixins: [ReactMapComponentMixin],
41 | constructGoogleMapsClass: jest.genMockFn().mockReturnValue(node),
42 | render: function() {return null}
43 | });
44 | });
45 |
46 | it('should add all initial options on mount', function() {
47 | var React = require('react');
48 | var ReactTestUtils = require('react/lib/ReactTestUtils');
49 | var noop = jest.genMockFn();
50 |
51 | ReactTestUtils.renderIntoDocument(
52 |
57 | );
58 |
59 | expect(node.setOptions.mock.calls.length).toBe(2);
60 | expect(node.setOptions).toBeCalledWith({option1: noop, option2: null, option3: 'hi'});
61 | expect(node.setOptions).toBeCalledWith({});
62 | });
63 |
64 | it('should add all options on mount', function() {
65 | var React = require('react');
66 | var ReactTestUtils = require('react/lib/ReactTestUtils');
67 | var noop = jest.genMockFn();
68 |
69 | ReactTestUtils.renderIntoDocument(
70 |
75 | );
76 |
77 | expect(node.setOptions.mock.calls.length).toBe(2);
78 | expect(node.setOptions).toBeCalledWith({});
79 | expect(node.setOptions).toBeCalledWith({option1: noop, option2: null, option3: 'hi'});
80 | });
81 |
82 | it('should add new options on update', function() {
83 | var React = require('react');
84 | var noop = jest.genMockFn();
85 |
86 | var container = document.createElement('div');
87 | React.render(
88 | ,
91 | container
92 | );
93 |
94 | node.setOptions.mockClear();
95 | React.render(
96 | ,
99 | container
100 | );
101 |
102 | expect(node.setOptions.mock.calls.length).toBe(1);
103 | expect(node.setOptions).toBeCalledWith({option2: noop});
104 | });
105 |
106 | it('should set changed options on update', function() {
107 | var React = require('react');
108 | var noop = jest.genMockFn();
109 | var noop2 = jest.genMockFn();
110 |
111 | var container = document.createElement('div');
112 | React.render(
113 | ,
117 | container
118 | );
119 |
120 | node.setOptions.mockClear();
121 | React.render(
122 | ,
126 | container
127 | );
128 |
129 | expect(node.setOptions.mock.calls.length).toBe(1);
130 | expect(node.setOptions).toBeCalledWith({option1: noop2, option2: null});
131 | });
132 |
133 | it('should null old options on update', function() {
134 | var React = require('react');
135 | var noop = jest.genMockFn();
136 |
137 | var container = document.createElement('div');
138 | React.render(
139 | ,
143 | container
144 | );
145 |
146 | node.setOptions.mockClear();
147 | React.render(
148 | ,
150 | container
151 | );
152 |
153 | expect(node.setOptions.mock.calls.length).toBe(1);
154 | expect(node.setOptions).toBeCalledWith({option2: null});
155 | });
156 |
157 | it('should null `map` option on unmount', function() {
158 | var React = require('react');
159 |
160 | var map = {};
161 | var container = document.createElement('div');
162 | React.render(
163 | ,
167 | container
168 | );
169 |
170 | node.setOptions.mockClear();
171 | React.unmountComponentAtNode(container);
172 |
173 | expect(node.setOptions.mock.calls.length).toBe(1);
174 | expect(node.setOptions).toBeCalledWith({map: null});
175 | });
176 | });
177 |
178 | describe('Map Events', function() {
179 | var Component;
180 | var node;
181 |
182 | beforeEach(function() {
183 | var ReactMapComponentMixin = require('../ReactMapComponentMixin');
184 | var MapOption = require('../MapOption');
185 | var MapEvent = require('../MapEvent');
186 | var React = require('react');
187 |
188 | node = {
189 | setOptions: jest.genMockFn()
190 | };
191 |
192 | MapOption.isStandardName = {
193 | sideEffectOption: true
194 | };
195 | MapOption.getOptionName = {
196 | sideEffectOption: 'sideEffectOption'
197 | };
198 |
199 | MapEvent.isStandardName = {
200 | event1: true,
201 | event2: true,
202 | event3: true,
203 | sideEffectEvent: true
204 | };
205 | MapEvent.getEventName = {
206 | event1: 'googleMapsEvent1',
207 | event2: 'googleMapsEvent2',
208 | event3: 'googleMapsEvent3',
209 | sideEffectEvent: 'googleMapsEvent4'
210 | };
211 | MapEvent.getOptionSideEffectEvent = {
212 | sideEffectOption: 'sideEffectEvent'
213 | };
214 | MapEvent.createEventDispatcher.mockReturnValue(jest.genMockFn());
215 |
216 | Component = React.createClass({
217 | mixins: [ReactMapComponentMixin],
218 | constructGoogleMapsClass: jest.genMockFn().mockReturnValue(node),
219 | render: function() {return null}
220 | });
221 | });
222 |
223 | it('should add all events on mount', function() {
224 | var React = require('react');
225 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
226 | var ReactTestUtils = require('react/lib/ReactTestUtils');
227 | var noop = jest.genMockFn();
228 |
229 | ReactTestUtils.renderIntoDocument(
230 |
235 | );
236 |
237 | expect(GoogleMapsAPI.event.addListener.mock.calls.length).toBe(3);
238 | expect(GoogleMapsAPI.event.addListener).toBeCalledWith(node, 'googleMapsEvent1', jasmine.any(Function));
239 | expect(GoogleMapsAPI.event.addListener).toBeCalledWith(node, 'googleMapsEvent2', jasmine.any(Function));
240 | expect(GoogleMapsAPI.event.addListener).toBeCalledWith(node, 'googleMapsEvent4', jasmine.any(Function));
241 | });
242 |
243 | it('should add new events on update', function() {
244 | var React = require('react');
245 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
246 | var noop = jest.genMockFn();
247 |
248 | var container = document.createElement('div');
249 | React.render(
250 | ,
253 | container
254 | );
255 |
256 | GoogleMapsAPI.event.addListener.mockClear();
257 | React.render(
258 | ,
262 | container
263 | );
264 |
265 | expect(GoogleMapsAPI.event.addListener.mock.calls.length).toBe(2);
266 | expect(GoogleMapsAPI.event.addListener).toBeCalledWith(node, 'googleMapsEvent2', jasmine.any(Function));
267 | expect(GoogleMapsAPI.event.addListener).toBeCalledWith(node, 'googleMapsEvent4', jasmine.any(Function));
268 | });
269 |
270 | it('should not change events when side effect option is dirty', function() {
271 | var React = require('react');
272 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
273 |
274 | var container = document.createElement('div');
275 | var instance = React.render(
276 | ,
278 | container
279 | );
280 |
281 | GoogleMapsAPI.event.addListener.mockClear();
282 |
283 | instance.__dirtyOptions['sideEffectOption'] = true;
284 | React.render(
285 | ,
287 | container
288 | );
289 |
290 | expect(GoogleMapsAPI.event.addListener).not.toBeCalled();
291 | expect(GoogleMapsAPI.event.removeListener).not.toBeCalled();
292 | });
293 |
294 | it('should remove old events on update', function() {
295 | var React = require('react');
296 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
297 | var noop = jest.genMockFn();
298 | var EventReturn = {};
299 | GoogleMapsAPI.event.addListener.mockReturnValue(EventReturn);
300 |
301 | var container = document.createElement('div');
302 | React.render(
303 | ,
308 | container
309 | );
310 |
311 | GoogleMapsAPI.event.removeListener.mockClear();
312 | React.render(
313 | ,
315 | container
316 | );
317 |
318 | expect(GoogleMapsAPI.event.removeListener.mock.calls.length).toBe(2);
319 | expect(GoogleMapsAPI.event.removeListener).toBeCalledWith(EventReturn);
320 | });
321 |
322 | it('should remove all events on unmount', function() {
323 | var React = require('react');
324 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
325 | var noop = jest.genMockFn();
326 | var EventReturn = {};
327 | GoogleMapsAPI.event.addListener.mockReturnValue(EventReturn);
328 |
329 | var container = document.createElement('div');
330 | React.render(
331 | ,
336 | container
337 | );
338 |
339 | React.unmountComponentAtNode(container);
340 |
341 | expect(GoogleMapsAPI.event.removeListener.mock.calls.length).toBe(3);
342 | expect(GoogleMapsAPI.event.removeListener).toBeCalledWith(EventReturn);
343 | });
344 | });
345 | });
346 |
--------------------------------------------------------------------------------
/src/ui/components/ReactFrag.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var cloneWithProps = require('react/lib/cloneWithProps');
5 | var MapPropTypes = require('../MapPropTypes');
6 |
7 | function injectMapInto(child) {
8 | return React.isValidElement(child) ?
9 | React.cloneElement(child, {map: this.props.map}) : child;
10 | }
11 |
12 | var ReactFrag = React.createClass({
13 | propTypes: {
14 | map: MapPropTypes.Map.isRequired,
15 | },
16 |
17 | render: function() {
18 | // Inject the `mapProps` into all children that are
19 | // valid components.
20 | var children = React.Children
21 | .map(this.props.children, injectMapInto, this);
22 |
23 | return (
24 | {children}
25 | );
26 | }
27 | });
28 |
29 | module.exports = ReactFrag;
30 |
--------------------------------------------------------------------------------
/src/ui/components/ReactMap.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var ReactDOM = require('react-dom');
5 | var cloneWithProps = require('react/lib/cloneWithProps');
6 | var keyMirror = require('fbjs/lib/keyMirror');
7 | var ReactMapComponents = require('../../ReactMapComponents');
8 | var MapPropTypes = require('../MapPropTypes');
9 | var PropTypeUtils = require('../../utils/PropTypeUtils');
10 | var ReactFrag = require('./ReactFrag');
11 |
12 | var GoogleMapsMap = ReactMapComponents.Map;
13 |
14 | // TODO: Remove the need for this, we shouldn't need to render 3 times to initialise
15 | var MapLifeCycle = keyMirror({
16 | CREATING_HOLDER: null,
17 | CREATING_MAP: null
18 | });
19 |
20 | var ReactMap = React.createClass({
21 | propTypes: {
22 | zoom: PropTypeUtils.or('initialZoom', React.PropTypes.number).isRequired,
23 | center: PropTypeUtils.or('initialCenter', MapPropTypes.LatLng).isRequired,
24 | width: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string]),
25 | height: React.PropTypes.oneOfType([React.PropTypes.number, React.PropTypes.string])
26 | },
27 |
28 | getInitialState: function() {
29 | return {
30 | mapLifeCycleState: MapLifeCycle.CREATING_HOLDER
31 | };
32 | },
33 |
34 | componentDidMount: function() {
35 | // Now we have the map created, we need to run the render
36 | // cycle again to pass down the `map` holder for the
37 | // components to render into.
38 | this.setState({mapLifeCycleState: MapLifeCycle.CREATING_MAP});
39 | },
40 |
41 | componentDidUpdate: function() {
42 | if (this.state.mapLifeCycleState === MapLifeCycle.CREATING_MAP) {
43 | this.setState({mapLifeCycleState: null});
44 | }
45 | },
46 |
47 | render: function() {
48 | var holderStyle = {
49 | width: this.props.width,
50 | height: this.props.height
51 | };
52 |
53 | var map;
54 | if (this.state.mapLifeCycleState !== MapLifeCycle.CREATING_HOLDER) {
55 | map = (
56 |
62 | );
63 | }
64 |
65 | var children;
66 | if (!this.state.mapLifeCycleState) {
67 | children = (
68 |
69 | {this.props.children}
70 |
71 | );
72 | }
73 |
74 | return (
75 |
76 |
80 | {map}
81 | {children}
82 |
83 | );
84 | }
85 | });
86 |
87 | module.exports = ReactMap;
88 |
--------------------------------------------------------------------------------
/src/ui/components/ReactOverlayView.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | var React = require('react');
4 | var ReactDOM = require('react-dom');
5 | var assign = require('react/lib/Object.assign');
6 | var cloneWithProps = require('react/lib/cloneWithProps');
7 | var GoogleMapsAPI = require('../../GoogleMapsAPI');
8 | var MapPropTypes = require('../MapPropTypes');
9 |
10 | function MapOverlayView(props) {
11 | this.props = props;
12 | this.setMap(props.map);
13 | }
14 |
15 | MapOverlayView.prototype = new GoogleMapsAPI.OverlayView();
16 |
17 | MapOverlayView.prototype.onAdd = function() {
18 | this._containerElement = document.createElement('div');
19 | this.getPanes()[this.props.mapPane]
20 | .appendChild(this._containerElement);
21 | };
22 |
23 | MapOverlayView.prototype.draw = function() {
24 | var props = assign({}, this.props, {position: null, mapPane: null});
25 | if (this.props.position) {
26 | var point = this.getProjection()
27 | .fromLatLngToDivPixel(this.props.position);
28 |
29 | props.style = assign({}, {
30 | position: 'absolute',
31 | left: point.x,
32 | top: point.y
33 | }, this.props.style);
34 | }
35 |
36 | ReactDOM.render(
37 | React.cloneElement(
, props),
38 | this._containerElement
39 | )
40 | };
41 |
42 | MapOverlayView.prototype.onRemove = function() {
43 | React.unmountComponentAtNode(this._containerElement);
44 | this._containerElement.parentNode
45 | .removeChild(this._containerElement);
46 | this._containerElement = null;
47 | };
48 |
49 | var ReactOverlayView = React.createClass({
50 | displayName: 'OverlayView',
51 |
52 | propTypes: {
53 | mapPane: MapPropTypes.MapPanes.isRequired
54 | },
55 |
56 | getDefaultProps: function() {
57 | return {
58 | mapPane: 'overlayLayer'
59 | };
60 | },
61 |
62 | render: function() {
63 | // Nothing to render
64 | return null;
65 | },
66 |
67 | componentDidMount: function() {
68 | this.__node = new MapOverlayView(this.props);
69 | },
70 |
71 | componentDidUpdate: function(prevProps) {
72 | this.__node.props = this.props;
73 | this.__node.draw();
74 |
75 | if (this.props.mapPane != prevProps.mapPane) {
76 | // Unmount then, mount again onto the correct map pane
77 | this.__node.setMap(null);
78 | this.__node.setMap(this.props.map);
79 | }
80 | },
81 |
82 | componentWillUnmount: function() {
83 | this.__node.setMap(null);
84 | this.__node = null;
85 | }
86 | });
87 |
88 | module.exports = ReactOverlayView;
89 |
--------------------------------------------------------------------------------
/src/ui/components/__tests__/ReactFrag-test.js:
--------------------------------------------------------------------------------
1 |
2 | jest.dontMock('../ReactFrag');
3 |
4 | describe('ReactFrag', function() {
5 | it('should pass all props to all valid children', function() {
6 | var React = require('react');
7 | var ReactTestUtils = require('react/lib/ReactTestUtils');
8 | var ReactFrag = require('../ReactFrag');
9 |
10 | var didMount = jest.genMockFn();
11 | var Test = React.createClass({
12 | render: function() { return null; },
13 | componentDidMount: function() {
14 | didMount(this.props);
15 | }
16 | });
17 |
18 | var map = {};
19 | var testProp = {};
20 | ReactTestUtils.renderIntoDocument(
21 |
22 |
23 | {null}
24 | {false}
25 |
26 |
27 |
28 |
29 | );
30 |
31 | expect(didMount.mock.calls.length).toBe(2);
32 | expect(didMount.mock.calls[0][0]).toEqual({map: map});
33 | expect(didMount.mock.calls[1][0]).toEqual({map: map});
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/src/utils/PropTypeUtils.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | /**
4 | * Check if a prop or another specified prop has a valid value
5 | * @type {function}
6 | */
7 | exports.or = createChainableOrTypeChecker;
8 |
9 | function createChainableOrTypeChecker(orPropName, validate) {
10 | function checkType(isRequired, props, propName, componentName) {
11 | componentName = componentName || ANONYMOUS;
12 | if (props[propName] == null && props[orPropName] == null) {
13 | if (isRequired) {
14 | return new Error(
15 | 'Required prop `' + propName + '` or `' + orPropName + '` was not specified in ' +
16 | '`' + componentName + '`.'
17 | );
18 | }
19 | } else {
20 | var validatePropName = props[propName] == null ? orPropName : propName;
21 | return validate(props, validatePropName, componentName);
22 | }
23 | }
24 |
25 | var chainedCheckType = checkType.bind(null, false);
26 | chainedCheckType.isRequired = checkType.bind(null, true);
27 |
28 | return chainedCheckType;
29 | }
--------------------------------------------------------------------------------
/src/utils/__tests__/PropTypeUtils-test.js:
--------------------------------------------------------------------------------
1 |
2 | jest.dontMock('../PropTypeUtils');
3 |
4 | describe('PropTypeUtils', function() {
5 | describe('or', function() {
6 | var validate, validateRequired;
7 |
8 | beforeEach(function() {
9 | var PropTypeUtils = require('../PropTypeUtils');
10 | var React = require('react');
11 | var propType = PropTypeUtils.or('initialP', React.PropTypes.number);
12 |
13 | validate = function validate(prop, initialProp) {
14 | return propType({p: prop, initialP: initialProp}, 'p', 'Component');
15 | };
16 | validateRequired = function validateRequired(prop, initialProp) {
17 | return propType.isRequired({p: prop, initialP: initialProp}, 'p', 'Component');
18 | };
19 | });
20 |
21 | it('Should return error with valid values', function() {
22 | expect(validateRequired()).toEqual(jasmine.any(Error));
23 | expect(validateRequired(null)).toEqual(jasmine.any(Error));
24 | expect(validate('hi', 10)).toEqual(jasmine.any(Error));
25 | expect(validate(null, 'hi')).toEqual(jasmine.any(Error));
26 | });
27 |
28 | it('Should return null with valid values', function() {
29 | expect(validateRequired(10)).toBeNull();
30 | expect(validateRequired(null, 10)).toBeNull();
31 | expect(validateRequired(10, 'num')).toBeNull();
32 | expect(validate(10)).toBeNull();
33 | expect(validate(null, 10)).toBeNull();
34 | });
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var fs = require('fs');
3 |
4 | function buildEntries() {
5 | return fs.readdirSync('examples').reduce(function(entries, dir) {
6 | if (dir === 'build') {
7 | return entries;
8 | }
9 | var isDraft = dir.charAt(0) === '_';
10 | if (!isDraft && fs.lstatSync(path.join('examples', dir)).isDirectory()) {
11 | entries[dir] = './examples/' + dir + '/' + 'app.js';
12 | }
13 | return entries;
14 | }, {});
15 | }
16 |
17 | module.exports = {
18 | entry: buildEntries(),
19 |
20 | output: {
21 | filename: '[name].js',
22 | chunkFilename: '[id].chunk.js',
23 | path: path.join(__dirname, 'examples', 'build'),
24 | publicPath: '../build/'
25 | },
26 |
27 | module: {
28 | loaders: [
29 | {test: /\.js$/, loader: 'jsx-loader'}
30 | ]
31 | }
32 | };
--------------------------------------------------------------------------------