├── .eslintrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── assets └── powered_by_google.png ├── build ├── react-place.js ├── react-place.min.js └── react-place.min.js.map ├── example-es5-browserify ├── build │ ├── Location.js │ └── app.js ├── index.html └── src │ └── index.js ├── example-es5 └── index.html ├── example-es6 ├── build │ ├── app.js │ └── app.js.map ├── index.html └── src │ └── index.js ├── index.html ├── karma.conf.js ├── lib ├── Location.js ├── Location.js.map └── vendor │ ├── google.js │ └── google.js.map ├── package.json ├── src ├── Location.jsx └── vendor │ └── google.js ├── test ├── fixtures │ └── predictions.js └── test.spec.jsx ├── tests.webpack.js ├── webpack.config.js └── yarn.lock /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "ecmaFeatures": { 3 | "globalReturn": true, 4 | "jsx": true, 5 | "modules": true 6 | }, 7 | 8 | "env": { 9 | "browser": true, 10 | "es6": true, 11 | "node": true 12 | }, 13 | 14 | "globals": { 15 | "document": false, 16 | "escape": false, 17 | "navigator": false, 18 | "unescape": false, 19 | "window": false, 20 | "describe": true, 21 | "before": true, 22 | "it": true, 23 | "expect": true, 24 | "sinon": true 25 | }, 26 | 27 | "parser": "babel-eslint", 28 | 29 | "plugins": [ 30 | "react" 31 | ], 32 | 33 | "rules": { 34 | "block-scoped-var": 2, 35 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 36 | "camelcase": [2, { "properties": "always" }], 37 | "comma-dangle": [2, "never"], 38 | "comma-spacing": [2, { "before": false, "after": true }], 39 | "comma-style": [2, "last"], 40 | "complexity": 0, 41 | "consistent-return": 2, 42 | "consistent-this": 0, 43 | "curly": [2, "multi-line"], 44 | "default-case": 0, 45 | "dot-location": [2, "property"], 46 | "dot-notation": 0, 47 | "eol-last": 2, 48 | "eqeqeq": [2, "allow-null"], 49 | "func-names": 0, 50 | "func-style": 0, 51 | "generator-star-spacing": [2, "both"], 52 | "guard-for-in": 0, 53 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ], 54 | "indent": [2, 2, { "SwitchCase": 1 }], 55 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }], 56 | "linebreak-style": 0, 57 | "max-depth": 0, 58 | "max-len": [2, 120, 4], 59 | "max-nested-callbacks": 0, 60 | "max-params": 0, 61 | "max-statements": 0, 62 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }], 63 | "newline-after-var": [2, "always"], 64 | "new-parens": 2, 65 | "no-alert": 0, 66 | "no-array-constructor": 2, 67 | "no-bitwise": 0, 68 | "no-caller": 2, 69 | "no-catch-shadow": 0, 70 | "no-cond-assign": 2, 71 | "no-console": 0, 72 | "no-constant-condition": 0, 73 | "no-continue": 0, 74 | "no-control-regex": 2, 75 | "no-debugger": 2, 76 | "no-delete-var": 2, 77 | "no-div-regex": 0, 78 | "no-dupe-args": 2, 79 | "no-dupe-keys": 2, 80 | "no-duplicate-case": 2, 81 | "no-else-return": 2, 82 | "no-empty": 0, 83 | "no-empty-character-class": 2, 84 | "no-empty-label": 2, 85 | "no-eq-null": 0, 86 | "no-eval": 2, 87 | "no-ex-assign": 2, 88 | "no-extend-native": 2, 89 | "no-extra-bind": 2, 90 | "no-extra-boolean-cast": 2, 91 | "no-extra-parens": 0, 92 | "no-extra-semi": 0, 93 | "no-extra-strict": 0, 94 | "no-fallthrough": 2, 95 | "no-floating-decimal": 2, 96 | "no-func-assign": 2, 97 | "no-implied-eval": 2, 98 | "no-inline-comments": 0, 99 | "no-inner-declarations": [2, "functions"], 100 | "no-invalid-regexp": 2, 101 | "no-irregular-whitespace": 2, 102 | "no-iterator": 2, 103 | "no-label-var": 2, 104 | "no-labels": 2, 105 | "no-lone-blocks": 0, 106 | "no-lonely-if": 0, 107 | "no-loop-func": 0, 108 | "no-mixed-requires": 0, 109 | "no-mixed-spaces-and-tabs": [2, false], 110 | "no-multi-spaces": 2, 111 | "no-multi-str": 2, 112 | "no-multiple-empty-lines": [2, { "max": 1 }], 113 | "no-native-reassign": 2, 114 | "no-negated-in-lhs": 2, 115 | "no-nested-ternary": 0, 116 | "no-new": 2, 117 | "no-new-func": 2, 118 | "no-new-object": 2, 119 | "no-new-require": 2, 120 | "no-new-wrappers": 2, 121 | "no-obj-calls": 2, 122 | "no-octal": 2, 123 | "no-octal-escape": 2, 124 | "no-path-concat": 0, 125 | "no-plusplus": 0, 126 | "no-process-env": 0, 127 | "no-process-exit": 0, 128 | "no-proto": 2, 129 | "no-redeclare": 2, 130 | "no-regex-spaces": 2, 131 | "no-reserved-keys": 0, 132 | "no-restricted-modules": 0, 133 | "no-return-assign": 2, 134 | "no-script-url": 0, 135 | "no-self-compare": 2, 136 | "no-sequences": 2, 137 | "no-shadow": 0, 138 | "no-shadow-restricted-names": 2, 139 | "no-spaced-func": 2, 140 | "no-sparse-arrays": 2, 141 | "no-sync": 0, 142 | "no-ternary": 0, 143 | "no-throw-literal": 2, 144 | "no-trailing-spaces": 2, 145 | "no-undef": 2, 146 | "no-undef-init": 2, 147 | "no-undefined": 0, 148 | "no-underscore-dangle": 0, 149 | "no-unneeded-ternary": 2, 150 | "no-unreachable": 2, 151 | "no-unused-expressions": 0, 152 | "no-unused-vars": [2, { "vars": "all", "args": "none" }], 153 | "no-use-before-define": 2, 154 | "no-var": 0, 155 | "no-void": 0, 156 | "no-warning-comments": 0, 157 | "no-with": 2, 158 | "one-var": 0, 159 | "operator-assignment": 0, 160 | "operator-linebreak": [2, "after"], 161 | "padded-blocks": 0, 162 | "quote-props": 0, 163 | "quotes": [2, "single", "avoid-escape"], 164 | "radix": 2, 165 | "jsx-quotes": [2, "prefer-single"], 166 | "react/display-name": 0, 167 | "react/jsx-boolean-value": 2, 168 | "react/jsx-no-undef": 2, 169 | "react/jsx-sort-prop-types": 0, 170 | "react/jsx-sort-props": 0, 171 | "react/jsx-uses-react": 2, 172 | "react/jsx-uses-vars": 2, 173 | "react/no-did-mount-set-state": 2, 174 | "react/no-did-update-set-state": 2, 175 | "react/no-multi-comp": 2, 176 | "react/no-unknown-property": 2, 177 | "react/prop-types": 2, 178 | "react/react-in-jsx-scope": 2, 179 | "react/self-closing-comp": 2, 180 | "react/sort-comp": 0, 181 | "react/wrap-multilines": 2, 182 | "semi": [2, "always"], 183 | "semi-spacing": 0, 184 | "sort-vars": 0, 185 | "space-after-keywords": [2, "always"], 186 | "space-before-blocks": [2, "always"], 187 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 188 | "space-in-brackets": 0, 189 | "space-in-parens": [2, "never"], 190 | "space-infix-ops": 2, 191 | "space-return-throw-case": 2, 192 | "space-unary-ops": [2, { "words": true, "nonwords": false }], 193 | "spaced-comment": [2, "always"], 194 | "strict": 0, 195 | "use-isnan": 2, 196 | "valid-jsdoc": 0, 197 | "valid-typeof": 2, 198 | "vars-on-top": 2, 199 | "wrap-iife": [2, "any"], 200 | "wrap-regex": 0, 201 | "yoda": [2, "never"] 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build ... 2 | node_modules 3 | npm-debug.log 4 | public 5 | tmp 6 | 7 | # Created by https://www.gitignore.io 8 | 9 | ### OSX ### 10 | .DS_Store 11 | .AppleDouble 12 | .LSOverride 13 | 14 | # Icon must end with two \r 15 | Icon 16 | 17 | 18 | # Thumbnails 19 | ._* 20 | 21 | # Files that might appear in the root of a volume 22 | .DocumentRevisions-V100 23 | .fseventsd 24 | .Spotlight-V100 25 | .TemporaryItems 26 | .Trashes 27 | .VolumeIcon.icns 28 | 29 | # Directories potentially created on remote AFP share 30 | .AppleDB 31 | .AppleDesktop 32 | Network Trash Folder 33 | Temporary Items 34 | .apdisk 35 | 36 | 37 | ### Vim ### 38 | [._]*.s[a-w][a-z] 39 | [._]s[a-w][a-z] 40 | *.un~ 41 | Session.vim 42 | .netrwhist 43 | *~ 44 | 45 | 46 | ### SublimeText ### 47 | # cache files for sublime text 48 | *.tmlanguage.cache 49 | *.tmPreferences.cache 50 | *.stTheme.cache 51 | 52 | # workspace files are user-specific 53 | *.sublime-workspace 54 | 55 | # project files should be checked into the repository, unless a significant 56 | # proportion of contributors will probably not be using SublimeText 57 | # *.sublime-project 58 | 59 | # sftp configuration file 60 | sftp-config.json 61 | 62 | 63 | ### Node ### 64 | # Logs 65 | logs 66 | *.log 67 | 68 | # Runtime data 69 | pids 70 | *.pid 71 | *.seed 72 | 73 | # Directory for instrumented libs generated by jscoverage/JSCover 74 | lib-cov 75 | 76 | # Coverage directory used by tools like istanbul 77 | coverage 78 | 79 | # node-waf configuration 80 | .lock-wscript 81 | 82 | # Compiled binary addons (http://nodejs.org/api/addons.html) 83 | 84 | # Dependency directory 85 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 86 | node_modules 87 | 88 | 89 | ### Linux ### 90 | *~ 91 | 92 | # KDE directory preferences 93 | .directory 94 | 95 | # Linux trash folder which might appear on any partition or disk 96 | .Trash-* 97 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example 2 | example-es5 3 | example-es5-browserify 4 | src 5 | test -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Krasimir Tsonev 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 geo location component based on Google Maps 2 | 3 | The component uses Google Maps API to fetch the locations. It uses [Awesomplete](http://leaverou.github.io/awesomplete/) as a hard dependency for the dropdown. 4 | 5 | Check out the demo [here](http://krasimir.github.io/react-place). 6 | 7 | ![react-place](http://work.krasimirtsonev.com/react-place/react-place.gif) 8 | 9 | ## Installation 10 | 11 | ``` 12 | npm install react-place 13 | ``` 14 | 15 | ## Dependencies 16 | 17 | * Of course [react](https://www.npmjs.com/package/react) and [react-dom](https://www.npmjs.com/package/react-dom). You need to have these modules installed. 18 | * [Awesomplete](http://leaverou.github.io/awesomplete/) - installed automatically while running `npm install react-place`. It comes with the component so you don't need to have it loaded on the page. 19 | * Google Maps API - you have to add `` to your page to have the component working. 20 | 21 | ## Usage (ES6) 22 | 23 | ```js 24 | import React from 'react'; 25 | import ReactDOM from 'react-dom'; 26 | import Location from 'react-place'; 27 | 28 | var location; 29 | var container = document.querySelector('...'); 30 | 31 | var onLocationSet = (data) => { 32 | // data.description 33 | // data.coords.lat 34 | // data.coords.lng 35 | }; 36 | 37 | location = ReactDOM.render( 38 | , 48 | container 49 | ); 50 | ``` 51 | 52 | ## Usage ES5 (with bundling) 53 | 54 | ```js 55 | var React = require('react'); 56 | var ReactDOM = require('react-dom'); 57 | var Location = require('react-place'); 58 | var createLocation = React.createFactory(Location); 59 | 60 | function onLocationSet (data) { 61 | // data.description 62 | // data.coords.lat 63 | // data.coords.lng 64 | } 65 | 66 | var container = document.querySelector('#container'); 67 | var LocationComp = createLocation({ 68 | country: country.value, 69 | noMatching: 'Sorry, I can not find {{value}}.', 70 | onLocationSet: onLocationSet, 71 | inputProps={{ 72 | style: {color: '#0099FF'}, 73 | className:'location', 74 | placeholder: 'Where are your?' 75 | }} 76 | }); 77 | 78 | var location = ReactDOM.render(LocationComp, container); 79 | ``` 80 | 81 | If you need to update the country dynamically use the following API: 82 | 83 | ```js 84 | location.updateCountry('FR'); 85 | ``` 86 | 87 | ## Usage ES5 (no bundling) 88 | 89 | Download [react-place.min.js](https://github.com/krasimir/react-place/blob/master/build/react-place.min.js) file and add it to your page. 90 | 91 | ```js 92 | 93 | 94 | 95 | 96 | 122 | ``` 123 | 124 | ## Testing 125 | 126 | ``` 127 | npm run test 128 | ``` 129 | 130 | ## Powered by Google API 131 | 132 | ![powered by google](./assets/powered_by_google.png) 133 | -------------------------------------------------------------------------------- /assets/powered_by_google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/krasimir/react-place/96ca92cbe1b15023d72441942afceba8e982fe38/assets/powered_by_google.png -------------------------------------------------------------------------------- /build/react-place.min.js: -------------------------------------------------------------------------------- 1 | !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var t;t="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,t.ReactPlace=e()}}(function(){return function e(t,n,r){function i(u,a){if(!n[u]){if(!t[u]){var s="function"==typeof require&&require;if(!a&&s)return s(u,!0);if(o)return o(u,!0);var c=new Error("Cannot find module '"+u+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[u]={exports:{}};t[u][0].call(l.exports,function(e){var n=t[u][1][e];return i(n?n:e)},l,l.exports,e,t,n,r)}return n[u].exports}for(var o="function"==typeof require&&require,u=0;u=0;n--)t=e[n].call(this,t);return t}},T=function(e){function t(){return i(this,t),o(this,(t.__proto__||Object.getPrototypeOf(t)).apply(this,arguments))}return u(t,e),s(t,[{key:"render",value:function(){return l["default"].createElement("input",a({type:"text"},this.props.inputProps))}},{key:"componentWillMount",value:function(){this._googlePredictions=[],this._country=this.props.country||E,this._noMatching=this.props.noMatching||_}},{key:"componentDidMount",value:function(){var e,t={minChars:1,keepListItems:!1,sort:function(){return 0},item:function(e,t){return v["default"].$.create("li",{innerHTML:e.replace(RegExp(v["default"].$.regExpEscape(t.trim()),"gi"),"$&"),"aria-selected":"false"})}};e=p["default"].findDOMNode(this),this._autocomplete=new v["default"](e,t),e.addEventListener("awesomplete-selectcomplete",this._handleAutocompleteSelect.bind(this)),e.addEventListener("keyup",this._handleInputChange.bind(this))}},{key:"updateCountry",value:function(e){this._country=e}},{key:"_handleInputChange",value:function(e){var t=this,n=this._getInputValue(),r=O(this._autocomplete.evaluate.bind(this._autocomplete),function(e){return t._autocomplete.list=e},function(e){return e.map(function(e){return e.description})},function(e){return t._googlePredictions=e}),i=O(r,function(e){return[{description:e}]},function(e){return t._noMatching.replace("{{value}}",e)}),o=[38,40,13,27],u=o.indexOf(e.keyCode)>=0;u||this._getPredictions(n).then(r,i)}},{key:"_handleAutocompleteSelect",value:function(){var e=this,t=this._getInputValue(),n=function(e){var n=e.filter(function(e){return e.description===t});return n.length>0?n[0]:!1},r=function(e){return e&&e.place_id?e.place_id:!1},i=O(r,n),o=function(n){e.props.onLocationSet&&e.props.onLocationSet({description:t,coords:{lat:n.lat(),lng:n.lng()}})};this._getCoordinates(i(this._googlePredictions)).then(o)}},{key:"_getInputValue",value:function(){return p["default"].findDOMNode(this).value}},{key:"_getPredictions",value:function(e){var t=this,n=(this.props.google||w["default"]).createAutocompleteService(),r=!!e;return r?new m["default"](function(r,i){n.getPlacePredictions({input:e,componentRestrictions:{country:t._country},types:["(regions)"]},function(t){null!==t?r(t):i(e)})}):new m["default"](function(e,t){})}},{key:"_getCoordinates",value:function(e){var t=(this.props.google||w["default"]).createGeocoder();return new m["default"](function(n,r){t.geocode({placeId:e},function(e,t){"OK"===t&&e&&e.length>0?n(e[0].geometry.location):r(!1)})})}}]),t}(l["default"].Component);n["default"]=T,T.defaultProps={className:"",style:{}},T.propTypes={onLocationSet:h["default"].func,inputProps:h["default"].object,country:h["default"].string,noMatching:h["default"].string,google:h["default"].object},t.exports=n["default"]},{"./vendor/google":2,awesomplete:3,"promise-polyfill":8,"prop-types":12}],2:[function(e,t,n){(function(e){"use strict";function r(){return new e.google.maps.places.AutocompleteService}function i(){return new e.google.maps.Geocoder}Object.defineProperty(n,"__esModule",{value:!0}),n["default"]={createAutocompleteService:r,createGeocoder:i},t.exports=n["default"]}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],3:[function(e,t,n){!function(){function e(e){var t=Array.isArray(e)?{label:e[0],value:e[1]}:"object"==typeof e&&"label"in e&&"value"in e?e:{label:e,value:e};this.label=t.label||t.value,this.value=t.value}function n(e,t,n){for(var r in t){var i=t[r],o=e.input.getAttribute("data-"+r.toLowerCase());"number"==typeof i?e[r]=parseInt(o):i===!1?e[r]=null!==o:i instanceof Function?e[r]=null:e[r]=o,e[r]||0===e[r]||(e[r]=r in n?n[r]:i)}}function r(e,t){return"string"==typeof e?(t||document).querySelector(e):e||null}function i(e,t){return a.call((t||document).querySelectorAll(e))}function o(){i("input.awesomplete").forEach(function(e){new u(e)})}var u=function(e,t){var i=this;this.input=r(e),this.input.setAttribute("autocomplete","off"),this.input.setAttribute("aria-autocomplete","list"),t=t||{},n(this,{minChars:2,maxItems:10,autoFirst:!1,data:u.DATA,filter:u.FILTER_CONTAINS,sort:u.SORT_BYLENGTH,item:u.ITEM,replace:u.REPLACE},t),this.index=-1,this.container=r.create("div",{className:"awesomplete",around:e}),this.ul=r.create("ul",{hidden:"hidden",inside:this.container}),this.status=r.create("span",{className:"visually-hidden",role:"status","aria-live":"assertive","aria-relevant":"additions",inside:this.container}),r.bind(this.input,{input:this.evaluate.bind(this),blur:this.close.bind(this),keydown:function(e){var t=e.keyCode;i.opened&&(13===t&&i.selected?(e.preventDefault(),i.select()):27===t?i.close():(38===t||40===t)&&(e.preventDefault(),i[38===t?"previous":"next"]()))}}),r.bind(this.input.form,{submit:this.close.bind(this)}),r.bind(this.ul,{mousedown:function(e){var t=e.target;if(t!==this){for(;t&&!/li/i.test(t.nodeName);)t=t.parentNode;t&&0===e.button&&(e.preventDefault(),i.select(t,e.target))}}}),this.input.hasAttribute("list")?(this.list="#"+this.input.getAttribute("list"),this.input.removeAttribute("list")):this.list=this.input.getAttribute("data-list")||t.list||[],u.all.push(this)};u.prototype={set list(e){if(Array.isArray(e))this._list=e;else if("string"==typeof e&&e.indexOf(",")>-1)this._list=e.split(/\s*,\s*/);else if(e=r(e),e&&e.children){var t=[];a.apply(e.children).forEach(function(e){if(!e.disabled){var n=e.textContent.trim(),r=e.value||n,i=e.label||n;""!==r&&t.push({label:i,value:r})}}),this._list=t}document.activeElement===this.input&&this.evaluate()},get selected(){return this.index>-1},get opened(){return!this.ul.hasAttribute("hidden")},close:function(){this.ul.setAttribute("hidden",""),this.index=-1,r.fire(this.input,"awesomplete-close")},open:function(){this.ul.removeAttribute("hidden"),this.autoFirst&&-1===this.index&&this["goto"](0),r.fire(this.input,"awesomplete-open")},next:function(){var e=this.ul.children.length;this["goto"](this.index-1&&t.length>0&&(t[e].setAttribute("aria-selected","true"),this.status.textContent=t[e].textContent,r.fire(this.input,"awesomplete-highlight",{text:this.suggestions[this.index]}))},select:function(e,t){if(e?this.index=r.siblingIndex(e):e=this.ul.children[this.index],e){var n=this.suggestions[this.index],i=r.fire(this.input,"awesomplete-select",{text:n,origin:t||e});i&&(this.replace(n),this.close(),r.fire(this.input,"awesomplete-selectcomplete",{text:n}))}},evaluate:function(){var t=this,n=this.input.value;n.length>=this.minChars&&this._list.length>0?(this.index=-1,this.ul.innerHTML="",this.suggestions=this._list.map(function(r){return new e(t.data(r,n))}).filter(function(e){return t.filter(e,n)}).sort(this.sort).slice(0,this.maxItems),this.suggestions.forEach(function(e){t.ul.appendChild(t.item(e,n))}),0===this.ul.children.length?this.close():this.open()):this.close()}},u.all=[],u.FILTER_CONTAINS=function(e,t){return RegExp(r.regExpEscape(t.trim()),"i").test(e)},u.FILTER_STARTSWITH=function(e,t){return RegExp("^"+r.regExpEscape(t.trim()),"i").test(e)},u.SORT_BYLENGTH=function(e,t){return e.length!==t.length?e.length-t.length:t>e?-1:1},u.ITEM=function(e,t){var n=""===t?e:e.replace(RegExp(r.regExpEscape(t.trim()),"gi"),"$&");return r.create("li",{innerHTML:n,"aria-selected":"false"})},u.REPLACE=function(e){this.input.value=e.value},u.DATA=function(e){return e},Object.defineProperty(e.prototype=Object.create(String.prototype),"length",{get:function(){return this.label.length}}),e.prototype.toString=e.prototype.valueOf=function(){return""+this.label};var a=Array.prototype.slice;return r.create=function(e,t){var n=document.createElement(e);for(var i in t){var o=t[i];if("inside"===i)r(o).appendChild(n);else if("around"===i){var u=r(o);u.parentNode.insertBefore(n,u),n.appendChild(u)}else i in n?n[i]=o:n.setAttribute(i,o)}return n},r.bind=function(e,t){if(e)for(var n in t){var r=t[n];n.split(/\s+/).forEach(function(t){e.addEventListener(t,r)})}},r.fire=function(e,t,n){var r=document.createEvent("HTMLEvents");r.initEvent(t,!0,!0);for(var i in n)r[i]=n[i];return e.dispatchEvent(r)},r.regExpEscape=function(e){return e.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&")},r.siblingIndex=function(e){for(var t=0;e=e.previousElementSibling;t++);return t},"undefined"!=typeof Document&&("loading"!==document.readyState?o():document.addEventListener("DOMContentLoaded",o)),u.$=r,u.$$=i,"undefined"!=typeof self&&(self.Awesomplete=u),"object"==typeof t&&t.exports&&(t.exports=u),u}()},{}],4:[function(e,t,n){"use strict";function r(e){return function(){return e}}var i=function(){};i.thatReturns=r,i.thatReturnsFalse=r(!1),i.thatReturnsTrue=r(!0),i.thatReturnsNull=r(null),i.thatReturnsThis=function(){return this},i.thatReturnsArgument=function(e){return e},t.exports=i},{}],5:[function(e,t,n){(function(e){"use strict";function n(e,t,n,i,o,u,a,s){if(r(t),!e){var c;if(void 0===t)c=new Error("Minified exception occurred; use the non-minified dev environment for the full error message and additional helpful warnings.");else{var l=[n,i,o,u,a,s],f=0;c=new Error(t.replace(/%s/g,function(){return l[f++]})),c.name="Invariant Violation"}throw c.framesToPop=1,c}}var r=function(e){};"production"!==e.env.NODE_ENV&&(r=function(e){if(void 0===e)throw new Error("invariant requires an error message argument")}),t.exports=n}).call(this,e("_process"))},{_process:7}],6:[function(e,t,n){(function(n){"use strict";var r=e("./emptyFunction"),i=r;if("production"!==n.env.NODE_ENV){var o=function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),r=1;t>r;r++)n[r-1]=arguments[r];var i=0,o="Warning: "+e.replace(/%s/g,function(){return n[i++]});"undefined"!=typeof console&&console.error(o);try{throw new Error(o)}catch(u){}};i=function(e,t){if(void 0===t)throw new Error("`warning(condition, format, ...args)` requires a warning message argument");if(0!==t.indexOf("Failed Composite propType: ")&&!e){for(var n=arguments.length,r=Array(n>2?n-2:0),i=2;n>i;i++)r[i-2]=arguments[i];o.apply(void 0,[t].concat(r))}}}t.exports=i}).call(this,e("_process"))},{"./emptyFunction":4,_process:7}],7:[function(e,t,n){function r(){throw new Error("setTimeout has not been defined")}function i(){throw new Error("clearTimeout has not been defined")}function o(e){if(f===setTimeout)return setTimeout(e,0);if((f===r||!f)&&setTimeout)return f=setTimeout,setTimeout(e,0);try{return f(e,0)}catch(t){try{return f.call(null,e,0)}catch(t){return f.call(this,e,0)}}}function u(e){if(p===clearTimeout)return clearTimeout(e);if((p===i||!p)&&clearTimeout)return p=clearTimeout,clearTimeout(e);try{return p(e)}catch(t){try{return p.call(null,e)}catch(t){return p.call(this,e)}}}function a(){v&&h&&(v=!1,h.length?y=h.concat(y):g=-1,y.length&&s())}function s(){if(!v){var e=o(a);v=!0;for(var t=y.length;t;){for(h=y,y=[];++g1)for(var n=1;ne;e++)i.call(this,this._deferreds[e]);this._deferreds=null}function s(e,t,n,r){this.onFulfilled="function"==typeof e?e:null,this.onRejected="function"==typeof t?t:null,this.resolve=n,this.reject=r}function c(e,t,n){var r=!1;try{e(function(e){r||(r=!0,t(e))},function(e){r||(r=!0,n(e))})}catch(i){if(r)return;r=!0,n(i)}}var l="function"==typeof setImmediate&&setImmediate||function(e){setTimeout(e,1)},f=Array.isArray||function(e){return"[object Array]"===Object.prototype.toString.call(e)};r.prototype["catch"]=function(e){return this.then(null,e)},r.prototype.then=function(e,t){var n=this;return new r(function(r,o){i.call(n,new s(e,t,r,o))})},r.all=function(){var e=Array.prototype.slice.call(1===arguments.length&&f(arguments[0])?arguments[0]:arguments);return new r(function(t,n){function r(o,u){try{if(u&&("object"==typeof u||"function"==typeof u)){var a=u.then;if("function"==typeof a)return void a.call(u,function(e){r(o,e)},n)}e[o]=u,0===--i&&t(e)}catch(s){n(s)}}if(0===e.length)return t([]);for(var i=e.length,o=0;or;r++)e[r].then(t,n)})},r._setImmediateFn=function(e){l=e},"undefined"!=typeof t&&t.exports?t.exports=r:e.Promise||(e.Promise=r)}(this)},{}],9:[function(e,t,n){(function(n){"use strict";function r(e,t,r,s,c){if("production"!==n.env.NODE_ENV)for(var l in e)if(e.hasOwnProperty(l)){var f;try{i("function"==typeof e[l],"%s: %s type `%s` is invalid; it must be a function, usually from the `prop-types` package, but received `%s`.",s||"React class",r,l,typeof e[l]),f=e[l](t,l,s,r,null,u)}catch(p){f=p}if(o(!f||f instanceof Error,"%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",s||"React class",r,l,typeof f),f instanceof Error&&!(f.message in a)){a[f.message]=!0;var d=c?c():"";o(!1,"Failed %s type: %s%s",r,f.message,null!=d?d:"")}}}if("production"!==n.env.NODE_ENV)var i=e("fbjs/lib/invariant"),o=e("fbjs/lib/warning"),u=e("./lib/ReactPropTypesSecret"),a={};t.exports=r}).call(this,e("_process"))},{"./lib/ReactPropTypesSecret":13,_process:7,"fbjs/lib/invariant":5,"fbjs/lib/warning":6}],10:[function(e,t,n){"use strict";var r=e("fbjs/lib/emptyFunction"),i=e("fbjs/lib/invariant"),o=e("./lib/ReactPropTypesSecret");t.exports=function(){function e(e,t,n,r,u,a){a!==o&&i(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t};return n.checkPropTypes=r,n.PropTypes=n,n}},{"./lib/ReactPropTypesSecret":13,"fbjs/lib/emptyFunction":4,"fbjs/lib/invariant":5}],11:[function(e,t,n){(function(n){"use strict";var r=e("fbjs/lib/emptyFunction"),i=e("fbjs/lib/invariant"),o=e("fbjs/lib/warning"),u=e("object-assign"),a=e("./lib/ReactPropTypesSecret"),s=e("./checkPropTypes");t.exports=function(e,t){function c(e){var t=e&&(R&&e[R]||e[k]);return"function"==typeof t?t:void 0}function l(e,t){return e===t?0!==e||1/e===1/t:e!==e&&t!==t}function f(e){this.message=e,this.stack=""}function p(e){function r(r,c,l,p,d,h,y){if(p=p||N,h=h||l,y!==a)if(t)i(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types");else if("production"!==n.env.NODE_ENV&&"undefined"!=typeof console){var v=p+":"+l;!u[v]&&3>s&&(o(!1,"You are manually calling a React.PropTypes validation function for the `%s` prop on `%s`. This is deprecated and will throw in the standalone `prop-types` package. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details.",h,p),u[v]=!0,s++)}return null==c[l]?r?new f(null===c[l]?"The "+d+" `"+h+"` is marked as required "+("in `"+p+"`, but its value is `null`."):"The "+d+" `"+h+"` is marked as required in "+("`"+p+"`, but its value is `undefined`.")):null:e(c,l,p,d,h)}if("production"!==n.env.NODE_ENV)var u={},s=0;var c=r.bind(null,!1);return c.isRequired=r.bind(null,!0),c}function d(e){function t(t,n,r,i,o,u){var a=t[n],s=x(a);if(s!==e){var c=P(a);return new f("Invalid "+i+" `"+o+"` of type "+("`"+c+"` supplied to `"+r+"`, expected ")+("`"+e+"`."))}return null}return p(t)}function h(){return p(r.thatReturnsNull)}function y(e){function t(t,n,r,i,o){if("function"!=typeof e)return new f("Property `"+o+"` of component `"+r+"` has invalid PropType notation inside arrayOf.");var u=t[n];if(!Array.isArray(u)){var s=x(u);return new f("Invalid "+i+" `"+o+"` of type "+("`"+s+"` supplied to `"+r+"`, expected an array."))}for(var c=0;cn;n++)t["_"+String.fromCharCode(n)]=n;var r=Object.getOwnPropertyNames(t).map(function(e){return t[e]});if("0123456789"!==r.join(""))return!1;var i={};return"abcdefghijklmnopqrst".split("").forEach(function(e){i[e]=e}),"abcdefghijklmnopqrst"!==Object.keys(Object.assign({},i)).join("")?!1:!0}catch(o){return!1}}var o=Object.getOwnPropertySymbols,u=Object.prototype.hasOwnProperty,a=Object.prototype.propertyIsEnumerable;t.exports=i()?Object.assign:function(e,t){for(var n,i,s=r(e),c=1;c 2 | 3 | 4 | 5 | React Location 6 | 154 | 155 | 156 |
157 | 408 |
409 |

