├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── dist ├── idle-maps.js └── idle-maps.min.js ├── examples ├── index.html ├── index.jsx ├── map-component.jsx └── overlay-component.jsx ├── index.js ├── lib ├── google-maps-mixin.js ├── map-instance-mixin.jsx └── overlay-mixin.js ├── package.json ├── test ├── google-maps-mixin.spec.js ├── helpers │ └── jsdom.js ├── map-instance-mixin.spec.js └── overlay-mixin.spec.js └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [package.json] 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | examples/app.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true, 4 | "browser": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "ecmaFeatures": { 9 | "modules": true 10 | }, 11 | "rules": { 12 | "no-alert": 2, 13 | "no-array-constructor": 2, 14 | "no-caller": 2, 15 | "no-bitwise": 2, 16 | "no-catch-shadow": 2, 17 | "no-console": 2, 18 | "no-comma-dangle": 2, 19 | "no-control-regex": 2, 20 | "no-debugger": 2, 21 | "no-div-regex": 2, 22 | "no-dupe-keys": 2, 23 | "no-else-return": 2, 24 | "no-empty": 2, 25 | "no-empty-class": 2, 26 | "no-eq-null": 2, 27 | "no-eval": 2, 28 | "no-ex-assign": 2, 29 | "no-extra-semi": 2, 30 | "no-func-assign": 2, 31 | "no-floating-decimal": 2, 32 | "no-implied-eval": 2, 33 | "no-with": 2, 34 | "no-fallthrough": 2, 35 | "no-unreachable": 2, 36 | "no-undef": 2, 37 | "no-undef-init": 2, 38 | "no-unused-expressions": 2, 39 | "no-octal": 2, 40 | "no-octal-escape": 2, 41 | "no-obj-calls": 2, 42 | "no-multi-str": 2, 43 | "no-new-wrappers": 2, 44 | "no-new": 2, 45 | "no-new-func": 2, 46 | "no-native-reassign": 2, 47 | "no-plusplus": 2, 48 | "no-delete-var": 2, 49 | "no-return-assign": 2, 50 | "no-new-object": 2, 51 | "no-label-var": 2, 52 | "no-ternary": 0, 53 | "no-self-compare": 2, 54 | "no-sync": 2, 55 | "no-underscore-dangle": 2, 56 | "no-loop-func": 2, 57 | "no-empty-label": 2, 58 | "no-unused-vars": 2, 59 | "no-script-url": 2, 60 | "no-proto": 2, 61 | "no-iterator": 2, 62 | "no-mixed-requires": [0, false], 63 | "no-wrap-func": 2, 64 | "no-shadow": 2, 65 | "no-use-before-define": 2, 66 | "no-redeclare": 2, 67 | "no-regex-spaces": 2, 68 | "brace-style": [2, "1tbs"], 69 | "block-scoped-var": 0, 70 | "camelcase": 2, 71 | "complexity": [0, 4], 72 | "curly": 2, 73 | "dot-notation": 2, 74 | "eqeqeq": 2, 75 | "global-strict": 0, 76 | "guard-for-in": 0, 77 | "max-depth": [0, 4], 78 | "max-len": [0, 80, 4], 79 | "max-params": [0, 3], 80 | "max-statements": [0, 10], 81 | "new-cap": 2, 82 | "new-parens": 2, 83 | "one-var": 2, 84 | "quotes": [2, "single"], 85 | "quote-props": 0, 86 | "radix": 0, 87 | "semi": 2, 88 | "space-after-keywords": [2, "always", { "checkFunctionKeyword": true } ], 89 | "space-before-blocks": [2, "always"], 90 | "strict": 2, 91 | "unnecessary-strict": 0, 92 | "use-isnan": 2, 93 | "valid-typeof": 0, 94 | "wrap-iife": 2, 95 | "wrap-regex": 0, 96 | "yoda": "always" 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | examples/app.js 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | test 3 | .idea 4 | .travis.yml 5 | .editorconfig 6 | .eslintrc 7 | .eslintignore 8 | .gitignore 9 | webpack.config.js 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "iojs" 4 | env: 5 | global: 6 | - NODE_ENV=travisci 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zach Pratt 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | React components for [Async Google Maps](https://github.com/zpratt/async-google-maps) 2 | 3 | [](https://travis-ci.org/zpratt/idle-maps) 4 | [](https://www.npmjs.com/package/idle-maps) 5 | 6 | ### Not fully baked yet 7 | 8 | I am still working out some things with the overlay mixin. 9 | 10 | ## Running tests 11 | 12 | This module is 100% test-driven. Please feel free to run the tests and critique them. 13 | 14 | 1. `git clone` ... 15 | 2. Install io.js (needed for jsdom) 16 | 3. `npm i` 17 | 4. `npm test` 18 | 19 | ## Usage 20 | 21 | Take a look at the examples directory for a few samples of how to use the mixins. 22 | 23 | ### Running examples locally 24 | 25 | 1. `npm i` 26 | 2. `npm start` 27 | 3. Navigate to [http://localhost:8080/](http://localhost:8080/) 28 | 29 | ## Documentation 30 | 31 | Coming soon. 32 | -------------------------------------------------------------------------------- /dist/idle-maps.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("react")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["react"], factory); 6 | else if(typeof exports === 'object') 7 | exports["IdleMaps"] = factory(require("react")); 8 | else 9 | root["IdleMaps"] = factory(root["React"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_5__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = ""; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | var GoogleMapsApiMixin = __webpack_require__(1), 60 | MapInstanceMixin = __webpack_require__(3), 61 | OverlayViewMixin = __webpack_require__(2); 62 | 63 | module.exports = { 64 | GoogleMapsApiMixin: GoogleMapsApiMixin, 65 | MapInstanceMixin: MapInstanceMixin, 66 | OverlayViewMixin: OverlayViewMixin 67 | }; 68 | 69 | 70 | /***/ }, 71 | /* 1 */ 72 | /***/ function(module, exports, __webpack_require__) { 73 | 74 | 'use strict'; 75 | 76 | var MapLoader = __webpack_require__(4).MapLoader; 77 | 78 | module.exports = { 79 | componentWillMount: function () { 80 | MapLoader.load({ 81 | key: this.props.apiKey, 82 | version: this.props.apiVersion 83 | }); 84 | } 85 | }; 86 | 87 | 88 | /***/ }, 89 | /* 2 */ 90 | /***/ function(module, exports, __webpack_require__) { 91 | 92 | 'use strict'; 93 | 94 | var OverlayFactory = __webpack_require__(4).BaseOverlayFactory; 95 | 96 | module.exports = { 97 | componentDidMount: function () { 98 | var element = this.getDOMNode(); 99 | 100 | OverlayFactory.create({ 101 | point: this.props.point, 102 | el: element, 103 | map: this.props.map 104 | }); 105 | } 106 | }; 107 | 108 | 109 | /***/ }, 110 | /* 3 */ 111 | /***/ function(module, exports, __webpack_require__) { 112 | 113 | /** 114 | * @jsx React.DOM 115 | */ 116 | 117 | 'use strict'; 118 | 119 | var MapLoader = __webpack_require__(4).MapLoader, 120 | 121 | React = __webpack_require__(5); 122 | 123 | module.exports = { 124 | componentDidMount: function () { 125 | MapLoader.create(this.getDOMNode(), this.props.mapOptions).then(function (map) { 126 | this.idle(map); 127 | }.bind(this)); 128 | }, 129 | render: function () { 130 | return ( 131 | React.createElement("div", {className: "map-container"}, 132 | React.createElement("div", {className: "test"}) 133 | ) 134 | ); 135 | } 136 | }; 137 | 138 | 139 | /***/ }, 140 | /* 4 */ 141 | /***/ function(module, exports, __webpack_require__) { 142 | 143 | 'use strict'; 144 | 145 | /** 146 | * @author zach pratt 147 | * @licence MIT 148 | * @module async-google-maps 149 | */ 150 | 151 | var MapLoader = __webpack_require__(6), 152 | BaseOverlayFactory = __webpack_require__(7); 153 | 154 | module.exports = { 155 | /** 156 | * See {@link async-google-maps/map-loader} 157 | * */ 158 | MapLoader: MapLoader, 159 | /** 160 | * See {@link async-google-maps/base-overlay-factory} 161 | * */ 162 | BaseOverlayFactory: BaseOverlayFactory 163 | }; 164 | 165 | 166 | /***/ }, 167 | /* 5 */ 168 | /***/ function(module, exports, __webpack_require__) { 169 | 170 | module.exports = __WEBPACK_EXTERNAL_MODULE_5__; 171 | 172 | /***/ }, 173 | /* 6 */ 174 | /***/ function(module, exports, __webpack_require__) { 175 | 176 | /* WEBPACK VAR INJECTION */(function(global) {'use strict'; 177 | 178 | /** 179 | * @module async-google-maps/map-loader 180 | */ 181 | 182 | var URL_PREFIX = '//maps.googleapis.com/maps/api/js', 183 | CALLBACK_IDENTIFIER = 'mapLoaded', 184 | 185 | mapLoadedDeferred, 186 | mapLoadedPromise; 187 | 188 | global[CALLBACK_IDENTIFIER] = function mapLoaded() { 189 | mapLoadedDeferred.resolve(); 190 | }; 191 | 192 | function buildUrl(options) { 193 | return URL_PREFIX 194 | + '?v=' + options.version 195 | + '&key=' + options.key 196 | + '&callback=' + CALLBACK_IDENTIFIER; 197 | } 198 | 199 | function createDeferredToAllowForResolutionAfterGoogleMapsLoaderFinishes(resolve, reject) { 200 | mapLoadedDeferred = { 201 | resolve: resolve, 202 | reject: reject 203 | }; 204 | } 205 | 206 | function createMapLoadedPromise() { 207 | mapLoadedPromise = new Promise(function (resolve, reject) { 208 | createDeferredToAllowForResolutionAfterGoogleMapsLoaderFinishes(resolve, reject); 209 | }); 210 | } 211 | 212 | function appendScriptOnDocumentReady(scriptEl) { 213 | document.addEventListener('DOMContentLoaded', function () { 214 | document.head.appendChild(scriptEl); 215 | }); 216 | } 217 | 218 | function injectGoogleMapsScript(options) { 219 | var scriptEl = document.createElement('script'); 220 | scriptEl.src = buildUrl(options); 221 | scriptEl.type = 'text/javascript'; 222 | 223 | appendScriptOnDocumentReady(scriptEl); 224 | } 225 | 226 | function init() { 227 | createMapLoadedPromise(); 228 | } 229 | 230 | init(); 231 | 232 | module.exports = { 233 | /** 234 | * Asynchronously loads the google maps javascript library, given the supplied options. 235 | * Returns a promise that will be resolved once the google maps loader has finished. 236 | * Once the promise resolves, it is safe to reference anything under the `google.maps` namespace. 237 | * This method should only be called once for a given application. 238 | * 239 | * @param {Object} options - The configuration used to load the google maps API 240 | * @param {string} options.version - The version of the google maps API to load 241 | * @param {string} options.key - The API key of the consuming application 242 | * @returns {Promise} 243 | * */ 244 | load: function loadGoogleMapsAsync(options) { 245 | injectGoogleMapsScript(options); 246 | 247 | return mapLoadedPromise; 248 | }, 249 | 250 | /** 251 | * @property {Promise} loaded - A reference to a promise that is resolved once the google maps API is ready. 252 | */ 253 | loaded: (function () { 254 | return mapLoadedPromise; 255 | }()), 256 | 257 | /** 258 | * Creates a map instance given the supplied options. The options will be passed into the `google.maps.Map` constructor, 259 | * therefore, all options from the [google maps api](https://developers.google.com/maps/documentation/javascript/reference#MapOptions) can be used. 260 | * This function returns a promise which will be resolved once the newly created map instance is in the `idle` state, 261 | * which is the point at which overlays, markers, and geometries can be added to the map. 262 | * 263 | * @param mapContainer - The element to attach the map instance to 264 | * @param options - Options passed to the google Map constructor 265 | * @returns {Promise} 266 | * */ 267 | create: function createMap(mapContainer, options) { 268 | return new Promise(function mapIdleResolver(resolve) { 269 | mapLoadedPromise.then(function whenMapHasLoaded() { 270 | var googleMaps = __webpack_require__(8), 271 | mapInstance = new googleMaps.maps.Map(mapContainer, options); 272 | 273 | googleMaps.maps.event.addListenerOnce(mapInstance, 'idle', function mapIdleHandler() { 274 | resolve(mapInstance); 275 | }); 276 | }); 277 | }); 278 | } 279 | }; 280 | 281 | /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) 282 | 283 | /***/ }, 284 | /* 7 */ 285 | /***/ function(module, exports, __webpack_require__) { 286 | 287 | 'use strict'; 288 | 289 | /** 290 | * @module async-google-maps/base-overlay-factory 291 | */ 292 | 293 | var MapLoader = __webpack_require__(6); 294 | 295 | module.exports = { 296 | /** 297 | * Creates an instance of a custom overlay that will position itself on the map based on the overlay dimensions 298 | * and the provided point. 299 | * 300 | * @param options - Options passed to the base overlay constructor 301 | * @param options.el - The element that the overlay will use to append into the overlay pane. 302 | * @param options.point - The lat/lng that the overlay will use to position itself on the map. 303 | */ 304 | create: function (options) { 305 | MapLoader.loaded.then(function () { 306 | var BaseOverlay = __webpack_require__(9), 307 | overlayInstance = new BaseOverlay(options); 308 | 309 | overlayInstance.setMap(options.map); 310 | }); 311 | } 312 | }; 313 | 314 | 315 | /***/ }, 316 | /* 8 */ 317 | /***/ function(module, exports, __webpack_require__) { 318 | 319 | /* WEBPACK VAR INJECTION */(function(global) {'use strict'; 320 | 321 | module.exports = global.google; 322 | 323 | /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) 324 | 325 | /***/ }, 326 | /* 9 */ 327 | /***/ function(module, exports, __webpack_require__) { 328 | 329 | /* WEBPACK VAR INJECTION */(function(global) {'use strict'; 330 | 331 | var google = global.google; 332 | 333 | function onAdd() { 334 | var panes = this.getPanes(); 335 | 336 | panes.overlayLayer.appendChild(this.el); 337 | } 338 | 339 | function positionOverlayByDimensions(projectedLatLng) { 340 | var offsetHeight = this.el.offsetHeight, 341 | offsetWidth = this.el.offsetWidth; 342 | 343 | this.el.style.top = projectedLatLng.y - offsetHeight + 'px'; 344 | this.el.style.left = projectedLatLng.x - Math.floor(offsetWidth / 2) + 'px'; 345 | } 346 | 347 | function draw() { 348 | var projection = this.getProjection(), 349 | projectedLatLng = projection.fromLatLngToDivPixel(this.point); 350 | 351 | positionOverlayByDimensions.call(this, projectedLatLng); 352 | } 353 | 354 | function BaseOverlay(options) { 355 | var point = options.point; 356 | 357 | this.el = options.el; 358 | this.point = new google.maps.LatLng(point.lat, point.lng); 359 | 360 | this.el.style.position = 'absolute'; 361 | } 362 | 363 | BaseOverlay.prototype = Object.create(google.maps.OverlayView.prototype); 364 | BaseOverlay.prototype.constructor = BaseOverlay; 365 | 366 | BaseOverlay.prototype.onAdd = onAdd; 367 | BaseOverlay.prototype.draw = draw; 368 | 369 | module.exports = BaseOverlay; 370 | 371 | /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()))) 372 | 373 | /***/ } 374 | /******/ ]) 375 | }); 376 | ; -------------------------------------------------------------------------------- /dist/idle-maps.min.js: -------------------------------------------------------------------------------- 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("react")):"function"==typeof define&&define.amd?define(["react"],e):"object"==typeof exports?exports.IdleMaps=e(require("react")):t.IdleMaps=e(t.React)}(this,function(t){return function(t){function e(o){if(n[o])return n[o].exports;var i=n[o]={exports:{},id:o,loaded:!1};return t[o].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var n={};return e.m=t,e.c=n,e.p="",e(0)}([function(t,e,n){"use strict";var o=n(1),i=n(3),r=n(2);t.exports={GoogleMapsApiMixin:o,MapInstanceMixin:i,OverlayViewMixin:r}},function(t,e,n){"use strict";var o=n(4).MapLoader;t.exports={componentWillMount:function(){o.load({key:this.props.apiKey,version:this.props.apiVersion})}}},function(t,e,n){"use strict";var o=n(4).BaseOverlayFactory;t.exports={componentDidMount:function(){var t=this.getDOMNode();o.create({point:this.props.point,el:t,map:this.props.map})}}},function(t,e,n){"use strict";var o=n(4).MapLoader,i=n(5);t.exports={componentDidMount:function(){o.create(this.getDOMNode(),this.props.mapOptions).then(function(t){this.idle(t)}.bind(this))},render:function(){return i.createElement("div",{className:"map-container"},i.createElement("div",{className:"test"}))}}},function(t,e,n){"use strict";var o=n(6),i=n(7);t.exports={MapLoader:o,BaseOverlayFactory:i}},function(e){e.exports=t},function(t,e,n){(function(e){"use strict";function o(t){return f+"?v="+t.version+"&key="+t.key+"&callback="+l}function i(t,e){p={resolve:t,reject:e}}function r(){u=new Promise(function(t,e){i(t,e)})}function s(t){document.addEventListener("DOMContentLoaded",function(){document.head.appendChild(t)})}function a(t){var e=document.createElement("script");e.src=o(t),e.type="text/javascript",s(e)}function c(){r()}var p,u,f="//maps.googleapis.com/maps/api/js",l="mapLoaded";e[l]=function(){p.resolve()},c(),t.exports={load:function(t){return a(t),u},loaded:function(){return u}(),create:function(t,e){return new Promise(function(o){u.then(function(){var i=n(8),r=new i.maps.Map(t,e);i.maps.event.addListenerOnce(r,"idle",function(){o(r)})})})}}}).call(e,function(){return this}())},function(t,e,n){"use strict";var o=n(6);t.exports={create:function(t){o.loaded.then(function(){var e=n(9),o=new e(t);o.setMap(t.map)})}}},function(t,e){(function(e){"use strict";t.exports=e.google}).call(e,function(){return this}())},function(t,e){(function(e){"use strict";function n(){var t=this.getPanes();t.overlayLayer.appendChild(this.el)}function o(t){var e=this.el.offsetHeight,n=this.el.offsetWidth;this.el.style.top=t.y-e+"px",this.el.style.left=t.x-Math.floor(n/2)+"px"}function i(){var t=this.getProjection(),e=t.fromLatLngToDivPixel(this.point);o.call(this,e)}function r(t){var e=t.point;this.el=t.el,this.point=new s.maps.LatLng(e.lat,e.lng),this.el.style.position="absolute"}var s=e.google;r.prototype=Object.create(s.maps.OverlayView.prototype),r.prototype.constructor=r,r.prototype.onAdd=n,r.prototype.draw=i,t.exports=r}).call(e,function(){return this}())}])}); -------------------------------------------------------------------------------- /examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |22 | {this.props.message} 23 |
24 |