mirror of
https://github.com/jlengrand/leaflet-geosearch.git
synced 2026-03-10 08:31:26 +00:00
total rewrite, rewritten in es6, added tests, improved decoupling
This commit is contained in:
22
.babelrc
Normal file
22
.babelrc
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"presets": ["es2015", "stage-0", "power-assert"],
|
||||||
|
"plugins": [
|
||||||
|
["transform-react-jsx", { "pragma": "preact.h"}],
|
||||||
|
"transform-async-to-generator",
|
||||||
|
["babel-plugin-espower", {
|
||||||
|
"embedAst": true,
|
||||||
|
"patterns": [
|
||||||
|
"t.truthy(value, [message])",
|
||||||
|
"t.falsy(value, [message])",
|
||||||
|
"t.true(value, [message])",
|
||||||
|
"t.false(value, [message])",
|
||||||
|
"t.is(value, expected, [message])",
|
||||||
|
"t.not(value, expected, [message])",
|
||||||
|
"t.deepEqual(value, expected, [message])",
|
||||||
|
"t.notDeepEqual(value, expected, [message])",
|
||||||
|
"t.regex(contents, regex, [message])",
|
||||||
|
"t.notRegex(contents, regex, [message])"
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# @see http://editorconfig.org/
|
||||||
|
|
||||||
|
# This is the top-most .editorconfig file; do not search in parent directories.
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# All files.
|
||||||
|
[*]
|
||||||
|
end_of_line = LF
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
2
.env
Normal file
2
.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
BING_API_KEY=AtUDjSVEBxo8BwgYUPdfnzHpznaYwDdjjS27jyFDj18nhTUDUjrhc0NwMndZvrXs
|
||||||
|
GOOGLE_API_KEY=AIzaSyDigZ5WMPoTj_gnkUn3p1waYPDa5oE8WOw
|
||||||
55
.eslintrc.yml
Normal file
55
.eslintrc.yml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
extends: "airbnb"
|
||||||
|
|
||||||
|
env:
|
||||||
|
browser: true
|
||||||
|
|
||||||
|
parser: "babel-eslint"
|
||||||
|
parserOptions:
|
||||||
|
ecmaVersion: 6
|
||||||
|
ecmaFeatures:
|
||||||
|
experimentalObjectRestSpread: true
|
||||||
|
jsx: true
|
||||||
|
sourceType: "module"
|
||||||
|
|
||||||
|
rules:
|
||||||
|
# we need this to test leaflet private vars
|
||||||
|
no-underscore-dangle: "off"
|
||||||
|
|
||||||
|
# force else and catch to a new line
|
||||||
|
brace-style:
|
||||||
|
- "error"
|
||||||
|
- "stroustrup"
|
||||||
|
- allowSingleLine: true
|
||||||
|
|
||||||
|
quote-props:
|
||||||
|
- "error"
|
||||||
|
- "consistent-as-needed"
|
||||||
|
- keywords: true
|
||||||
|
|
||||||
|
class-methods-use-this: "off"
|
||||||
|
|
||||||
|
import/no-extraneous-dependencies:
|
||||||
|
- "error"
|
||||||
|
- devDependencies:
|
||||||
|
- "**/*.spec.js"
|
||||||
|
- "**/test_helpers/**/*.js"
|
||||||
|
- "example/**/*.js"
|
||||||
|
|
||||||
|
react/jsx-filename-extension:
|
||||||
|
- "error"
|
||||||
|
- extensions:
|
||||||
|
- ".js"
|
||||||
|
|
||||||
|
react/prop-types: "off"
|
||||||
|
|
||||||
|
jsx-a11y/anchor-has-content: "off"
|
||||||
|
|
||||||
|
globals:
|
||||||
|
L: false
|
||||||
|
fetch: false
|
||||||
|
document: false
|
||||||
|
location: false
|
||||||
|
|
||||||
|
settings:
|
||||||
|
react:
|
||||||
|
pragma: "preact"
|
||||||
22
.gitattributes
vendored
22
.gitattributes
vendored
@@ -1,22 +0,0 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
# Custom for Visual Studio
|
|
||||||
*.cs diff=csharp
|
|
||||||
*.sln merge=union
|
|
||||||
*.csproj merge=union
|
|
||||||
*.vbproj merge=union
|
|
||||||
*.fsproj merge=union
|
|
||||||
*.dbproj merge=union
|
|
||||||
|
|
||||||
# Standard to msysgit
|
|
||||||
*.doc diff=astextplain
|
|
||||||
*.DOC diff=astextplain
|
|
||||||
*.docx diff=astextplain
|
|
||||||
*.DOCX diff=astextplain
|
|
||||||
*.dot diff=astextplain
|
|
||||||
*.DOT diff=astextplain
|
|
||||||
*.pdf diff=astextplain
|
|
||||||
*.PDF diff=astextplain
|
|
||||||
*.rtf diff=astextplain
|
|
||||||
*.RTF diff=astextplain
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -165,3 +165,6 @@ pip-log.txt
|
|||||||
*.sublime-project
|
*.sublime-project
|
||||||
*.sublime-workspace
|
*.sublime-workspace
|
||||||
.idea
|
.idea
|
||||||
|
dist/
|
||||||
|
lib/
|
||||||
|
node_modules/
|
||||||
|
|||||||
1
.npmignore
Normal file
1
.npmignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
src/
|
||||||
0
assets/css/geosearch.css
Normal file
0
assets/css/geosearch.css
Normal file
@@ -1,34 +1,32 @@
|
|||||||
.displayNone {
|
.geosearch.leaflet-bar form,
|
||||||
display: none;
|
.geosearch.leaflet-bar .message {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch {
|
.geosearch.leaflet-bar.active form {
|
||||||
position: relative;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch a {
|
.geosearch a.leaflet-bar-part {
|
||||||
-webkit-border-radius: 4px;
|
border-radius: 4px;
|
||||||
border-radius: 4px;
|
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-control-geosearch a.glass {
|
|
||||||
background-image: url(../img/geosearch.png);
|
background-image: url(../img/geosearch.png);
|
||||||
|
background-position: center center;
|
||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch a.spinner {
|
.geosearch.pending a.leaflet-bar-part {
|
||||||
background-image: url(../img/spinner.gif);
|
background-image: url(../img/spinner.gif);
|
||||||
background-position: 50% 50%;
|
background-size: 12px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch a.alert {
|
.geosearch.error a.leaflet-bar-part {
|
||||||
background-image: url(../img/alert.png);
|
background-image: url(../img/alert.png);
|
||||||
background-size: 64% 64%;
|
background-size: 18px 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch a:hover {
|
.leaflet-control-geosearch {
|
||||||
border-bottom: none;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch form {
|
.leaflet-control-geosearch form {
|
||||||
@@ -36,17 +34,12 @@
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 22px;
|
left: 22px;
|
||||||
box-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
|
box-shadow: 0 1px 7px rgba(0, 0, 0, 0.65);
|
||||||
-webkit-border-radius: 4px;
|
border-radius: 0 4px 4px 0;
|
||||||
border-radius: 0px 4px 4px 0px;
|
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
background: #FFF;
|
background: #FFF;
|
||||||
height: 26px;
|
height: auto;
|
||||||
padding: 0 6px 0 6px;
|
margin: 0;
|
||||||
}
|
padding: 0;
|
||||||
|
|
||||||
.leaflet-touch .leaflet-control-geosearch form {
|
|
||||||
padding: 2px 6px 2px 8px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch form input {
|
.leaflet-control-geosearch form input {
|
||||||
@@ -56,7 +49,9 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
height: 30px;
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.leaflet-control-geosearch .message {
|
.leaflet-control-geosearch .message {
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 699 B After Width: | Height: | Size: 699 B |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
54
example/Layout.js
Normal file
54
example/Layout.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import preact, { Component } from 'preact';
|
||||||
|
import Search from './Search';
|
||||||
|
|
||||||
|
class Layout extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
hash: window.location.hash.slice(1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
window.addEventListener('hashchange', this.changePage, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
window.removeEventListener('hashchange', this.changePage, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
changePage = () => {
|
||||||
|
this.setState({
|
||||||
|
hash: window.location.hash.slice(1),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { pages } = this.props;
|
||||||
|
const { hash } = this.state;
|
||||||
|
const page = pages.find(p => p.slug === (hash || 'search'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="header">
|
||||||
|
<h1>{`GeoSearch / ${page.title}`}</h1>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
{pages.map((p, idx) => (
|
||||||
|
<li key={idx} className={p.slug === hash ? 'active' : ''}>
|
||||||
|
<a href={`#${p.slug}`}>{p.title}</a>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={`content ${hash}`}>
|
||||||
|
{page && page.view()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Layout;
|
||||||
55
example/Map.js
Normal file
55
example/Map.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import preact, { Component } from 'preact';
|
||||||
|
import merge from 'lodash.merge';
|
||||||
|
import { GeoSearchControl, OpenStreetMapProvider, Provider as BaseProvider } from '../src';
|
||||||
|
|
||||||
|
const L = window.L;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-confusing-arrow
|
||||||
|
const ensureInstance = Provider => Provider instanceof BaseProvider ? Provider : new Provider();
|
||||||
|
|
||||||
|
const mapOptions = () => ({
|
||||||
|
layers: [
|
||||||
|
new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
maxZoom: 18,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
center: new L.LatLng(53.2, 5.8),
|
||||||
|
zoom: 12,
|
||||||
|
});
|
||||||
|
|
||||||
|
class Map extends Component {
|
||||||
|
componentDidMount() {
|
||||||
|
const { options, Provider } = this.props;
|
||||||
|
|
||||||
|
this.map = new L.Map(this.container, merge(mapOptions(), options));
|
||||||
|
|
||||||
|
const provider = (Provider) ? ensureInstance(Provider) : new OpenStreetMapProvider();
|
||||||
|
|
||||||
|
this.searchControl = new GeoSearchControl({
|
||||||
|
provider,
|
||||||
|
}).addTo(this.map);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
const Provider = this.props.Provider || OpenStreetMapProvider;
|
||||||
|
this.searchControl.options.provider = ensureInstance(Provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.map.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
bindContainer = (container) => {
|
||||||
|
this.container = container;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { style } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="leaflet-map" style={style} ref={this.bindContainer} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Map;
|
||||||
103
example/Search.js
Normal file
103
example/Search.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
import preact, { Component } from 'preact';
|
||||||
|
import debounce from 'lodash.debounce';
|
||||||
|
import * as providers from '../src/providers';
|
||||||
|
import SearchResults from './SearchResults';
|
||||||
|
|
||||||
|
const specialKeys = ['ArrowDown', 'ArrowUp', 'Escape'];
|
||||||
|
|
||||||
|
class Search extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
const Provider = providers[`${props.provider}Provider`] ||
|
||||||
|
providers.OpenStreetMapProvider;
|
||||||
|
|
||||||
|
this.provider = new Provider();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit = async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const { query } = this.state;
|
||||||
|
|
||||||
|
const results = await this.provider.search({ query });
|
||||||
|
this.setState({
|
||||||
|
results,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyUp = (event) => {
|
||||||
|
if (specialKeys.includes(event.code)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const query = event.target.value;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
query,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.autoSearch(event);
|
||||||
|
};
|
||||||
|
|
||||||
|
onKeyDown = (event) => {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
this.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.code !== 'ArrowDown' && event.code !== 'ArrowUp') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { selected = -1, results } = this.state;
|
||||||
|
const max = results.length - 1;
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-bitwise
|
||||||
|
const next = (event.code === 'ArrowDown') ? ~~selected + 1 : ~~selected - 1;
|
||||||
|
// eslint-disable-next-line no-nested-ternary
|
||||||
|
const idx = (next < 0) ? max : (next > max) ? 0 : next;
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
selected: idx,
|
||||||
|
query: results[idx].label,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
autoSearch = debounce((event) => {
|
||||||
|
this.onSubmit(event);
|
||||||
|
}, 250);
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.setState({
|
||||||
|
results: [],
|
||||||
|
selected: -1,
|
||||||
|
query: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { results, selected, query } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="search">
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
<input
|
||||||
|
onKeyUp={this.onKeyUp}
|
||||||
|
onKeyDown={this.onKeyDown}
|
||||||
|
type="text"
|
||||||
|
placeholder="search"
|
||||||
|
value={query}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{results &&
|
||||||
|
<SearchResults results={results} selected={selected} />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Search;
|
||||||
11
example/SearchResults.js
Normal file
11
example/SearchResults.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import preact, { Component } from 'preact';
|
||||||
|
|
||||||
|
const SearchResults = ({ results = [], selected }) => (
|
||||||
|
<div className="results">
|
||||||
|
{results.map((result, idx) => (
|
||||||
|
<div className={idx === selected ? 'active' : ''}>{result.label}</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default SearchResults;
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
new L.Control.GeoSearch({
|
new L.Control.GeoSearch({
|
||||||
provider: new L.GeoSearch.Provider.Google()
|
provider: new L.GeoSearch.Provider.Google()
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
18
example/index.html
Normal file
18
example/index.html
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Leaflet.GeoSearch / Google Provider</title>
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
|
||||||
|
<script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="style.css" />
|
||||||
|
<link rel="stylesheet" href="/assets/css/leaflet.css" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
<script type="text/javascript" src="/dist/bundle.js" charset="utf-8"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
61
example/main.js
Normal file
61
example/main.js
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import preact, { render } from 'preact';
|
||||||
|
import Layout from './Layout';
|
||||||
|
import Search from './Search';
|
||||||
|
import Map from './Map';
|
||||||
|
import * as providers from '../src/providers';
|
||||||
|
|
||||||
|
// import css to enable hot reloading
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
/* eslint-disable global-require */
|
||||||
|
require('./index.html');
|
||||||
|
require('./style.css');
|
||||||
|
require('../assets/css/leaflet.css');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
{
|
||||||
|
slug: 'search',
|
||||||
|
title: 'Search',
|
||||||
|
view: () => (<Search />),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'openstreetmap',
|
||||||
|
title: 'OpenStreetMap',
|
||||||
|
view: () => (<Map Provider={providers.OpenStreetMapProvider} />),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'google',
|
||||||
|
title: 'Google',
|
||||||
|
view: () => {
|
||||||
|
const Provider = new providers.GoogleProvider({ params: {
|
||||||
|
key: 'AIzaSyDigZ5WMPoTj_gnkUn3p1waYPDa5oE8WOw',
|
||||||
|
} });
|
||||||
|
|
||||||
|
return <Map Provider={Provider} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'bing',
|
||||||
|
title: 'Bing',
|
||||||
|
view: () => {
|
||||||
|
const Provider = new providers.BingProvider({ params: {
|
||||||
|
key: 'AtUDjSVEBxo8BwgYUPdfnzHpznaYwDdjjS27jyFDj18nhTUDUjrhc0NwMndZvrXs',
|
||||||
|
} });
|
||||||
|
|
||||||
|
return <Map Provider={Provider} />;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
slug: 'esri',
|
||||||
|
title: 'Esri',
|
||||||
|
view: () => (<Map Provider={providers.EsriProvider} />),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (location.hash === '') {
|
||||||
|
location.hash = 'search';
|
||||||
|
}
|
||||||
|
|
||||||
|
render((
|
||||||
|
<Layout pages={pages} />
|
||||||
|
), document.getElementById('app'));
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
center: new L.LatLng(53.2, 5.8), zoom: 12
|
center: new L.LatLng(53.2, 5.8), zoom: 12
|
||||||
});
|
});
|
||||||
|
|
||||||
new L.Control.GeoSearch({
|
new L.Control.GeoSearch({
|
||||||
provider: new L.GeoSearch.Provider.Nokia()
|
provider: new L.GeoSearch.Provider.Nokia()
|
||||||
}).addTo(map);
|
}).addTo(map);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -6,26 +6,27 @@
|
|||||||
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
|
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.css" />
|
||||||
<script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
|
<script src="http://cdn.leafletjs.com/leaflet/v0.7.7/leaflet.js"></script>
|
||||||
|
|
||||||
<script src="../src/js/l.control.geosearch.js"></script>
|
<script src="../dist/geosearch.js"></script>
|
||||||
<script src="../src/js/l.geosearch.provider.openstreetmap.js"></script>
|
<link rel="stylesheet" href="../assets/css/leaflet.css" />
|
||||||
<link rel="stylesheet" href="../src/css/l.geosearch.css" />
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="map" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
<div id="map" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
const { GeoSearchControl, OpenStreetMapProvider } = window.GeoSearch;
|
||||||
|
|
||||||
var osmTileUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
const osmTileUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
var basemap = new L.TileLayer(osmTileUrl, {maxZoom: 18});
|
const basemap = new L.TileLayer(osmTileUrl, { maxZoom: 18 });
|
||||||
|
|
||||||
var map = new L.Map('map', {
|
const map = new L.Map('map', {
|
||||||
layers: [basemap],
|
layers: [basemap],
|
||||||
center: new L.LatLng(53.2, 5.8), zoom: 12
|
center: new L.LatLng(53.2, 5.8), zoom: 12
|
||||||
});
|
});
|
||||||
|
|
||||||
new L.Control.GeoSearch({
|
const provider = new OpenStreetMapProvider();
|
||||||
provider: new L.GeoSearch.Provider.OpenStreetMap()
|
new GeoSearchControl({
|
||||||
}).addTo(map);
|
provider
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
128
example/style.css
Normal file
128
example/style.css
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
html, body {
|
||||||
|
font-family: 'Open Sans', sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app, #app > .container {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app .header,
|
||||||
|
.content.search {
|
||||||
|
width: 80%;
|
||||||
|
margin: 0 auto;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app .header {
|
||||||
|
height: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 64px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
line-height: 32px
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li {
|
||||||
|
float: left;
|
||||||
|
display: block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.active {
|
||||||
|
border-bottom: 4px solid #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li:hover {
|
||||||
|
border-bottom: 4px solid #00bcd4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(100% - 100px);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.results > * {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
line-height: 32px;
|
||||||
|
padding: 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results > *:hover,
|
||||||
|
.results > .active {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border-color: #c6c6c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
position: relative;
|
||||||
|
margin: 32px 0;
|
||||||
|
background-color: #fff;
|
||||||
|
vertical-align: top;
|
||||||
|
border-radius: 2px;
|
||||||
|
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.16), 0 0 0 1px rgba(0,0,0,0.08);
|
||||||
|
transition: box-shadow 200ms cubic-bezier(0.4, 0.0, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
form.active, form:hover {
|
||||||
|
box-shadow: 0 3px 8px 0 rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
font: 16px arial, sans-serif;
|
||||||
|
line-height: 48px;
|
||||||
|
height: 48px;
|
||||||
|
text-indent: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
form button {
|
||||||
|
outline: none;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100px;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
border: 1px solid #c6c6c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.leaflet-map {
|
||||||
|
position: absolute;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
97
package.json
97
package.json
@@ -1,24 +1,36 @@
|
|||||||
{
|
{
|
||||||
"name": "leaflet-geosearch",
|
"name": "leaflet-geosearch",
|
||||||
"version": "1.1.0",
|
"version": "2.0.0-rc.1",
|
||||||
|
"publishConfig": {
|
||||||
|
"tag": "next"
|
||||||
|
},
|
||||||
"description": "Adds support for address lookup (a.k.a. geocoding / geoseaching) to Leaflet.",
|
"description": "Adds support for address lookup (a.k.a. geocoding / geoseaching) to Leaflet.",
|
||||||
"main": "src/js/l.control.geosearch.js",
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build:commonjs": "babel src --out-dir lib --ignore *.spec.js",
|
||||||
|
"build:umd": "cross-env NODE_ENV=development webpack",
|
||||||
|
"build:umd:min": "cross-env NODE_ENV=production webpack",
|
||||||
|
"build:watch": "npm run build:umd -- --watch",
|
||||||
|
"build": "npm run clean && npm run build:commonjs && npm run build:umd && npm run build:umd:min",
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"lint": "esw src webpack.config --color",
|
||||||
|
"lint:fix": "npm run lint -- --fix",
|
||||||
|
"lint:watch": "npm run lint -- --watch",
|
||||||
|
"prepublish": "npm run lint && npm run test && npm run build",
|
||||||
|
"test": "ava",
|
||||||
|
"test:watch": "ava -w",
|
||||||
|
"test:cover": "nyc ava",
|
||||||
|
"test:report": "nyc report --reporter html",
|
||||||
|
"start": "webpack-dev-server"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"dist",
|
||||||
|
"assets"
|
||||||
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"example": "example"
|
"example": "example"
|
||||||
},
|
},
|
||||||
"scripts": {
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git+https://github.com/smeijer/L.GeoSearch.git"
|
|
||||||
},
|
|
||||||
"author": "Stephan Meijer",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/smeijer/L.GeoSearch/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/smeijer/L.GeoSearch#readme",
|
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"geolocation",
|
"geolocation",
|
||||||
"geocoding",
|
"geocoding",
|
||||||
@@ -26,5 +38,58 @@
|
|||||||
"leaflet",
|
"leaflet",
|
||||||
"geo",
|
"geo",
|
||||||
"map"
|
"map"
|
||||||
]
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/smeijer/L.GeoSearch.git"
|
||||||
|
},
|
||||||
|
"author": "Stephan Meijer <stephan@meijer.ws>",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/smeijer/L.GeoSearch/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/smeijer/L.GeoSearch#readme",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"ava": "^0.17.0",
|
||||||
|
"babel-cli": "^6.18.0",
|
||||||
|
"babel-eslint": "^7.1.1",
|
||||||
|
"babel-loader": "^6.2.10",
|
||||||
|
"babel-plugin-transform-react-jsx": "^6.8.0",
|
||||||
|
"babel-polyfill": "^6.20.0",
|
||||||
|
"babel-preset-es2015": "^6.18.0",
|
||||||
|
"babel-preset-power-assert": "^1.0.0",
|
||||||
|
"babel-preset-stage-0": "^6.16.0",
|
||||||
|
"browser-env": "^2.0.16",
|
||||||
|
"cross-env": "^3.1.3",
|
||||||
|
"dotenv": "^2.0.0",
|
||||||
|
"eslint": "^3.12.2",
|
||||||
|
"eslint-config-airbnb": "^13.0.0",
|
||||||
|
"eslint-plugin-import": "^2.2.0",
|
||||||
|
"eslint-plugin-jsx-a11y": "^2.2.3",
|
||||||
|
"eslint-plugin-react": "^6.8.0",
|
||||||
|
"eslint-watch": "^2.1.14",
|
||||||
|
"leaflet": "^1.0.2",
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.merge": "^4.6.0",
|
||||||
|
"node-fetch": "^1.6.3",
|
||||||
|
"power-assert": "^1.4.2",
|
||||||
|
"preact": "^7.1.0",
|
||||||
|
"raw-loader": "^0.5.1",
|
||||||
|
"rimraf": "^2.5.4",
|
||||||
|
"testdouble": "^1.10.0",
|
||||||
|
"webpack": "^1.14.0",
|
||||||
|
"webpack-dev-server": "^1.16.2"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"ava": {
|
||||||
|
"files": [
|
||||||
|
"./src/**/__tests__/**.spec.js"
|
||||||
|
],
|
||||||
|
"require": [
|
||||||
|
"dotenv/config",
|
||||||
|
"babel-core/register",
|
||||||
|
"./test/browserEnv"
|
||||||
|
],
|
||||||
|
"babel": "inherit"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
src/__tests__/leafletControl.spec.js
Normal file
97
src/__tests__/leafletControl.spec.js
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import td from 'testdouble';
|
||||||
|
import LeafletControl from '../leafletControl';
|
||||||
|
import id from '../../test/randomId';
|
||||||
|
|
||||||
|
function MapInstance(options) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.id = id();
|
||||||
|
|
||||||
|
document.body.appendChild(div);
|
||||||
|
const map = new L.map(div, options); // eslint-disable-line new-cap
|
||||||
|
|
||||||
|
this.div = div;
|
||||||
|
this.map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Can init leaflet', (t) => {
|
||||||
|
const { div, map } = new MapInstance();
|
||||||
|
|
||||||
|
t.truthy(div._leaflet_id);
|
||||||
|
t.truthy(map._leaflet_id);
|
||||||
|
t.not(div._leaflet_id, map._leaflet_id);
|
||||||
|
t.true(map._initHooksCalled);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can add geosearch control to leaflet', (t) => {
|
||||||
|
const { div, map } = new MapInstance();
|
||||||
|
|
||||||
|
const provider = { search: td.function() };
|
||||||
|
const control = new LeafletControl({
|
||||||
|
provider,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
const element = document.querySelector(`#${div.id}`);
|
||||||
|
t.truthy(element._leaflet_events);
|
||||||
|
t.is(control._map, map);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('It toggles the active class when the search button is clicked', (t) => {
|
||||||
|
const { map } = new MapInstance();
|
||||||
|
|
||||||
|
const provider = { search: td.function() };
|
||||||
|
const control = new LeafletControl({
|
||||||
|
provider,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
const { button } = control.elements;
|
||||||
|
const { container } = control.searchElement.elements;
|
||||||
|
|
||||||
|
button.click(new Event('click'));
|
||||||
|
t.regex(container.className, /active/);
|
||||||
|
|
||||||
|
button.click(new Event('click'));
|
||||||
|
t.notRegex(container.className, /active/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Shows result on submit', async () => {
|
||||||
|
const { map } = new MapInstance();
|
||||||
|
|
||||||
|
const query = 'some city';
|
||||||
|
const result = [{ x: 0, y: 50 }];
|
||||||
|
|
||||||
|
const provider = { search: td.function() };
|
||||||
|
|
||||||
|
td.when(provider.search(query))
|
||||||
|
.thenReturn(Promise.resolve(result));
|
||||||
|
|
||||||
|
const control = new LeafletControl({
|
||||||
|
provider,
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
control.showResult = td.function();
|
||||||
|
await control.onSubmit('some city');
|
||||||
|
|
||||||
|
td.verify(control.showResult(result[0]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Change view on result', () => {
|
||||||
|
const { map } = new MapInstance({
|
||||||
|
center: [180, 180],
|
||||||
|
zoom: 18,
|
||||||
|
animateZoom: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
map.setView = td.function();
|
||||||
|
|
||||||
|
const control = new LeafletControl({
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
control.showResult({ x: 50, y: 0 });
|
||||||
|
|
||||||
|
td.verify(map.setView(
|
||||||
|
td.matchers.isA(L.LatLng),
|
||||||
|
td.matchers.isA(Number),
|
||||||
|
td.matchers.anything(),
|
||||||
|
));
|
||||||
|
});
|
||||||
29
src/__tests__/searchElement.spec.js
Normal file
29
src/__tests__/searchElement.spec.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import td from 'testdouble';
|
||||||
|
import SearchElement from '../searchElement';
|
||||||
|
|
||||||
|
test('Can localize texts', (t) => {
|
||||||
|
const searchLabel = 'Lookup address';
|
||||||
|
|
||||||
|
const control = new SearchElement({
|
||||||
|
searchLabel,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { input } = control.elements;
|
||||||
|
t.is(input.getAttribute('placeholder'), searchLabel);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('It will search when enter key is pressed', () => {
|
||||||
|
const handleSubmit = td.function();
|
||||||
|
const control = new SearchElement({
|
||||||
|
handleSubmit,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { input } = control.elements;
|
||||||
|
input.value = 'Nederland';
|
||||||
|
input.dispatchEvent(new KeyboardEvent('keypress', {
|
||||||
|
keyCode: 13,
|
||||||
|
}));
|
||||||
|
|
||||||
|
td.verify(handleSubmit({ query: 'Nederland' }));
|
||||||
|
});
|
||||||
2
src/constants.js
Normal file
2
src/constants.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export const ENTER_KEY = 13;
|
||||||
|
export const ESCAPE_KEY = 27;
|
||||||
39
src/domUtils.js
Normal file
39
src/domUtils.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/* eslint-disable import/prefer-default-export */
|
||||||
|
export const createElement = (element, classNames = '', parent = null) => {
|
||||||
|
const el = document.createElement(element);
|
||||||
|
el.className = classNames;
|
||||||
|
|
||||||
|
if (parent) {
|
||||||
|
parent.appendChild(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createScriptElement = (url, cb) => {
|
||||||
|
const script = createElement('script', null, document.body);
|
||||||
|
script.setAttribute('type', 'text/javascript');
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
window[cb] = (json) => {
|
||||||
|
script.remove();
|
||||||
|
delete window[cb];
|
||||||
|
resolve(json);
|
||||||
|
};
|
||||||
|
|
||||||
|
script.setAttribute('src', url);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addClassName = (element, className) => {
|
||||||
|
if (element && !element.classList.contains(className)) {
|
||||||
|
element.classList.add(className);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeClassName = (element, className) => {
|
||||||
|
if (element && element.classList.contains(className)) {
|
||||||
|
element.classList.remove(className);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
8
src/index.js
Normal file
8
src/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export { default as GeoSearchControl } from './leafletControl';
|
||||||
|
export { default as SearchElement } from './searchElement';
|
||||||
|
|
||||||
|
export { default as BingProvider } from './providers/bingProvider';
|
||||||
|
export { default as EsriProvider } from './providers/esriProvider';
|
||||||
|
export { default as GoogleProvider } from './providers/googleProvider';
|
||||||
|
export { default as OpenStreetMapProvider } from './providers/openStreetMapProvider';
|
||||||
|
export { default as Provider } from './providers/provider';
|
||||||
@@ -1,289 +0,0 @@
|
|||||||
/*
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to its location
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.GeoSearch = {};
|
|
||||||
L.GeoSearch.Provider = {};
|
|
||||||
|
|
||||||
L.GeoSearch.Result = function (x, y, label, bounds, details) {
|
|
||||||
this.X = x;
|
|
||||||
this.Y = y;
|
|
||||||
this.Label = label;
|
|
||||||
this.bounds = bounds;
|
|
||||||
|
|
||||||
if (details)
|
|
||||||
this.details = details;
|
|
||||||
};
|
|
||||||
|
|
||||||
L.Control.GeoSearch = L.Control.extend({
|
|
||||||
options: {
|
|
||||||
position: 'topleft',
|
|
||||||
showMarker: true,
|
|
||||||
showPopup: false,
|
|
||||||
customIcon: false,
|
|
||||||
retainZoomLevel: false,
|
|
||||||
draggable: false
|
|
||||||
},
|
|
||||||
|
|
||||||
_config: {
|
|
||||||
country: '',
|
|
||||||
searchLabel: 'Enter address',
|
|
||||||
notFoundMessage: 'Sorry, that address could not be found.',
|
|
||||||
messageHideDelay: 3000,
|
|
||||||
zoomLevel: 18
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function (options) {
|
|
||||||
L.Util.extend(this.options, options);
|
|
||||||
L.Util.extend(this._config, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
resetLink: function(extraClass) {
|
|
||||||
var link = this._container.querySelector('a');
|
|
||||||
link.className = 'leaflet-bar-part leaflet-bar-part-single' + ' ' + extraClass;
|
|
||||||
},
|
|
||||||
|
|
||||||
onAdd: function (map) {
|
|
||||||
this._container = L.DomUtil.create('div', 'leaflet-bar leaflet-control leaflet-control-geosearch');
|
|
||||||
|
|
||||||
// create the link - this will contain one of the icons
|
|
||||||
var link = L.DomUtil.create('a', '', this._container);
|
|
||||||
link.href = '#';
|
|
||||||
link.title = this._config.searchLabel;
|
|
||||||
|
|
||||||
// set the link's icon to magnifying glass
|
|
||||||
this.resetLink('glass');
|
|
||||||
|
|
||||||
// create the form that will contain the input
|
|
||||||
var form = L.DomUtil.create('form', 'displayNone', this._container);
|
|
||||||
|
|
||||||
// create the input, and set its placeholder text
|
|
||||||
var searchbox = L.DomUtil.create('input', null, form);
|
|
||||||
searchbox.type = 'text';
|
|
||||||
searchbox.placeholder = this._config.searchLabel;
|
|
||||||
this._searchbox = searchbox;
|
|
||||||
|
|
||||||
var msgbox = L.DomUtil.create('div', 'leaflet-bar message displayNone', this._container);
|
|
||||||
this._msgbox = msgbox;
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.on(link, 'click', L.DomEvent.stopPropagation)
|
|
||||||
.on(link, 'click', L.DomEvent.preventDefault)
|
|
||||||
.on(link, 'click', function() {
|
|
||||||
|
|
||||||
if (L.DomUtil.hasClass(form, 'displayNone')) {
|
|
||||||
L.DomUtil.removeClass(form, 'displayNone'); // unhide form
|
|
||||||
searchbox.focus();
|
|
||||||
} else {
|
|
||||||
L.DomUtil.addClass(form, 'displayNone'); // hide form
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
||||||
.on(link, 'dblclick', L.DomEvent.stopPropagation);
|
|
||||||
|
|
||||||
L.DomEvent
|
|
||||||
.addListener(this._searchbox, 'keypress', this._onKeyPress, this)
|
|
||||||
.addListener(this._searchbox, 'keyup', this._onKeyUp, this)
|
|
||||||
.addListener(this._searchbox, 'input', this._onInput, this);
|
|
||||||
|
|
||||||
L.DomEvent.disableClickPropagation(this._container);
|
|
||||||
|
|
||||||
return this._container;
|
|
||||||
},
|
|
||||||
|
|
||||||
geosearch: function (qry) {
|
|
||||||
var that = this;
|
|
||||||
try {
|
|
||||||
var provider = this._config.provider;
|
|
||||||
|
|
||||||
if(typeof provider.GetLocations == 'function') {
|
|
||||||
provider.GetLocations(qry, function(results) {
|
|
||||||
that._processResults(results, qry);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var url = provider.GetServiceUrl(qry);
|
|
||||||
this.sendRequest(provider, url, qry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
this._printError(error);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
cancelSearch: function() {
|
|
||||||
var form = this._container.querySelector('form');
|
|
||||||
L.DomUtil.addClass(form, 'displayNone');
|
|
||||||
|
|
||||||
this._searchbox.value = '';
|
|
||||||
this.resetLink('glass');
|
|
||||||
|
|
||||||
L.DomUtil.addClass(this._msgbox, 'displayNone');
|
|
||||||
|
|
||||||
this._map._container.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
startSearch: function() {
|
|
||||||
// show spinner icon
|
|
||||||
this.resetLink('spinner');
|
|
||||||
this.geosearch(this._searchbox.value);
|
|
||||||
},
|
|
||||||
|
|
||||||
sendRequest: function (provider, url, qry) {
|
|
||||||
var that = this;
|
|
||||||
|
|
||||||
window.parseLocation = function (response) {
|
|
||||||
var results = provider.ParseJSON(response);
|
|
||||||
that._processResults(results, qry);
|
|
||||||
|
|
||||||
document.body.removeChild(document.getElementById('getJsonP'));
|
|
||||||
delete window.parseLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
function getJsonP (url) {
|
|
||||||
url = url + '&callback=parseLocation';
|
|
||||||
var script = document.createElement('script');
|
|
||||||
script.id = 'getJsonP';
|
|
||||||
script.src = url;
|
|
||||||
script.async = true;
|
|
||||||
document.body.appendChild(script);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (XMLHttpRequest) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
if ('withCredentials' in xhr) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
|
||||||
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState == 4) {
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
var response = JSON.parse(xhr.responseText),
|
|
||||||
results = provider.ParseJSON(response);
|
|
||||||
|
|
||||||
that._processResults(results, qry);
|
|
||||||
} else if (xhr.status == 0 || xhr.status == 400) {
|
|
||||||
getJsonP(url);
|
|
||||||
} else {
|
|
||||||
that._printError(xhr.responseText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.open('GET', url, true);
|
|
||||||
xhr.send();
|
|
||||||
} else if (XDomainRequest) {
|
|
||||||
var xdr = new XDomainRequest();
|
|
||||||
|
|
||||||
xdr.onerror = function (err) {
|
|
||||||
that._printError(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
xdr.onload = function () {
|
|
||||||
var response = JSON.parse(xdr.responseText),
|
|
||||||
results = provider.ParseJSON(response);
|
|
||||||
|
|
||||||
that._processResults(results, qry);
|
|
||||||
};
|
|
||||||
|
|
||||||
xdr.open('GET', url);
|
|
||||||
xdr.send();
|
|
||||||
} else {
|
|
||||||
getJsonP(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_processResults: function(results, qry) {
|
|
||||||
if (results.length > 0) {
|
|
||||||
this._map.fireEvent('geosearch_foundlocations', {Locations: results});
|
|
||||||
this._showLocation(results[0], qry);
|
|
||||||
this.cancelSearch();
|
|
||||||
} else {
|
|
||||||
this._printError(this._config.notFoundMessage);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_showLocation: function (location, qry) {
|
|
||||||
if (this.options.showMarker == true) {
|
|
||||||
if (typeof this._positionMarker === 'undefined') {
|
|
||||||
this._positionMarker = L.marker(
|
|
||||||
[location.Y, location.X],
|
|
||||||
{draggable: this.options.draggable}
|
|
||||||
).addTo(this._map);
|
|
||||||
if( this.options.customIcon ) {
|
|
||||||
this._positionMarker.setIcon(this.options.customIcon);
|
|
||||||
}
|
|
||||||
if( this.options.showPopup ) {
|
|
||||||
this._positionMarker.bindPopup(qry).openPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._positionMarker.setLatLng([location.Y, location.X]);
|
|
||||||
if( this.options.showPopup ) {
|
|
||||||
this._positionMarker.bindPopup(qry).openPopup();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!this.options.retainZoomLevel && location.bounds && location.bounds.isValid()) {
|
|
||||||
this._map.fitBounds(location.bounds);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._map.setView([location.Y, location.X], this._getZoomLevel(), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._map.fireEvent('geosearch_showlocation', {
|
|
||||||
Location: location,
|
|
||||||
Marker : this._positionMarker
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
_isShowingError: false,
|
|
||||||
|
|
||||||
_printError: function(message) {
|
|
||||||
this._msgbox.innerHTML = message;
|
|
||||||
L.DomUtil.removeClass(this._msgbox, 'displayNone');
|
|
||||||
|
|
||||||
this._map.fireEvent('geosearch_error', {message: message});
|
|
||||||
|
|
||||||
// show alert icon
|
|
||||||
this.resetLink('alert');
|
|
||||||
this._isShowingError = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyUp: function (e) {
|
|
||||||
var esc = 27;
|
|
||||||
|
|
||||||
if (e.keyCode === esc) { // escape key detection is unreliable
|
|
||||||
this.cancelSearch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_getZoomLevel: function() {
|
|
||||||
if (! this.options.retainZoomLevel) {
|
|
||||||
return this._config.zoomLevel;
|
|
||||||
}
|
|
||||||
return this._map._zoom;
|
|
||||||
},
|
|
||||||
|
|
||||||
_onInput: function() {
|
|
||||||
if (this._isShowingError) {
|
|
||||||
this.resetLink('glass');
|
|
||||||
L.DomUtil.addClass(this._msgbox, 'displayNone');
|
|
||||||
|
|
||||||
this._isShowingError = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_onKeyPress: function (e) {
|
|
||||||
var enterKey = 13;
|
|
||||||
|
|
||||||
if (e.keyCode === enterKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
this.startSearch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
||||||
* L.GeoSearch.Provider.Bing uses bing geocoding service
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.GeoSearch.Provider.Bing = L.Class.extend({
|
|
||||||
options: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
options = L.Util.setOptions(this, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
GetServiceUrl: function (qry) {
|
|
||||||
var parameters = L.Util.extend({
|
|
||||||
query: qry,
|
|
||||||
jsonp: 'parseLocation'
|
|
||||||
}, this.options);
|
|
||||||
|
|
||||||
return 'http://dev.virtualearth.net/REST/v1/Locations'
|
|
||||||
+ L.Util.getParamString(parameters);
|
|
||||||
},
|
|
||||||
|
|
||||||
ParseJSON: function (data) {
|
|
||||||
if (data.resourceSets.length == 0 || data.resourceSets[0].resources.length == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var results = [];
|
|
||||||
for (var i = 0; i < data.resourceSets[0].resources.length; i++)
|
|
||||||
results.push(new L.GeoSearch.Result(
|
|
||||||
data.resourceSets[0].resources[i].point.coordinates[1],
|
|
||||||
data.resourceSets[0].resources[i].point.coordinates[0],
|
|
||||||
data.resourceSets[0].resources[i].address.formattedAddress
|
|
||||||
));
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
||||||
* L.GeoSearch.Provider.Esri uses arcgis geocoding service
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.GeoSearch.Provider.Esri = L.Class.extend({
|
|
||||||
options: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
options = L.Util.setOptions(this, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
GetServiceUrl: function (qry) {
|
|
||||||
var parameters = L.Util.extend({
|
|
||||||
text: qry,
|
|
||||||
f: 'pjson'
|
|
||||||
}, this.options);
|
|
||||||
|
|
||||||
return (location.protocol === 'https:' ? 'https:' : 'http:')
|
|
||||||
+ '//geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find'
|
|
||||||
+ L.Util.getParamString(parameters);
|
|
||||||
},
|
|
||||||
|
|
||||||
ParseJSON: function (data) {
|
|
||||||
if (data.locations.length == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var results = [];
|
|
||||||
for (var i = 0; i < data.locations.length; i++)
|
|
||||||
results.push(new L.GeoSearch.Result(
|
|
||||||
data.locations[i].feature.geometry.x,
|
|
||||||
data.locations[i].feature.geometry.y,
|
|
||||||
data.locations[i].name
|
|
||||||
));
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
/**
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
||||||
* L.GeoSearch.Provider.Google uses google geocoding service
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
onLoadGoogleApiCallback = function() {
|
|
||||||
L.GeoSearch.Provider.Google.Geocoder = new google.maps.Geocoder();
|
|
||||||
var scriptNode = document.getElementById('load_google_api');
|
|
||||||
if (!!scriptNode) {
|
|
||||||
document.body.removeChild(scriptNode);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
L.GeoSearch.Provider.Google = L.Class.extend({
|
|
||||||
_isReady: false,
|
|
||||||
_onReadyQueue: [],
|
|
||||||
|
|
||||||
options: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
options = L.Util.setOptions(this, options);
|
|
||||||
if (!window.google || !window.google.maps) {
|
|
||||||
this.loadMapsApi();
|
|
||||||
} else {
|
|
||||||
// if google is already loaded, make sure we initialize the Geocoder
|
|
||||||
onLoadGoogleApiCallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
loadMapsApi: function () {
|
|
||||||
var self = this;
|
|
||||||
var url = "https://maps.googleapis.com/maps/api/js?v=3&callback=onLoadGoogleApiCallback&sensor=false";
|
|
||||||
var script = document.createElement('script');
|
|
||||||
script.id = 'load_google_api';
|
|
||||||
script.type = "text/javascript";
|
|
||||||
script.src = url;
|
|
||||||
document.body.appendChild(script);
|
|
||||||
|
|
||||||
var handle = setInterval(function() {
|
|
||||||
if (typeof google !== 'undefined' && L.GeoSearch.Provider.Google.Geocoder instanceof google.maps.Geocoder) {
|
|
||||||
clearInterval(handle);
|
|
||||||
self._onReady();
|
|
||||||
}
|
|
||||||
}, 25);
|
|
||||||
},
|
|
||||||
|
|
||||||
_onReady: function() {
|
|
||||||
this._isReady = true;
|
|
||||||
|
|
||||||
var data;
|
|
||||||
while (data = this._onReadyQueue.shift()) {
|
|
||||||
this.GetLocations(data.qry, data.callback);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
GetLocations: function(qry, callback) {
|
|
||||||
if (!this._isReady) {
|
|
||||||
// store calls to this method, so the can be re-invoked once
|
|
||||||
// the google api is loaded
|
|
||||||
this._onReadyQueue.push({ qry: qry, callback: callback });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var geocoder = L.GeoSearch.Provider.Google.Geocoder;
|
|
||||||
|
|
||||||
var parameters = L.Util.extend({
|
|
||||||
address: qry
|
|
||||||
}, this.options);
|
|
||||||
|
|
||||||
var results = geocoder.geocode(parameters, function(data){
|
|
||||||
data = {results: data};
|
|
||||||
|
|
||||||
var results = [],
|
|
||||||
northEastLatLng,
|
|
||||||
southWestLatLng,
|
|
||||||
bounds;
|
|
||||||
for (var i = 0; i < data.results.length; i++) {
|
|
||||||
|
|
||||||
if( data.results[i].geometry.bounds ) {
|
|
||||||
var northEastGoogle = data.results[i].geometry.bounds.getNorthEast(),
|
|
||||||
southWestGoogle = data.results[i].geometry.bounds.getSouthWest();
|
|
||||||
|
|
||||||
northEastLatLng = new L.LatLng( northEastGoogle.lat(), northEastGoogle.lng() );
|
|
||||||
southWestLatLng = new L.LatLng( southWestGoogle.lat(), southWestGoogle.lng() );
|
|
||||||
bounds = new L.LatLngBounds([ northEastLatLng, southWestLatLng ]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
bounds = undefined;
|
|
||||||
}
|
|
||||||
results.push(new L.GeoSearch.Result(
|
|
||||||
data.results[i].geometry.location.lng(),
|
|
||||||
data.results[i].geometry.location.lat(),
|
|
||||||
data.results[i].formatted_address,
|
|
||||||
bounds
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(typeof callback == 'function')
|
|
||||||
callback(results);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
||||||
* L.GeoSearch.Provider.Nokia uses Nokia geocoding service
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.GeoSearch.Provider.Nokia = L.Class.extend({
|
|
||||||
options: {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
options = L.Util.setOptions(this, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
GetServiceUrl: function (qry) {
|
|
||||||
var parameters = L.Util.extend({
|
|
||||||
searchtext: qry,
|
|
||||||
jsoncallback: 'parseLocation'
|
|
||||||
}, this.options);
|
|
||||||
|
|
||||||
return 'http://geo.nlp.nokia.com/search/6.2/geocode.json'
|
|
||||||
+ L.Util.getParamString(parameters);
|
|
||||||
},
|
|
||||||
|
|
||||||
ParseJSON: function (data) {
|
|
||||||
if (data.Response.View.length == 0 || data.Response.View[0].Result.length == 0)
|
|
||||||
return [];
|
|
||||||
|
|
||||||
var results = [];
|
|
||||||
for (var i = 0; i < data.Response.View[0].Result.length; i++)
|
|
||||||
results.push(new L.GeoSearch.Result(
|
|
||||||
data.Response.View[0].Result[i].Location.DisplayPosition.Longitude,
|
|
||||||
data.Response.View[0].Result[i].Location.DisplayPosition.Latitude,
|
|
||||||
data.Response.View[0].Result[i].Location.Address.Label
|
|
||||||
));
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/**
|
|
||||||
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
||||||
* L.GeoSearch.Provider.OpenStreetMap uses openstreetmap geocoding service
|
|
||||||
* https://github.com/smeijer/L.GeoSearch
|
|
||||||
*/
|
|
||||||
|
|
||||||
L.GeoSearch.Provider.OpenStreetMap = L.Class.extend({
|
|
||||||
options: {},
|
|
||||||
|
|
||||||
initialize: function(options) {
|
|
||||||
options = L.Util.setOptions(this, options);
|
|
||||||
},
|
|
||||||
|
|
||||||
GetServiceUrl: function (qry) {
|
|
||||||
var parameters = L.Util.extend({
|
|
||||||
q: qry,
|
|
||||||
format: 'json'
|
|
||||||
}, this.options);
|
|
||||||
|
|
||||||
return (location.protocol === 'https:' ? 'https:' : 'http:')
|
|
||||||
+ '//nominatim.openstreetmap.org/search'
|
|
||||||
+ L.Util.getParamString(parameters);
|
|
||||||
},
|
|
||||||
|
|
||||||
ParseJSON: function (data) {
|
|
||||||
var results = [];
|
|
||||||
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
var boundingBox = data[i].boundingbox,
|
|
||||||
northEastLatLng = new L.LatLng( boundingBox[1], boundingBox[3] ),
|
|
||||||
southWestLatLng = new L.LatLng( boundingBox[0], boundingBox[2] );
|
|
||||||
|
|
||||||
if (data[i].address)
|
|
||||||
data[i].address.type = data[i].type;
|
|
||||||
|
|
||||||
results.push(new L.GeoSearch.Result(
|
|
||||||
data[i].lon,
|
|
||||||
data[i].lat,
|
|
||||||
data[i].display_name,
|
|
||||||
new L.LatLngBounds([
|
|
||||||
northEastLatLng,
|
|
||||||
southWestLatLng
|
|
||||||
]),
|
|
||||||
data[i].address
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
139
src/leafletControl.js
Normal file
139
src/leafletControl.js
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import GeoSearchElement from './searchElement';
|
||||||
|
import { createElement, addClassName, removeClassName } from './domUtils';
|
||||||
|
|
||||||
|
const defaultOptions = {
|
||||||
|
position: 'topleft',
|
||||||
|
showMarker: true,
|
||||||
|
showPopup: false,
|
||||||
|
marker: {
|
||||||
|
icon: new L.Icon.Default(),
|
||||||
|
draggable: false,
|
||||||
|
},
|
||||||
|
maxMarkers: 1,
|
||||||
|
retainZoomLevel: false,
|
||||||
|
animateZoom: true,
|
||||||
|
country: '',
|
||||||
|
searchLabel: 'Enter address',
|
||||||
|
notFoundMessage: 'Sorry, that address could not be found.',
|
||||||
|
messageHideDelay: 3000,
|
||||||
|
zoomLevel: 18,
|
||||||
|
classNames: {
|
||||||
|
container: 'leaflet-bar leaflet-control leaflet-control-geosearch',
|
||||||
|
button: 'leaflet-bar-part leaflet-bar-part-single',
|
||||||
|
msgbox: 'leaflet-bar message',
|
||||||
|
form: '',
|
||||||
|
input: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default L.Control.extend({
|
||||||
|
initialize(options) {
|
||||||
|
this.markers = new L.FeatureGroup();
|
||||||
|
|
||||||
|
this.options = {
|
||||||
|
...defaultOptions,
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { classNames, searchLabel } = this.options;
|
||||||
|
|
||||||
|
this.searchElement = new GeoSearchElement({
|
||||||
|
...this.options,
|
||||||
|
handleSubmit: query => this.onSubmit(query),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { container } = this.searchElement.elements;
|
||||||
|
container.addEventListener('dblclick', (e) => { e.stopPropagation(); });
|
||||||
|
|
||||||
|
const button = createElement('a', classNames.button, container);
|
||||||
|
button.title = searchLabel;
|
||||||
|
button.href = '#';
|
||||||
|
|
||||||
|
button.addEventListener('click', (e) => { this.onClick(e); }, false);
|
||||||
|
|
||||||
|
this.elements = { button };
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd(map) {
|
||||||
|
const { showMarker } = this.options;
|
||||||
|
|
||||||
|
this.map = map;
|
||||||
|
if (showMarker) {
|
||||||
|
this.markers.addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.searchElement.elements.container;
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { container, input } = this.searchElement.elements;
|
||||||
|
|
||||||
|
if (container.classList.contains('active')) {
|
||||||
|
removeClassName(container, 'active');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addClassName(container, 'active');
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async onSubmit(query) {
|
||||||
|
const { provider } = this.options;
|
||||||
|
|
||||||
|
const results = await provider.search(query);
|
||||||
|
|
||||||
|
if (results && results.length > 0) {
|
||||||
|
this.showResult(results[0]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showResult(result) {
|
||||||
|
const markers = Object.keys(this.markers._layers);
|
||||||
|
if (markers.length >= this.options.maxMarkers) {
|
||||||
|
this.markers.removeLayer(markers[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const marker = this.addMarker(result);
|
||||||
|
this.centerMap(result);
|
||||||
|
|
||||||
|
this.map.fireEvent('geosearch/showlocation', {
|
||||||
|
location: result,
|
||||||
|
marker,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addMarker(result) {
|
||||||
|
const { marker: options, showPopup } = this.options;
|
||||||
|
const marker = new L.Marker([result.y, result.x], options);
|
||||||
|
marker.bindPopup(result.label);
|
||||||
|
|
||||||
|
this.markers.addLayer(marker);
|
||||||
|
|
||||||
|
if (showPopup) {
|
||||||
|
marker.openPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
|
},
|
||||||
|
|
||||||
|
centerMap(result) {
|
||||||
|
const { retainZoomLevel, animateZoom } = this.options;
|
||||||
|
|
||||||
|
const resultBounds = new L.LatLngBounds(result.bounds);
|
||||||
|
const bounds = resultBounds.isValid() ? resultBounds : this.markers.getBounds();
|
||||||
|
|
||||||
|
if (!retainZoomLevel && resultBounds.isValid()) {
|
||||||
|
this.map.fitBounds(bounds, { animate: animateZoom });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.map.setView(bounds.getCenter(), this.getZoom(), { animate: animateZoom });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getZoom() {
|
||||||
|
const { retainZoomLevel, zoomLevel } = this.options;
|
||||||
|
return retainZoomLevel ? this.map.getZoom() : zoomLevel;
|
||||||
|
},
|
||||||
|
});
|
||||||
33
src/providers/__tests__/bingProvider.spec.js
Normal file
33
src/providers/__tests__/bingProvider.spec.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import Provider from '../bingProvider';
|
||||||
|
|
||||||
|
test.skip('Can fetch results with Bing Provider', async (t) => {
|
||||||
|
const provider = new Provider({
|
||||||
|
params: {
|
||||||
|
key: process.env.BING_API_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'nederland' });
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
t.truthy(result.label);
|
||||||
|
t.true(result.x > 5 && result.x < 6);
|
||||||
|
t.true(result.y > 50 && result.y < 55);
|
||||||
|
t.true(result.bounds[0][0] > result.bounds[0][1]);
|
||||||
|
t.true(result.bounds[1][0] > result.bounds[1][1]);
|
||||||
|
t.true(result.bounds[0][0] < result.bounds[1][0]);
|
||||||
|
t.true(result.bounds[0][1] < result.bounds[1][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skip('Can get localized results', async (t) => {
|
||||||
|
const provider = new Provider({
|
||||||
|
params: {
|
||||||
|
key: process.env.BING_API_KEY,
|
||||||
|
c: 'nl',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'leeuwarden' });
|
||||||
|
t.is(results[0].label, 'Leeuwarden, Nederland');
|
||||||
|
});
|
||||||
17
src/providers/__tests__/esriProvider.spec.js
Normal file
17
src/providers/__tests__/esriProvider.spec.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import Provider from '../esriProvider';
|
||||||
|
|
||||||
|
test('Can fetch results with Esri Provider', async (t) => {
|
||||||
|
const provider = new Provider();
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'nederland' });
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
t.truthy(result.label);
|
||||||
|
t.true(result.x > 5 && result.x < 6);
|
||||||
|
t.true(result.y > 50 && result.y < 55);
|
||||||
|
t.true(result.bounds[0][0] > result.bounds[0][1]);
|
||||||
|
t.true(result.bounds[1][0] > result.bounds[1][1]);
|
||||||
|
t.true(result.bounds[0][0] < result.bounds[1][0]);
|
||||||
|
t.true(result.bounds[0][1] < result.bounds[1][1]);
|
||||||
|
});
|
||||||
41
src/providers/__tests__/googleProvider.spec.js
Normal file
41
src/providers/__tests__/googleProvider.spec.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import Provider from '../googleProvider';
|
||||||
|
|
||||||
|
test('Can fetch results with Google Provider', async (t) => {
|
||||||
|
const provider = new Provider();
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'netherlands' });
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
t.truthy(result.label);
|
||||||
|
t.true(result.x > 5 && result.x < 6);
|
||||||
|
t.true(result.y > 50 && result.y < 55);
|
||||||
|
t.true(result.bounds[0][0] > result.bounds[0][1]);
|
||||||
|
t.true(result.bounds[1][0] > result.bounds[1][1]);
|
||||||
|
t.true(result.bounds[0][0] < result.bounds[1][0]);
|
||||||
|
t.true(result.bounds[0][1] < result.bounds[1][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can get localized results', async (t) => {
|
||||||
|
const provider = new Provider({
|
||||||
|
params: {
|
||||||
|
language: 'nl',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'leeuwarden' });
|
||||||
|
t.is(results[0].label, 'Leeuwarden, Nederland');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can fetch results with API Key', async (t) => {
|
||||||
|
const provider = new Provider({
|
||||||
|
params: {
|
||||||
|
key: process.env.GOOGLE_API_KEY,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'nederland' });
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
t.truthy(result.label);
|
||||||
|
});
|
||||||
28
src/providers/__tests__/openstreetmapProvider.spec.js
Normal file
28
src/providers/__tests__/openstreetmapProvider.spec.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import test from 'ava';
|
||||||
|
import Provider from '../openStreetMapProvider';
|
||||||
|
|
||||||
|
test('Can fetch results with OpenStreetMap', async (t) => {
|
||||||
|
const provider = new Provider();
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'nederland' });
|
||||||
|
const result = results[0];
|
||||||
|
|
||||||
|
t.truthy(result.label);
|
||||||
|
t.true(result.x > 5 && result.x < 6);
|
||||||
|
t.true(result.y > 50 && result.y < 55);
|
||||||
|
t.true(result.bounds[0][0] > result.bounds[0][1]);
|
||||||
|
t.true(result.bounds[1][0] > result.bounds[1][1]);
|
||||||
|
t.true(result.bounds[0][0] < result.bounds[1][0]);
|
||||||
|
t.true(result.bounds[0][1] < result.bounds[1][1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Can get localized results', async (t) => {
|
||||||
|
const provider = new Provider({
|
||||||
|
params: {
|
||||||
|
'accept-language': 'nl',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const results = await provider.search({ query: 'nederland' });
|
||||||
|
t.is(results[0].label, 'Nederland, Koninkrijk der Nederlanden');
|
||||||
|
});
|
||||||
42
src/providers/bingProvider.js
Normal file
42
src/providers/bingProvider.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import BaseProvider from './provider';
|
||||||
|
import { createScriptElement } from '../domUtils';
|
||||||
|
|
||||||
|
export default class Provider extends BaseProvider {
|
||||||
|
endpoint({ query, protocol, jsonp } = {}) {
|
||||||
|
const { params } = this.options;
|
||||||
|
|
||||||
|
const paramString = this.getParamString({
|
||||||
|
...params,
|
||||||
|
query,
|
||||||
|
jsonp,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${protocol}//dev.virtualearth.net/REST/v1/Locations?${paramString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse({ data }) {
|
||||||
|
if (data.resourceSets.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return (data.resourceSets[0].resources).map(r => ({
|
||||||
|
x: r.point.coordinates[1],
|
||||||
|
y: r.point.coordinates[0],
|
||||||
|
label: r.address.formattedAddress,
|
||||||
|
bounds: [
|
||||||
|
[r.bbox[0], r.bbox[1]], // s, w
|
||||||
|
[r.bbox[2], r.bbox[3]], // n, e
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query }) {
|
||||||
|
const protocol = location.protocol === 'https:' ? 'https:' : 'http:';
|
||||||
|
|
||||||
|
const jsonp = `BING_JSONP_CB_${Date.now()}`;
|
||||||
|
const url = this.endpoint({ query, protocol, jsonp });
|
||||||
|
|
||||||
|
const json = await createScriptElement(url, jsonp);
|
||||||
|
return this.parse({ data: json });
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/providers/esriProvider.js
Normal file
27
src/providers/esriProvider.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import BaseProvider from './provider';
|
||||||
|
|
||||||
|
export default class Provider extends BaseProvider {
|
||||||
|
endpoint({ query, protocol } = {}) {
|
||||||
|
const { params } = this.options;
|
||||||
|
|
||||||
|
const paramString = this.getParamString({
|
||||||
|
...params,
|
||||||
|
f: 'json',
|
||||||
|
text: query,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${protocol}//geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/find?${paramString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse({ data }) {
|
||||||
|
return data.locations.map(r => ({
|
||||||
|
x: r.feature.geometry.x,
|
||||||
|
y: r.feature.geometry.y,
|
||||||
|
label: r.name,
|
||||||
|
bounds: [
|
||||||
|
[r.extent.ymin, r.extent.xmin], // s, w
|
||||||
|
[r.extent.ymax, r.extent.xmax], // n, e
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/providers/googleProvider.js
Normal file
28
src/providers/googleProvider.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import BaseProvider from './provider';
|
||||||
|
|
||||||
|
export default class Provider extends BaseProvider {
|
||||||
|
endpoint({ query, protocol } = {}) {
|
||||||
|
const { params } = this.options;
|
||||||
|
|
||||||
|
const paramString = this.getParamString({
|
||||||
|
...params,
|
||||||
|
address: query,
|
||||||
|
});
|
||||||
|
|
||||||
|
// google requires a secure connection when using api keys
|
||||||
|
const proto = (params && params.key) ? 'https:' : protocol;
|
||||||
|
return `${proto}//maps.googleapis.com/maps/api/geocode/json?${paramString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse({ data }) {
|
||||||
|
return data.results.map(r => ({
|
||||||
|
x: r.geometry.location.lng,
|
||||||
|
y: r.geometry.location.lat,
|
||||||
|
label: r.formatted_address,
|
||||||
|
bounds: [
|
||||||
|
[r.geometry.viewport.southwest.lat, r.geometry.viewport.southwest.lng], // s, w
|
||||||
|
[r.geometry.viewport.northeast.lat, r.geometry.viewport.northeast.lng], // n, e
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/providers/index.js
Normal file
5
src/providers/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export { default as BingProvider } from './bingProvider';
|
||||||
|
export { default as EsriProvider } from './esriProvider';
|
||||||
|
export { default as GoogleProvider } from './googleProvider';
|
||||||
|
export { default as OpenStreetMapProvider } from './openStreetMapProvider';
|
||||||
|
export { default as Provider } from './provider';
|
||||||
27
src/providers/openStreetMapProvider.js
Normal file
27
src/providers/openStreetMapProvider.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import BaseProvider from './provider';
|
||||||
|
|
||||||
|
export default class Provider extends BaseProvider {
|
||||||
|
endpoint({ query, protocol } = {}) {
|
||||||
|
const { params } = this.options;
|
||||||
|
|
||||||
|
const paramString = this.getParamString({
|
||||||
|
...params,
|
||||||
|
format: 'json',
|
||||||
|
q: query,
|
||||||
|
});
|
||||||
|
|
||||||
|
return `${protocol}//nominatim.openstreetmap.org/search?${paramString}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse({ data }) {
|
||||||
|
return data.map(r => ({
|
||||||
|
x: r.lon,
|
||||||
|
y: r.lat,
|
||||||
|
label: r.display_name,
|
||||||
|
bounds: [
|
||||||
|
[parseFloat(r.boundingbox[0]), parseFloat(r.boundingbox[2])], // s, w
|
||||||
|
[parseFloat(r.boundingbox[1]), parseFloat(r.boundingbox[3])], // n, e
|
||||||
|
],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/providers/provider.js
Normal file
20
src/providers/provider.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
export default class Provider {
|
||||||
|
constructor(options = {}) {
|
||||||
|
this.options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
getParamString(params) {
|
||||||
|
return Object.keys(params).map(key =>
|
||||||
|
`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`,
|
||||||
|
).join('&');
|
||||||
|
}
|
||||||
|
|
||||||
|
async search({ query }) {
|
||||||
|
const protocol = location.protocol === 'https:' ? 'https:' : 'http:';
|
||||||
|
const url = this.endpoint({ query, protocol });
|
||||||
|
|
||||||
|
const request = await fetch(url);
|
||||||
|
const json = await request.json();
|
||||||
|
return this.parse({ data: json });
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/searchElement.js
Normal file
71
src/searchElement.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { createElement, addClassName, removeClassName } from './domUtils';
|
||||||
|
import { ESCAPE_KEY, ENTER_KEY } from './constants';
|
||||||
|
|
||||||
|
export default class SearchElement {
|
||||||
|
constructor({ handleSubmit = () => {}, searchLabel = 'search', classNames = {} }) {
|
||||||
|
const container = createElement('div', ['geosearch', classNames.container].join(' '));
|
||||||
|
const form = createElement('form', ['', classNames.form].join(' '), container);
|
||||||
|
const input = createElement('input', ['glass', classNames.input].join(' '), form);
|
||||||
|
const msgbox = createElement('div', ['', classNames.msgbox].join(' '), container);
|
||||||
|
|
||||||
|
input.type = 'text';
|
||||||
|
input.placeholder = searchLabel;
|
||||||
|
|
||||||
|
input.addEventListener('input', (e) => { this.onInput(e); }, false);
|
||||||
|
input.addEventListener('keyup', (e) => { this.onKeyUp(e); }, false);
|
||||||
|
input.addEventListener('keypress', (e) => { this.onKeyPress(e); }, false);
|
||||||
|
input.addEventListener('focus', (e) => { this.onFocus(e); }, false);
|
||||||
|
input.addEventListener('blur', (e) => { this.onBlur(e); }, false);
|
||||||
|
|
||||||
|
this.elements = { container, form, input, msgbox };
|
||||||
|
this.handleSubmit = handleSubmit;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFocus() {
|
||||||
|
addClassName(this.elements.form, 'active');
|
||||||
|
}
|
||||||
|
|
||||||
|
onBlur() {
|
||||||
|
removeClassName(this.elements.form, 'active');
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSubmit(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const { input, container } = this.elements;
|
||||||
|
addClassName(container, 'pending');
|
||||||
|
|
||||||
|
await this.handleSubmit({ query: input.value });
|
||||||
|
removeClassName(container, 'pending');
|
||||||
|
}
|
||||||
|
|
||||||
|
onInput() {
|
||||||
|
const { container } = this.elements;
|
||||||
|
|
||||||
|
if (this.hasError) {
|
||||||
|
removeClassName(container, 'error');
|
||||||
|
this.hasError = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyUp(event) {
|
||||||
|
const { container, input } = this.elements;
|
||||||
|
|
||||||
|
if (event.keyCode === ESCAPE_KEY) {
|
||||||
|
removeClassName(container, 'pending');
|
||||||
|
removeClassName(container, 'active');
|
||||||
|
|
||||||
|
input.value = '';
|
||||||
|
|
||||||
|
document.body.focus();
|
||||||
|
document.body.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyPress(event) {
|
||||||
|
if (event.keyCode === ENTER_KEY) {
|
||||||
|
this.onSubmit(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
test/browserEnv.js
Normal file
11
test/browserEnv.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
/* eslint-disable import/newline-after-import, no-undef */
|
||||||
|
const browserEnv = require('browser-env');
|
||||||
|
browserEnv();
|
||||||
|
|
||||||
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
window.fetch = global.fetch = fetch;
|
||||||
|
|
||||||
|
const leaflet = require('leaflet');
|
||||||
|
window.L = global.L = leaflet;
|
||||||
2
test/index.js
Normal file
2
test/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { default as id } from './randomId';
|
||||||
|
export { default as round } from './round';
|
||||||
3
test/randomId.js
Normal file
3
test/randomId.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
/* eslint-disable no-bitwise, prefer-template */
|
||||||
|
export default () => ('0000' + (Math.random() * Math.pow(36, 4) << 0)
|
||||||
|
.toString(36)).slice(-4);
|
||||||
9
test/round.js
Normal file
9
test/round.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const round = (num) => {
|
||||||
|
if (Array.isArray(num)) {
|
||||||
|
return num.map(round);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.round(num * 10) / 10;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default round;
|
||||||
36
wallaby.js
Normal file
36
wallaby.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* eslint-disable no-unused-vars, global-require, import/no-extraneous-dependencies */
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
process.env.BABEL_ENV = 'test';
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
|
||||||
|
module.exports = wallaby => ({
|
||||||
|
files: [
|
||||||
|
'src/**/*.js',
|
||||||
|
'test/**/*.js',
|
||||||
|
'!src/**/__tests__/**/*.spec.js',
|
||||||
|
],
|
||||||
|
tests: [
|
||||||
|
'src/**/__tests__/**/*.spec.js',
|
||||||
|
],
|
||||||
|
compilers: {
|
||||||
|
'**/*.js': wallaby.compilers.babel(),
|
||||||
|
},
|
||||||
|
env: {
|
||||||
|
type: 'node',
|
||||||
|
params: {
|
||||||
|
env: [
|
||||||
|
'NODE_ENV=production',
|
||||||
|
].join(';'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
debug: false,
|
||||||
|
testFramework: 'ava',
|
||||||
|
setup: () => {
|
||||||
|
require('dotenv').config({
|
||||||
|
path: `${wallaby.localProjectDir}/.env`,
|
||||||
|
});
|
||||||
|
|
||||||
|
require('./test/browserEnv');
|
||||||
|
},
|
||||||
|
});
|
||||||
49
webpack.config.babel.js
Normal file
49
webpack.config.babel.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import webpack from 'webpack';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
const { NODE_ENV } = process.env;
|
||||||
|
const production = NODE_ENV === 'production';
|
||||||
|
|
||||||
|
const plugins = [
|
||||||
|
new webpack.optimize.OccurenceOrderPlugin(),
|
||||||
|
new webpack.DefinePlugin({
|
||||||
|
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (production) {
|
||||||
|
plugins.push(
|
||||||
|
new webpack.optimize.UglifyJsPlugin({
|
||||||
|
compressor: {
|
||||||
|
pure_getters: true,
|
||||||
|
unsafe: true,
|
||||||
|
unsafe_comps: true,
|
||||||
|
screw_ie8: true,
|
||||||
|
warnings: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
entry: [
|
||||||
|
'babel-polyfill',
|
||||||
|
path.join(__dirname, 'src/index.js'),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'dist'),
|
||||||
|
filename: `bundle${production ? '.min' : ''}.js`,
|
||||||
|
library: 'GeoSearch',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
plugins,
|
||||||
|
};
|
||||||
34
webpack.config.js
Normal file
34
webpack.config.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const webpack = require('webpack');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: [
|
||||||
|
'babel-polyfill',
|
||||||
|
path.resolve(__dirname, 'example/main.js'),
|
||||||
|
path.resolve(__dirname, 'src/index.js'),
|
||||||
|
],
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'bundle.js',
|
||||||
|
library: 'GeoSearch',
|
||||||
|
libraryTarget: 'umd',
|
||||||
|
publicPath: 'dist',
|
||||||
|
},
|
||||||
|
devServer: {
|
||||||
|
// contentBase: './example',
|
||||||
|
inline: true,
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
loaders: [
|
||||||
|
{
|
||||||
|
test: /\.html|\.css$/,
|
||||||
|
loader: 'raw-loader',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.js$/,
|
||||||
|
loaders: ['babel-loader'],
|
||||||
|
exclude: /node_modules/,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user