├── .travis.yml
├── .npmignore
├── src
├── index.js
└── GooglePlaceAutocomplete.js
├── .babelrc
├── test
├── MockAutocompleteService.js
├── .setup.js
└── GooglePlaceAutocomplete.spec.js
├── .gitignore
├── webpack.config.js
├── package.json
├── README.md
└── .eslintrc
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "6"
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | test
3 | webpack.*
4 | .babelrc
5 | .eslintrc
6 | .travis.yml
7 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import GooglePlaceAutocomplete from './GooglePlaceAutocomplete';
2 |
3 | export default GooglePlaceAutocomplete;
4 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["react", "es2015"],
3 | "plugins": ["babel-plugin-add-module-exports", "transform-object-rest-spread"]
4 | }
5 |
--------------------------------------------------------------------------------
/test/MockAutocompleteService.js:
--------------------------------------------------------------------------------
1 | export default class MockAutoCompleteService {
2 | getPlacePredictions(request, callback) {
3 | callback([]);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/test/.setup.js:
--------------------------------------------------------------------------------
1 | require('babel-register')();
2 |
3 | var jsdom = require('jsdom').jsdom;
4 |
5 | var exposedProperties = ['window', 'navigator', 'document'];
6 |
7 | global.document = jsdom('');
8 | global.window = document.defaultView;
9 | Object.keys(document.defaultView).forEach((property) => {
10 | if (typeof global[property] === 'undefined') {
11 | exposedProperties.push(property);
12 | global[property] = document.defaultView[property];
13 | }
14 | });
15 |
16 | global.navigator = {
17 | userAgent: 'node.js'
18 | };
19 |
20 | documentRef = document;
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | .DS_Store
30 |
31 | lib
32 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var webpack = require('webpack');
2 | var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
3 | var path = require('path');
4 | var env = require('yargs').argv.env;
5 |
6 | var libraryName = 'material-ui-places';
7 |
8 | var plugins = [], outputFile;
9 |
10 | if (env.mode === 'build') {
11 | plugins.push(new UglifyJsPlugin({ minimize: true }));
12 | outputFile = libraryName + '.min.js';
13 | } else {
14 | outputFile = libraryName + '.js';
15 | }
16 |
17 | var config = {
18 | entry: __dirname + '/src/index.js',
19 | output: {
20 | path: __dirname + '/lib',
21 | filename: outputFile,
22 | library: libraryName,
23 | libraryTarget: 'umd',
24 | umdNamedDefine: true
25 | },
26 | externals: {
27 | 'react': 'react',
28 | 'material-ui': 'material-ui',
29 | 'prop-types': 'prop-types'
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /(\.jsx|\.js)$/,
35 | use: 'babel-loader',
36 | exclude: /(node_modules|bower_components)/
37 | },
38 | {
39 | test: /(\.jsx|\.js)$/,
40 | use: 'eslint-loader',
41 | exclude: /node_modules/
42 | }
43 | ]
44 | },
45 | resolve: {
46 | modules: [
47 | path.resolve('./src')
48 | ],
49 | extensions: ['.js']
50 | },
51 | plugins: plugins
52 | };
53 |
54 | module.exports = config;
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "material-ui-places",
3 | "version": "1.1.7",
4 | "description": "Wrapper on top of the material-ui AutoComplete component that use google place api",
5 | "main": "lib/material-ui-places.min.js",
6 | "scripts": {
7 | "build": "webpack --env.mode=build",
8 | "dev": "webpack --progress --colors --watch --mode=dev",
9 | "test": "mocha ./test/.setup.js ./test/**/*.spec.js"
10 | },
11 | "devDependencies": {
12 | "babel": "6.23.0",
13 | "babel-cli": "^6.16.0",
14 | "babel-core": "6.23.1",
15 | "babel-eslint": "7.1.1",
16 | "babel-loader": "6.3.2",
17 | "babel-plugin-add-module-exports": "0.2.1",
18 | "babel-plugin-transform-object-rest-spread": "^6.23.0",
19 | "babel-preset-es2015": "6.22.0",
20 | "babel-preset-react": "^6.16.0",
21 | "babel-register": "^6.16.3",
22 | "chai": "3.5.0",
23 | "enzyme": "^2.8.2",
24 | "eslint": "3.16.1",
25 | "eslint-loader": "1.6.3",
26 | "eslint-plugin-react": "^6.4.1",
27 | "jsdom": "^9.6.0",
28 | "material-ui": "^0.17.0",
29 | "mocha": "3.2.0",
30 | "prop-types": "^15.5.10",
31 | "react": "^15.5.4",
32 | "react-dom": "^15.5.4",
33 | "react-tap-event-plugin": "^2.0.1",
34 | "react-test-renderer": "^15.5.4",
35 | "sinon": "^1.17.6",
36 | "webpack": "2.2.1",
37 | "yargs": "6.6.0"
38 | },
39 | "repository": {
40 | "type": "git",
41 | "url": "https://github.com/ydeshayes/googlePlaceAutocomplete.git"
42 | },
43 | "keywords": [
44 | "google place",
45 | "place",
46 | "location",
47 | "autoComplete",
48 | "material-ui",
49 | "React",
50 | "ReactJS",
51 | "select",
52 | "google api"
53 | ],
54 | "author": "Yann Deshayes",
55 | "bugs": {
56 | "url": "https://github.com/ydeshayes/googlePlaceAutocomplete/issues"
57 | },
58 | "homepage": "https://github.com/ydeshayes/googlePlaceAutocomplete"
59 | }
60 |
--------------------------------------------------------------------------------
/test/GooglePlaceAutocomplete.spec.js:
--------------------------------------------------------------------------------
1 | /* global expect*/
2 |
3 | import sinon from 'sinon';
4 | import React from 'react';
5 | import { expect } from 'chai';
6 | import { shallow } from 'enzyme';
7 |
8 | import AutocompleteService from './MockAutocompleteService';
9 | import GooglePlaceAutocomplete from '../src';
10 |
11 | describe('', () => {
12 | before(function () {
13 | global.google = {
14 | maps: {
15 | LatLng: function (lat, lng) {
16 | return {
17 | latitude: parseFloat(lat),
18 | longitude: parseFloat(lng),
19 |
20 | lat: function () { return this.latitude; },
21 | lng: function () { return this.longitude; }
22 | };
23 | },
24 | LatLngBounds: function (ne, sw) {
25 | return {
26 | getSouthWest: function () { return sw; },
27 | getNorthEast: function () { return ne; }
28 | };
29 | },
30 | places: {
31 | AutocompleteService
32 | }
33 | }
34 | };
35 | });
36 |
37 | it('Render GooglePlaceAutocomplete', () => {
38 |
39 | const onNewRequest = sinon.spy();
40 |
41 | const onChange = sinon.spy();
42 |
43 | let wrapper = shallow();
48 | });
49 |
50 | it('Accepts country restrictions', () => {
51 | sinon.spy(AutocompleteService.prototype, 'getPlacePredictions');
52 | const onNewRequest = sinon.spy();
53 |
54 | const onChange = sinon.spy();
55 |
56 | let wrapper = shallow();
63 |
64 | wrapper.setProps({ searchText: 'test' });
65 |
66 | expect(AutocompleteService.prototype.getPlacePredictions.calledOnce).to.be.true;
67 | expect(AutocompleteService.prototype.getPlacePredictions.args[0][0]).to.have.property('componentRestrictions');
68 | expect(AutocompleteService.prototype.getPlacePredictions.args[0][0].componentRestrictions).to.have.property('country').that.includes('France');
69 |
70 | AutocompleteService.prototype.getPlacePredictions.restore();
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/ydeshayes/googlePlaceAutocomplete)
2 | # material-ui-places component for ReactJS
3 |
4 | Wrapper on top of the material-ui AutoComplete component that use google place api
5 |
6 | ## Installation
7 |
8 | Add this script to your html page:
9 | ```html
10 |
11 |
12 |
13 | ```
14 |
15 | Material-ui is required:
16 |
17 | ```
18 | npm install material-ui
19 | ```
20 |
21 | ```
22 | npm install material-ui-places
23 | ```
24 |
25 | ## Features
26 |
27 | * AutoComplete that auto-load google places
28 |
29 | ## Getting started
30 |
31 |
32 | ```jsx
33 |
39 | ```
40 | ### Props:
41 |
42 | * [Same as AutoComplete material-ui component](http://www.material-ui.com/#/components/auto-complete)
43 |
44 | * onNewRequest: function -> (selectedData, searchedText, selectedDataIndex)
45 |
46 | * onChange: function -> ({target: {value: searchText}}, dataSource, params)
47 |
48 | * location: {lat: latitude, lng: longitude}, default: ```{lat: 0, lng: 0}``` see [LatLng](https://developers.google.com/maps/documentation/javascript/reference?hl=fr#LatLng)
49 |
50 | * radius: number, default: ```0```
51 |
52 | * bounds: object, ```{sw: southWest, ne: northEast}``` for [LatLngBounds](https://developers.google.com/maps/documentation/javascript/reference?hl=fr#LatLngBounds) or ```{south: south, east: east, north: north, west: west}``` for [LatLngBoundsLiteral](https://developers.google.com/maps/documentation/javascript/reference?hl=fr#LatLngBoundsLiteral) default: ```undefined```
53 |
54 | * getRef: function -> (ref)
55 |
56 | * types: Array, ```
57 | The types of predictions to be returned. Four types are supported: 'establishment' for businesses, 'geocode' for addresses, '(regions)' for administrative regions and '(cities)' for localities. If nothing is specified, all types are returned.```, default ```undefined```
58 |
59 | * restrictions: ```country: Array|String```, ```{ country: [ 'fr', 'gb'] | 'gb' }```
60 | Restricts predictions to the specified country (ISO 3166-1 Alpha-2 country code, case insensitive). E.g., us, br, au. You can provide a single one, or an array of up to 5 country code strings. See [ComponentRestrictions](https://developers.google.com/maps/documentation/javascript/reference#ComponentRestrictions)
61 |
62 | ## Development
63 |
64 | * `npm run build` - produces production version
65 | * `npm run dev` - produces development version
66 | * `npm test` - run the tests
67 |
--------------------------------------------------------------------------------
/src/GooglePlaceAutocomplete.js:
--------------------------------------------------------------------------------
1 | /* global google*/
2 |
3 | import React from 'react';
4 | import { AutoComplete } from 'material-ui';
5 | import PropTypes from 'prop-types';
6 |
7 | class GooglePlaceAutocomplete extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.autocompleteService = new google.maps.places.AutocompleteService();
11 | this.state = {
12 | dataSource: [],
13 | data: []
14 | };
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (this.props.searchText !== nextProps.searchText) {
19 | this.onUpdateInput(nextProps.searchText, this.state.dataSource);
20 | this.onInputChange(nextProps.searchText);
21 | }
22 | }
23 |
24 | updateDatasource(data) {
25 | if (!data || !data.length) {
26 | return false;
27 | }
28 |
29 | if (this.state.data) {
30 | this.previousData = { ...this.state.data };
31 | }
32 | this.setState({
33 | dataSource: data.map(place => place.description),
34 | data
35 | });
36 | }
37 |
38 | getBounds() {
39 | if (!this.props.bounds || (!this.props.bounds.ne && !this.props.bounds.south)) {
40 | return undefined;
41 | }
42 |
43 | if (this.props.bounds.ne && this.props.bounds.sw) {
44 | return new google.maps.LatLngBounds(this.props.bounds.sw, this.props.bounds.ne);
45 | }
46 |
47 | return {
48 | ...this.props.bounds
49 | };
50 | }
51 |
52 | onUpdateInput(searchText, dataSource) {
53 | if (!searchText.length || !this.autocompleteService) {
54 | return false;
55 | }
56 |
57 | let request = {
58 | input: searchText,
59 | location: new google.maps.LatLng(this.props.location.lat, this.props.location.lng),
60 | radius: this.props.radius,
61 | types: this.props.types,
62 | bounds: this.getBounds()
63 | };
64 |
65 | if (this.props.restrictions) {
66 | request.componentRestrictions = { ...this.props.restrictions };
67 | }
68 |
69 | this.autocompleteService.getPlacePredictions(request, data => this.updateDatasource(data));
70 | }
71 |
72 | onNewRequest(searchText, index) {
73 | // The index in dataSource of the list item selected, or -1 if enter is pressed in the TextField
74 | if (index === -1) {
75 | return false;
76 | }
77 | const data = this.previousData || this.state.data;
78 |
79 | this.props.onNewRequest(data[index], searchText, index);
80 | }
81 |
82 | onInputChange(searchText, dataSource, params) {
83 | this.props.onChange({target: {value: searchText}}, dataSource, params);
84 | }
85 |
86 | render() {
87 | const {
88 | location, radius, bounds, types, restrictions, ...autoCompleteProps // eslint-disable-line no-unused-vars
89 | } = this.props;
90 |
91 | return (
92 |
101 | );
102 | }
103 | }
104 |
105 | GooglePlaceAutocomplete.propTypes = {
106 | location: PropTypes.object,
107 | radius: PropTypes.number,
108 | onNewRequest: PropTypes.func.isRequired,
109 | onChange: PropTypes.func.isRequired,
110 | getRef: PropTypes.func,
111 | types: PropTypes.arrayOf(PropTypes.string),
112 | bounds: PropTypes.object,
113 | restrictions: PropTypes.shape({
114 | country: PropTypes.oneOfType([
115 | PropTypes.string,
116 | PropTypes.arrayOf(PropTypes.string)
117 | ])
118 | })
119 | };
120 |
121 | GooglePlaceAutocomplete.defaultProps = {
122 | location: {lat: 0, lng: 0},
123 | radius: 0,
124 | filter: AutoComplete.noFilter
125 | };
126 |
127 | export default GooglePlaceAutocomplete;
128 |
--------------------------------------------------------------------------------
/.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 | "parserOptions": {
30 | "ecmaVersion": 6,
31 | "sourceType": "module",
32 | "ecmaFeatures": {
33 | "modules": true,
34 | "jsx": true
35 | }
36 | },
37 |
38 | "plugins": [
39 | "react"
40 | ],
41 |
42 | "rules": {
43 | "block-scoped-var": 2,
44 | "brace-style": [2, "1tbs", { "allowSingleLine": true }],
45 | "camelcase": [2, { "properties": "always" }],
46 | "comma-dangle": [2, "never"],
47 | "comma-spacing": [2, { "before": false, "after": true }],
48 | "comma-style": [2, "last"],
49 | "complexity": 0,
50 | "consistent-this": 0,
51 | "curly": [2, "multi-line"],
52 | "default-case": 0,
53 | "dot-location": [2, "property"],
54 | "dot-notation": 0,
55 | "eol-last": 2,
56 | "eqeqeq": [2, "allow-null"],
57 | "func-names": 0,
58 | "func-style": 0,
59 | "generator-star-spacing": [2, "both"],
60 | "guard-for-in": 0,
61 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
62 | "indent": [2, 2, { "SwitchCase": 1 }],
63 | "react/jsx-uses-vars": 1,
64 | "key-spacing": [2, { "beforeColon": false, "afterColon": true }],
65 | "keyword-spacing": 2,
66 | "linebreak-style": 0,
67 | "max-depth": 0,
68 | "max-len": [2, 120, 4],
69 | "max-nested-callbacks": 0,
70 | "max-params": 0,
71 | "max-statements": 0,
72 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
73 | "newline-after-var": [2, "always"],
74 | "new-parens": 2,
75 | "no-alert": 0,
76 | "no-array-constructor": 2,
77 | "no-bitwise": 0,
78 | "no-caller": 2,
79 | "no-catch-shadow": 0,
80 | "no-cond-assign": 2,
81 | "no-console": 0,
82 | "no-constant-condition": 0,
83 | "no-continue": 0,
84 | "no-control-regex": 2,
85 | "no-debugger": 2,
86 | "no-delete-var": 2,
87 | "no-div-regex": 0,
88 | "no-dupe-args": 2,
89 | "no-dupe-keys": 2,
90 | "no-duplicate-case": 2,
91 | "no-else-return": 2,
92 | "no-empty": 0,
93 | "no-empty-character-class": 2,
94 | "no-eq-null": 0,
95 | "no-eval": 2,
96 | "no-ex-assign": 2,
97 | "no-extend-native": 2,
98 | "no-extra-bind": 2,
99 | "no-extra-boolean-cast": 2,
100 | "no-extra-parens": 0,
101 | "no-extra-semi": 0,
102 | "no-extra-strict": 0,
103 | "no-fallthrough": 2,
104 | "no-floating-decimal": 2,
105 | "no-func-assign": 2,
106 | "no-implied-eval": 2,
107 | "no-inline-comments": 0,
108 | "no-inner-declarations": [2, "functions"],
109 | "no-invalid-regexp": 2,
110 | "no-irregular-whitespace": 2,
111 | "no-iterator": 2,
112 | "no-label-var": 2,
113 | "no-labels": 2,
114 | "no-lone-blocks": 0,
115 | "no-lonely-if": 0,
116 | "no-loop-func": 0,
117 | "no-mixed-requires": 0,
118 | "no-mixed-spaces-and-tabs": [2, false],
119 | "no-multi-spaces": 2,
120 | "no-multi-str": 2,
121 | "no-multiple-empty-lines": [2, { "max": 1 }],
122 | "no-native-reassign": 2,
123 | "no-negated-in-lhs": 2,
124 | "no-nested-ternary": 0,
125 | "no-new": 2,
126 | "no-new-func": 2,
127 | "no-new-object": 2,
128 | "no-new-require": 2,
129 | "no-new-wrappers": 2,
130 | "no-obj-calls": 2,
131 | "no-octal": 2,
132 | "no-octal-escape": 2,
133 | "no-path-concat": 0,
134 | "no-plusplus": 0,
135 | "no-process-env": 0,
136 | "no-process-exit": 0,
137 | "no-proto": 2,
138 | "no-redeclare": 2,
139 | "no-regex-spaces": 2,
140 | "no-reserved-keys": 0,
141 | "no-restricted-modules": 0,
142 | "no-return-assign": 2,
143 | "no-script-url": 0,
144 | "no-self-compare": 2,
145 | "no-sequences": 2,
146 | "no-shadow": 0,
147 | "no-shadow-restricted-names": 2,
148 | "no-spaced-func": 2,
149 | "no-sparse-arrays": 2,
150 | "no-sync": 0,
151 | "no-ternary": 0,
152 | "no-throw-literal": 2,
153 | "no-trailing-spaces": 2,
154 | "no-undef": 2,
155 | "no-undef-init": 2,
156 | "no-undefined": 0,
157 | "no-underscore-dangle": 0,
158 | "no-unneeded-ternary": 2,
159 | "no-unreachable": 2,
160 | "no-unused-expressions": 0,
161 | "no-unused-vars": [2, { "vars": "all", "args": "none" }],
162 | "no-use-before-define": 2,
163 | "no-var": 0,
164 | "no-void": 0,
165 | "no-warning-comments": 0,
166 | "no-with": 2,
167 | "one-var": 0,
168 | "operator-assignment": 0,
169 | "operator-linebreak": [2, "before"],
170 | "padded-blocks": 0,
171 | "quote-props": 0,
172 | "quotes": [2, "single", "avoid-escape"],
173 | "semi": [2, "always"],
174 | "semi-spacing": 0,
175 | "sort-vars": 0,
176 | "space-before-blocks": [2, "always"],
177 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}],
178 | "space-in-brackets": 0,
179 | "space-in-parens": [2, "never"],
180 | "space-infix-ops": 2,
181 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
182 | "spaced-comment": [2, "always"],
183 | "strict": 0,
184 | "use-isnan": 2,
185 | "valid-jsdoc": 0,
186 | "valid-typeof": 2,
187 | "vars-on-top": 2,
188 | "wrap-iife": [2, "any"],
189 | "wrap-regex": 0,
190 | "yoda": [2, "never"]
191 | }
192 | }
193 |
--------------------------------------------------------------------------------