410 |     
411 |   
412 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /example-es5-browserify/src/index.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOM = require('react-dom'); 3 | var Location = require('../../lib/Location'); 4 | var createLocation = React.createFactory(Location); 5 | 6 | function onLocationSet(value) { 7 | var pre = document.querySelector('pre'); 8 | 9 | pre.innerHTML = JSON.stringify(value, null, 2); 10 | } 11 | 12 | window.onload = function () { 13 | var country = document.querySelector('#country-dropdown'); 14 | var container = document.querySelector('#container'); 15 | var LocationComp = createLocation({ 16 | className: 'location', 17 | placeholder: 'Where are you?', 18 | country: country.value, 19 | noMatching: 'Sorry, I can not find {{value}}.', 20 | onLocationSet: onLocationSet 21 | }); 22 | var location = ReactDOM.render(LocationComp, container); 23 | 24 | country.addEventListener('change', function () { 25 | location.updateCountry(country.value); 26 | }); 27 | 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /example-es5/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Location 6 | 154 | 155 | 156 |
157 | 408 |
409 |

410 |     
411 |   
412 | 413 | 414 | 415 | 416 | 445 | 446 | -------------------------------------------------------------------------------- /example-es6/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Location 6 | 154 | 155 | 156 |
157 | 408 |
409 |

410 |     
411 |   
412 | 413 | 414 | 415 | -------------------------------------------------------------------------------- /example-es6/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Location from '../../src/Location.jsx'; 4 | 5 | function onLocationSet(value) { 6 | var pre = document.querySelector('pre'); 7 | 8 | pre.innerHTML = JSON.stringify(value, null, 2); 9 | } 10 | 11 | window.onload = () => { 12 | var country = document.querySelector('#country-dropdown'); 13 | var container = document.querySelector('#container'); 14 | var location; 15 | 16 | location = ReactDOM.render( 17 | , 27 | container 28 | ); 29 | 30 | country.addEventListener('change', () => { 31 | location.updateCountry(country.value); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | var webpackConfig = require('./webpack.config'); 2 | webpackConfig.devtool = 'inline-source-map'; 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | browsers: [ 'PhantomJS' ], 7 | singleRun: true, 8 | frameworks: [ 'mocha', 'chai', 'sinon', 'sinon-chai' ], 9 | files: [ 10 | 'tests.webpack.js' 11 | ], 12 | plugins: [ 13 | 'karma-phantomjs-launcher', 14 | 'karma-chai', 15 | 'karma-mocha', 16 | 'karma-sourcemap-loader', 17 | 'karma-webpack', 18 | 'karma-mocha-reporter', 19 | 'karma-sinon', 20 | 'karma-sinon-chai' 21 | ], 22 | preprocessors: { 23 | 'tests.webpack.js': [ 'webpack', 'sourcemap' ] 24 | }, 25 | reporters: [ 'mocha' ], 26 | webpack: webpackConfig, 27 | webpackServer: { 28 | noInfo: true 29 | }, 30 | autoWatch: true 31 | }); 32 | }; -------------------------------------------------------------------------------- /lib/Location.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 10 | 11 | var _react = require('react'); 12 | 13 | var _react2 = _interopRequireDefault(_react); 14 | 15 | var _reactDom = require('react-dom'); 16 | 17 | var _reactDom2 = _interopRequireDefault(_reactDom); 18 | 19 | var _propTypes = require('prop-types'); 20 | 21 | var _propTypes2 = _interopRequireDefault(_propTypes); 22 | 23 | var _awesomplete = require('awesomplete'); 24 | 25 | var _awesomplete2 = _interopRequireDefault(_awesomplete); 26 | 27 | var _promisePolyfill = require('promise-polyfill'); 28 | 29 | var _promisePolyfill2 = _interopRequireDefault(_promisePolyfill); 30 | 31 | var _google = require('./vendor/google'); 32 | 33 | var _google2 = _interopRequireDefault(_google); 34 | 35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 36 | 37 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 38 | 39 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 40 | 41 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 42 | 43 | var NO_MATCHING = 'Unrecognised {{value}}, please check and re-enter.'; 44 | var DEFAULT_COUNTRY = 'US'; 45 | 46 | var compose = function compose() { 47 | var fns = arguments; 48 | 49 | return function (result) { 50 | for (var i = fns.length - 1; i >= 0; i--) { 51 | result = fns[i].call(this, result); 52 | } 53 | return result; 54 | }; 55 | }; 56 | 57 | var Location = function (_React$Component) { 58 | _inherits(Location, _React$Component); 59 | 60 | function Location() { 61 | _classCallCheck(this, Location); 62 | 63 | return _possibleConstructorReturn(this, (Location.__proto__ || Object.getPrototypeOf(Location)).apply(this, arguments)); 64 | } 65 | 66 | _createClass(Location, [{ 67 | key: 'render', 68 | value: function render() { 69 | return _react2.default.createElement('input', _extends({ type: 'text' }, this.props.inputProps)); 70 | } 71 | }, { 72 | key: 'componentWillMount', 73 | value: function componentWillMount() { 74 | this._googlePredictions = []; 75 | this._country = this.props.country || DEFAULT_COUNTRY; 76 | this._noMatching = this.props.noMatching || NO_MATCHING; 77 | } 78 | }, { 79 | key: 'componentDidMount', 80 | value: function componentDidMount() { 81 | var input; 82 | var config = { 83 | minChars: 1, 84 | keepListItems: false, 85 | sort: function sort() { 86 | return 0; 87 | }, 88 | item: function item(text, input) { 89 | return _awesomplete2.default.$.create('li', { 90 | innerHTML: text.replace(RegExp(_awesomplete2.default.$.regExpEscape(input.trim()), 'gi'), '$&'), 91 | 'aria-selected': 'false' 92 | }); 93 | } 94 | }; 95 | 96 | input = _reactDom2.default.findDOMNode(this); 97 | this._autocomplete = new _awesomplete2.default(input, config); 98 | 99 | input.addEventListener('awesomplete-selectcomplete', this._handleAutocompleteSelect.bind(this)); 100 | input.addEventListener('keyup', this._handleInputChange.bind(this)); 101 | } 102 | }, { 103 | key: 'updateCountry', 104 | value: function updateCountry(country) { 105 | this._country = country; 106 | } 107 | }, { 108 | key: '_handleInputChange', 109 | value: function _handleInputChange(event) { 110 | var _this2 = this; 111 | 112 | var value = this._getInputValue(); 113 | var updateAutocomplete = compose(this._autocomplete.evaluate.bind(this._autocomplete), function (list) { 114 | return _this2._autocomplete.list = list; 115 | }, function (list) { 116 | return list.map(function (item) { 117 | return item.description; 118 | }); 119 | }, function (results) { 120 | return _this2._googlePredictions = results; 121 | }); 122 | var fail = compose(updateAutocomplete, function (text) { 123 | return [{ description: text }]; 124 | }, function (text) { 125 | return _this2._noMatching.replace('{{value}}', text); 126 | }); 127 | var navKeys = [38, 40, 13, 27]; 128 | var isItNavKey = navKeys.indexOf(event.keyCode) >= 0; 129 | 130 | if (!isItNavKey) { 131 | this._getPredictions(value).then(updateAutocomplete, fail); 132 | } 133 | } 134 | }, { 135 | key: '_handleAutocompleteSelect', 136 | value: function _handleAutocompleteSelect() { 137 | var _this3 = this; 138 | 139 | var value = this._getInputValue(); 140 | var find = function find(list) { 141 | var l = list.filter(function (item) { 142 | return item.description === value; 143 | }); 144 | 145 | return l.length > 0 ? l[0] : false; 146 | }; 147 | var validate = function validate(item) { 148 | return item && item.place_id ? item.place_id : false; 149 | }; 150 | var getPlaceId = compose(validate, find); 151 | var success = function success(location) { 152 | _this3.props.onLocationSet && _this3.props.onLocationSet({ 153 | description: value, 154 | coords: { 155 | lat: location.lat(), 156 | lng: location.lng() 157 | } 158 | }); 159 | }; 160 | 161 | this._getCoordinates(getPlaceId(this._googlePredictions)).then(success); 162 | } 163 | }, { 164 | key: '_getInputValue', 165 | value: function _getInputValue() { 166 | return _reactDom2.default.findDOMNode(this).value; 167 | } 168 | }, { 169 | key: '_getPredictions', 170 | value: function _getPredictions(text) { 171 | var _this4 = this; 172 | 173 | var service = (this.props.google || _google2.default).createAutocompleteService(); 174 | var isThereAnyText = !!text; 175 | 176 | if (isThereAnyText) { 177 | return new _promisePolyfill2.default(function (resolve, reject) { 178 | service.getPlacePredictions({ 179 | input: text, 180 | componentRestrictions: { country: _this4._country }, 181 | types: ['(regions)'] 182 | }, function (result) { 183 | if (result !== null) { 184 | resolve(result); 185 | } else { 186 | reject(text); 187 | } 188 | }); 189 | }); 190 | } 191 | return new _promisePolyfill2.default(function (resolve, reject) {}); 192 | } 193 | }, { 194 | key: '_getCoordinates', 195 | value: function _getCoordinates(placeId) { 196 | var geocoder = (this.props.google || _google2.default).createGeocoder(); 197 | 198 | return new _promisePolyfill2.default(function (resolve, reject) { 199 | geocoder.geocode({ placeId: placeId }, function (results, status) { 200 | if (status === 'OK' && results && results.length > 0) { 201 | resolve(results[0].geometry.location); 202 | } else { 203 | reject(false); 204 | } 205 | }); 206 | }); 207 | } 208 | }]); 209 | 210 | return Location; 211 | }(_react2.default.Component); 212 | 213 | exports.default = Location; 214 | ; 215 | 216 | Location.defaultProps = { 217 | className: '', 218 | style: {} 219 | }; 220 | 221 | Location.propTypes = { 222 | onLocationSet: _propTypes2.default.func, 223 | inputProps: _propTypes2.default.object, 224 | country: _propTypes2.default.string, 225 | noMatching: _propTypes2.default.string, 226 | google: _propTypes2.default.object 227 | }; 228 | module.exports = exports['default']; 229 | //# sourceMappingURL=Location.js.map -------------------------------------------------------------------------------- /lib/Location.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../src/Location.jsx"],"names":["NO_MATCHING","DEFAULT_COUNTRY","compose","fns","arguments","result","i","length","call","Location","props","inputProps","_googlePredictions","_country","country","_noMatching","noMatching","input","config","minChars","keepListItems","sort","item","text","$","create","innerHTML","replace","RegExp","regExpEscape","trim","findDOMNode","_autocomplete","addEventListener","_handleAutocompleteSelect","bind","_handleInputChange","event","value","_getInputValue","updateAutocomplete","evaluate","list","map","description","results","fail","navKeys","isItNavKey","indexOf","keyCode","_getPredictions","then","find","l","filter","validate","place_id","getPlaceId","success","location","onLocationSet","coords","lat","lng","_getCoordinates","service","google","createAutocompleteService","isThereAnyText","resolve","reject","getPlacePredictions","componentRestrictions","types","placeId","geocoder","createGeocoder","geocode","status","geometry","Component","defaultProps","className","style","propTypes","func","object","string"],"mappings":"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","file":"Location.js","sourcesContent":["'use strict';\n\nimport React from 'react';\nimport ReactDom from 'react-dom';\nimport PropTypes from 'prop-types';\nimport Awesomplete from 'awesomplete';\nimport Promise from 'promise-polyfill';\nimport google from './vendor/google';\n\nconst NO_MATCHING = 'Unrecognised {{value}}, please check and re-enter.';\nconst DEFAULT_COUNTRY = 'US';\n\nvar compose = function () {\n var fns = arguments;\n\n return function (result) {\n for (let i = fns.length - 1; i >= 0; i--) {\n result = fns[i].call(this, result);\n }\n return result;\n };\n};\n\nexport default class Location extends React.Component {\n\n render() {\n return ;\n }\n\n componentWillMount() {\n this._googlePredictions = [];\n this._country = this.props.country || DEFAULT_COUNTRY;\n this._noMatching = this.props.noMatching || NO_MATCHING;\n }\n\n componentDidMount() {\n var input;\n var config = {\n minChars: 1,\n keepListItems: false,\n sort: function () { return 0; },\n item: function (text, input) {\n return Awesomplete.$.create('li', {\n innerHTML: text.replace(\n RegExp(Awesomplete.$.regExpEscape(input.trim()), 'gi'),\n '$&'\n ),\n 'aria-selected': 'false'\n });\n }\n };\n\n input = ReactDom.findDOMNode(this);\n this._autocomplete = new Awesomplete(input, config);\n\n input.addEventListener(\n 'awesomplete-selectcomplete',\n this._handleAutocompleteSelect.bind(this)\n );\n input.addEventListener(\n 'keyup',\n this._handleInputChange.bind(this)\n );\n }\n\n updateCountry(country) {\n this._country = country;\n }\n\n _handleInputChange(event) {\n var value = this._getInputValue();\n var updateAutocomplete = compose(\n this._autocomplete.evaluate.bind(this._autocomplete),\n (list) => this._autocomplete.list = list,\n (list) => list.map((item) => item.description),\n (results) => this._googlePredictions = results\n );\n var fail = compose(\n updateAutocomplete,\n (text) => [{ description: text }],\n (text) => {\n return this._noMatching.replace('{{value}}', text);\n }\n );\n var navKeys = [38, 40, 13, 27];\n var isItNavKey = navKeys.indexOf(event.keyCode) >= 0;\n\n if (!isItNavKey) {\n this._getPredictions(value).then(updateAutocomplete, fail);\n }\n }\n\n _handleAutocompleteSelect() {\n var value = this._getInputValue();\n var find = (list) => {\n let l = list.filter(item => item.description === value);\n\n return l.length > 0 ? l[0] : false;\n };\n var validate = item => item && item.place_id ? item.place_id : false;\n var getPlaceId = compose(validate, find);\n var success = (location) => {\n this.props.onLocationSet && this.props.onLocationSet({\n description: value,\n coords: {\n lat: location.lat(),\n lng: location.lng()\n }\n });\n };\n\n this._getCoordinates(getPlaceId(this._googlePredictions)).then(success);\n }\n\n _getInputValue() {\n return ReactDom.findDOMNode(this).value;\n }\n\n _getPredictions(text) {\n var service = (this.props.google || google).createAutocompleteService();\n var isThereAnyText = !!text;\n\n if (isThereAnyText) {\n return new Promise((resolve, reject) => {\n service.getPlacePredictions({\n input: text,\n componentRestrictions: { country: this._country },\n types: ['(regions)']\n }, (result) => {\n if (result !== null) {\n resolve(result);\n } else {\n reject(text);\n }\n });\n });\n }\n return new Promise((resolve, reject) => {});\n }\n\n _getCoordinates(placeId) {\n var geocoder = (this.props.google || google).createGeocoder();\n\n return new Promise((resolve, reject) => {\n geocoder.geocode({ placeId: placeId }, (results, status) => {\n if (status === 'OK' && results && results.length > 0) {\n resolve(results[0].geometry.location);\n } else {\n reject(false);\n }\n });\n });\n }\n\n};\n\nLocation.defaultProps = {\n className: '',\n style: {}\n};\n\nLocation.propTypes = {\n onLocationSet: PropTypes.func,\n inputProps: PropTypes.object,\n country: PropTypes.string,\n noMatching: PropTypes.string,\n google: PropTypes.object\n};\n"]} -------------------------------------------------------------------------------- /lib/vendor/google.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | function createAutocompleteService() { 7 | return new global.google.maps.places.AutocompleteService(); 8 | }; 9 | 10 | function createGeocoder() { 11 | return new global.google.maps.Geocoder(); 12 | }; 13 | 14 | exports.default = { 15 | createAutocompleteService: createAutocompleteService, 16 | createGeocoder: createGeocoder 17 | }; 18 | module.exports = exports['default']; 19 | //# sourceMappingURL=google.js.map -------------------------------------------------------------------------------- /lib/vendor/google.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../src/vendor/google.js"],"names":["createAutocompleteService","global","google","maps","places","AutocompleteService","createGeocoder","Geocoder"],"mappings":";;;;;AAAA,SAASA,yBAAT,GAAqC;AACnC,SAAO,IAAIC,OAAOC,MAAP,CAAcC,IAAd,CAAmBC,MAAnB,CAA0BC,mBAA9B,EAAP;AACD;;AAED,SAASC,cAAT,GAA0B;AACxB,SAAO,IAAIL,OAAOC,MAAP,CAAcC,IAAd,CAAmBI,QAAvB,EAAP;AACD;;kBAEc;AACbP,sDADa;AAEbM;AAFa,C","file":"google.js","sourcesContent":["function createAutocompleteService() {\n return new global.google.maps.places.AutocompleteService();\n};\n\nfunction createGeocoder() {\n return new global.google.maps.Geocoder();\n};\n\nexport default {\n createAutocompleteService,\n createGeocoder\n};\n\n"]} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-place", 3 | "version": "0.6.2", 4 | "description": "React geo location component based on Google Maps", 5 | "main": "lib/Location.js", 6 | "author": { 7 | "name": "Krasimir Tsonev", 8 | "email": "info@krasimirtsonev.com", 9 | "url": "http://krasimirtsonev.com" 10 | }, 11 | "license": "MIT", 12 | "keywords": [ 13 | "react", 14 | "location", 15 | "googlemaps", 16 | "geo", 17 | "react-component" 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:krasimir/react-place.git" 22 | }, 23 | "scripts": { 24 | "dev": "webpack --watch --inline", 25 | "prepublish": "babel ./src --out-dir ./lib --source-maps --presets es2015,react --plugins babel-plugin-add-module-exports && browserify ./lib/Location.js -o ./build/react-place.js --transform browserify-global-shim --standalone ReactPlace && uglifyjs ./build/react-place.js --compress --mangle --output ./build/react-place.min.js --source-map ./build/react-place.min.js.map", 26 | "test": "karma start", 27 | "test:ci": "watch 'npm run test' src/", 28 | "example-es5-browserify": "browserify ./example-es5-browserify/src/index.js -o ./example-es5-browserify/build/app.js" 29 | }, 30 | "dependencies": { 31 | "awesomplete": "1.1.0", 32 | "promise-polyfill": "2.1.0" 33 | }, 34 | "devDependencies": { 35 | "babel": "6.5.2", 36 | "babel-cli": "6.1.18", 37 | "babel-core": "6.6.5", 38 | "babel-eslint": "5.0.0", 39 | "babel-loader": "6.2.4", 40 | "babel-plugin-add-module-exports": "0.1.1", 41 | "babel-preset-es2015": "6.6.0", 42 | "babel-preset-react": "6.5.0", 43 | "browserify": "12.0.1", 44 | "browserify-global-shim": "1.0.0", 45 | "chai": "3.3.0", 46 | "core-js": "1.1.4", 47 | "eslint": "1.4.3", 48 | "eslint-loader": "1.0.0", 49 | "eslint-plugin-react": "3.4.1", 50 | "karma": "0.13.19", 51 | "karma-chai": "0.1.0", 52 | "karma-chai-plugins": "0.6.0", 53 | "karma-chai-sinon": "0.1.5", 54 | "karma-chrome-launcher": "0.2.0", 55 | "karma-mocha": "0.2.0", 56 | "karma-mocha-reporter": "1.1.1", 57 | "karma-phantomjs-launcher": "0.2.1", 58 | "karma-sinon": "1.0.4", 59 | "karma-sinon-chai": "1.1.0", 60 | "karma-sourcemap-loader": "0.3.5", 61 | "karma-webpack": "1.7.0", 62 | "phantomjs": "^2.1.7", 63 | "prop-types": "^15.6.0", 64 | "react": "^15.2.1", 65 | "react-addons-test-utils": "^15.2.1", 66 | "react-dom": "^15.2.1", 67 | "uglify-js": "2.6.1", 68 | "watch": "0.16.0", 69 | "webpack": "1.12.2", 70 | "webpack-dev-server": "1.11.0" 71 | }, 72 | "browserify-global-shim": { 73 | "react": "React", 74 | "react-dom": "ReactDOM" 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Location.jsx: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | import ReactDom from 'react-dom'; 5 | import PropTypes from 'prop-types'; 6 | import Awesomplete from 'awesomplete'; 7 | import Promise from 'promise-polyfill'; 8 | import google from './vendor/google'; 9 | 10 | const NO_MATCHING = 'Unrecognised {{value}}, please check and re-enter.'; 11 | const DEFAULT_COUNTRY = 'US'; 12 | 13 | var compose = function () { 14 | var fns = arguments; 15 | 16 | return function (result) { 17 | for (let i = fns.length - 1; i >= 0; i--) { 18 | result = fns[i].call(this, result); 19 | } 20 | return result; 21 | }; 22 | }; 23 | 24 | export default class Location extends React.Component { 25 | 26 | render() { 27 | return ; 28 | } 29 | 30 | componentWillMount() { 31 | this._googlePredictions = []; 32 | this._country = this.props.country || DEFAULT_COUNTRY; 33 | this._noMatching = this.props.noMatching || NO_MATCHING; 34 | } 35 | 36 | componentDidMount() { 37 | var input; 38 | var config = { 39 | minChars: 1, 40 | keepListItems: false, 41 | sort: function () { return 0; }, 42 | item: function (text, input) { 43 | return Awesomplete.$.create('li', { 44 | innerHTML: text.replace( 45 | RegExp(Awesomplete.$.regExpEscape(input.trim()), 'gi'), 46 | '$&' 47 | ), 48 | 'aria-selected': 'false' 49 | }); 50 | } 51 | }; 52 | 53 | input = ReactDom.findDOMNode(this); 54 | this._autocomplete = new Awesomplete(input, config); 55 | 56 | input.addEventListener( 57 | 'awesomplete-selectcomplete', 58 | this._handleAutocompleteSelect.bind(this) 59 | ); 60 | input.addEventListener( 61 | 'keyup', 62 | this._handleInputChange.bind(this) 63 | ); 64 | } 65 | 66 | updateCountry(country) { 67 | this._country = country; 68 | } 69 | 70 | _handleInputChange(event) { 71 | var value = this._getInputValue(); 72 | var updateAutocomplete = compose( 73 | this._autocomplete.evaluate.bind(this._autocomplete), 74 | (list) => this._autocomplete.list = list, 75 | (list) => list.map((item) => item.description), 76 | (results) => this._googlePredictions = results 77 | ); 78 | var fail = compose( 79 | updateAutocomplete, 80 | (text) => [{ description: text }], 81 | (text) => { 82 | return this._noMatching.replace('{{value}}', text); 83 | } 84 | ); 85 | var navKeys = [38, 40, 13, 27]; 86 | var isItNavKey = navKeys.indexOf(event.keyCode) >= 0; 87 | 88 | if (!isItNavKey) { 89 | this._getPredictions(value).then(updateAutocomplete, fail); 90 | } 91 | } 92 | 93 | _handleAutocompleteSelect() { 94 | var value = this._getInputValue(); 95 | var find = (list) => { 96 | let l = list.filter(item => item.description === value); 97 | 98 | return l.length > 0 ? l[0] : false; 99 | }; 100 | var validate = item => item && item.place_id ? item.place_id : false; 101 | var getPlaceId = compose(validate, find); 102 | var success = (location) => { 103 | this.props.onLocationSet && this.props.onLocationSet({ 104 | description: value, 105 | coords: { 106 | lat: location.lat(), 107 | lng: location.lng() 108 | } 109 | }); 110 | }; 111 | 112 | this._getCoordinates(getPlaceId(this._googlePredictions)).then(success); 113 | } 114 | 115 | _getInputValue() { 116 | return ReactDom.findDOMNode(this).value; 117 | } 118 | 119 | _getPredictions(text) { 120 | var service = (this.props.google || google).createAutocompleteService(); 121 | var isThereAnyText = !!text; 122 | 123 | if (isThereAnyText) { 124 | return new Promise((resolve, reject) => { 125 | service.getPlacePredictions({ 126 | input: text, 127 | componentRestrictions: { country: this._country }, 128 | types: ['(regions)'] 129 | }, (result) => { 130 | if (result !== null) { 131 | resolve(result); 132 | } else { 133 | reject(text); 134 | } 135 | }); 136 | }); 137 | } 138 | return new Promise((resolve, reject) => {}); 139 | } 140 | 141 | _getCoordinates(placeId) { 142 | var geocoder = (this.props.google || google).createGeocoder(); 143 | 144 | return new Promise((resolve, reject) => { 145 | geocoder.geocode({ placeId: placeId }, (results, status) => { 146 | if (status === 'OK' && results && results.length > 0) { 147 | resolve(results[0].geometry.location); 148 | } else { 149 | reject(false); 150 | } 151 | }); 152 | }); 153 | } 154 | 155 | }; 156 | 157 | Location.defaultProps = { 158 | className: '', 159 | style: {} 160 | }; 161 | 162 | Location.propTypes = { 163 | onLocationSet: PropTypes.func, 164 | inputProps: PropTypes.object, 165 | country: PropTypes.string, 166 | noMatching: PropTypes.string, 167 | google: PropTypes.object 168 | }; 169 | -------------------------------------------------------------------------------- /src/vendor/google.js: -------------------------------------------------------------------------------- 1 | function createAutocompleteService() { 2 | return new global.google.maps.places.AutocompleteService(); 3 | }; 4 | 5 | function createGeocoder() { 6 | return new global.google.maps.Geocoder(); 7 | }; 8 | 9 | export default { 10 | createAutocompleteService, 11 | createGeocoder 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/predictions.js: -------------------------------------------------------------------------------- 1 | export default function predictions() { 2 | return [ 3 | { 4 | 'description': 'New York, NY, United States', 5 | 'id': '7eae6a016a9c6f58e2044573fb8f14227b6e1f96', 6 | 'matched_substrings': [ 7 | { 8 | 'length': 2, 9 | 'offset': 0 10 | } 11 | ], 12 | 'place_id': 'ChIJOwg_06VPwokRYv534QaPC8g', 13 | 'reference': '...', 14 | 'terms': [ 15 | { 16 | 'offset': 0, 17 | 'value': 'New York' 18 | }, 19 | { 20 | 'offset': 10, 21 | 'value': 'NY' 22 | }, 23 | { 24 | 'offset': 14, 25 | 'value': 'United States' 26 | } 27 | ], 28 | 'types': [ 29 | 'locality', 30 | 'political', 31 | 'geocode' 32 | ] 33 | }, 34 | { 35 | 'description': 'New York, IA, United States', 36 | 'id': '329cb7144660f29514f351db26cef864634f748a', 37 | 'matched_substrings': [ 38 | { 39 | 'length': 2, 40 | 'offset': 0 41 | } 42 | ], 43 | 'place_id': 'ChIJD_qB3F8X6YcRDraFbXmLUD4', 44 | 'reference': '...', 45 | 'terms': [ 46 | { 47 | 'offset': 0, 48 | 'value': 'New York' 49 | }, 50 | { 51 | 'offset': 10, 52 | 'value': 'IA' 53 | }, 54 | { 55 | 'offset': 14, 56 | 'value': 'United States' 57 | } 58 | ], 59 | 'types': [ 60 | 'locality', 61 | 'political', 62 | 'geocode' 63 | ] 64 | }, 65 | { 66 | 'description': 'New York, United States', 67 | 'id': '349c7fc49816ce54bb586cf8fa2cd79b255746b3', 68 | 'matched_substrings': [ 69 | { 70 | 'length': 2, 71 | 'offset': 0 72 | } 73 | ], 74 | 'place_id': 'ChIJqaUj8fBLzEwRZ5UY3sHGz90', 75 | 'reference': '...', 76 | 'terms': [ 77 | { 78 | 'offset': 0, 79 | 'value': 'New York' 80 | }, 81 | { 82 | 'offset': 10, 83 | 'value': 'United States' 84 | } 85 | ], 86 | 'types': [ 87 | 'administrative_area_level_1', 88 | 'political', 89 | 'geocode' 90 | ] 91 | }, 92 | { 93 | 'description': 'New Jersey, United States', 94 | 'id': '10806aba84cf3520ebd83c6a3f749bad23c4e2e6', 95 | 'matched_substrings': [ 96 | { 97 | 'length': 2, 98 | 'offset': 0 99 | } 100 | ], 101 | 'place_id': 'ChIJn0AAnpX7wIkRjW0_-Ad70iw', 102 | 'reference': '...', 103 | 'terms': [ 104 | { 105 | 'offset': 0, 106 | 'value': 'New Jersey' 107 | }, 108 | { 109 | 'offset': 12, 110 | 'value': 'United States' 111 | } 112 | ], 113 | 'types': [ 114 | 'administrative_area_level_1', 115 | 'political', 116 | 'geocode' 117 | ] 118 | }, 119 | { 120 | 'description': 'Newark, NJ, United States', 121 | 'id': 'c71040d6268e495203b4ca7ca4299893601f63fc', 122 | 'matched_substrings': [ 123 | { 124 | 'length': 2, 125 | 'offset': 0 126 | } 127 | ], 128 | 'place_id': 'ChIJHQ6aMnBTwokRc-T-3CrcvOE', 129 | 'reference': '...', 130 | 'terms': [ 131 | { 132 | 'offset': 0, 133 | 'value': 'Newark' 134 | }, 135 | { 136 | 'offset': 8, 137 | 'value': 'NJ' 138 | }, 139 | { 140 | 'offset': 12, 141 | 'value': 'United States' 142 | } 143 | ], 144 | 'types': [ 145 | 'locality', 146 | 'political', 147 | 'geocode' 148 | ] 149 | } 150 | ]; 151 | }; 152 | -------------------------------------------------------------------------------- /test/test.spec.jsx: -------------------------------------------------------------------------------- 1 | import Location from '../src/Location.jsx'; 2 | import TestUtils from 'react-addons-test-utils'; 3 | import React from 'react'; 4 | import predictions from './fixtures/predictions.js'; 5 | 6 | var component; 7 | var onLocationSet = sinon.stub(); 8 | var google = { 9 | createAutocompleteService: function () { 10 | return { 11 | getPlacePredictions: sinon.stub().callsArgWith(1, predictions()) 12 | }; 13 | }, 14 | createGeocoder: function () { 15 | return { 16 | geocode: sinon.stub().callsArgWith(1, [ 17 | { 18 | geometry: { 19 | location: { 20 | lat: () => 0, 21 | lng: () => 0 22 | } 23 | } 24 | } 25 | ], 'OK') 26 | }; 27 | } 28 | }; 29 | 30 | function simulateKeyboardEvent(el, keyCode, eventType = 'keyup') { 31 | var evt = document.createEvent('Events'); 32 | var code = typeof keyCode === 'string' ? keyCode.charCodeAt(0) : keyCode; 33 | 34 | evt.initEvent(eventType, true, true); 35 | evt.keyCode = evt.which = code; 36 | el.dispatchEvent(evt); 37 | }; 38 | 39 | function simulateEvent(el, eventName) { 40 | var evt = document.createEvent('Event'); 41 | 42 | evt.initEvent(eventName, true, true); 43 | el.dispatchEvent(evt); 44 | }; 45 | 46 | describe('Given an instance of the Component', function () { 47 | 48 | describe('when we render the component', function () { 49 | 50 | before(() => { 51 | component = TestUtils.renderIntoDocument( 52 | 55 | ); 56 | }); 57 | 58 | it('should have an input field', function () { 59 | var input = TestUtils.scryRenderedDOMComponentsWithTag(component, 'input'); 60 | 61 | expect(input).to.have.length.above(0, 'Expected to have element with tag '); 62 | }); 63 | 64 | describe('and when we type a city name and choose some of the suggestions', function () { 65 | this.timeout(5000); 66 | it('should call onLocationSet', function (done) { 67 | var input = TestUtils.findRenderedDOMComponentWithTag(component, 'input'); 68 | 69 | input.value = 'New'; 70 | simulateKeyboardEvent(input, 'n'); 71 | setTimeout(function () { 72 | simulateKeyboardEvent(input, 40, 'keydown'); 73 | simulateKeyboardEvent(input, 40, 'keydown'); 74 | simulateKeyboardEvent(input, 13, 'keydown'); 75 | simulateEvent(input, 'awesomplete-selectcomplete'); 76 | setTimeout(function () { 77 | expect(onLocationSet).to.be.called; 78 | done(); 79 | }, 10); 80 | }, 10); 81 | }); 82 | }); 83 | 84 | }); 85 | }); 86 | -------------------------------------------------------------------------------- /tests.webpack.js: -------------------------------------------------------------------------------- 1 | var context = require.context('./test', true, /.+\.spec\.jsx?$/); 2 | 3 | require('core-js/es5'); 4 | 5 | context.keys().forEach(context); 6 | module.exports = context; 7 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var WebpackDevServer = require('webpack-dev-server'); 3 | var path = require('path'); 4 | 5 | var host = '0.0.0.0'; 6 | var port = '9000'; 7 | 8 | var config = { 9 | entry: './example-es6/src', 10 | devtool: 'source-map', 11 | output: { 12 | path: __dirname + '/example-es6/build', 13 | filename: 'app.js', 14 | publicPath: __dirname + '/example-es6' 15 | }, 16 | module: { 17 | loaders: [ 18 | { 19 | test: /(\.jsx|\.js)$/, 20 | loader: 'babel', 21 | exclude: /(node_modules|bower_components)/, 22 | query: { 23 | presets: ['react', 'es2015'] 24 | } 25 | }, 26 | { 27 | test: /(\.jsx|\.js)$/, 28 | loader: "eslint-loader", 29 | exclude: /node_modules/ 30 | } 31 | ] 32 | }, 33 | resolve: { 34 | root: path.resolve('./src'), 35 | extensions: ['', '.js', '.jsx'] 36 | } 37 | }; 38 | 39 | new WebpackDevServer(webpack(config), { 40 | contentBase: './example-es6', 41 | hot: true, 42 | debug: true 43 | }).listen(port, host, function (err, result) { 44 | if (err) { 45 | console.log(err); 46 | } 47 | }); 48 | console.log('-------------------------'); 49 | console.log('Local web server runs at http://' + host + ':' + port); 50 | console.log('-------------------------'); 51 | 52 | module.exports = config; 53 | --------------------------------------------------------------------------------