leaflet-control-geocoder
Version:
Extendable geocoder with builtin support for Nominatim, Bing, Google, Mapbox, Photon, What3Words, MapQuest, Mapzen
231 lines (195 loc) • 6.51 kB
JavaScript
var L = require('leaflet'),
Nominatim = require('./geocoders/nominatim').class;
module.exports = {
class: L.Control.extend({
options: {
showResultIcons: false,
collapsed: true,
expand: 'click',
position: 'topright',
placeholder: 'Search...',
errorMessage: 'Nothing found.'
},
_callbackId: 0,
initialize: function (options) {
L.Util.setOptions(this, options);
if (!this.options.geocoder) {
this.options.geocoder = new Nominatim();
}
},
onAdd: function (map) {
var className = 'leaflet-control-geocoder',
container = L.DomUtil.create('div', className + ' leaflet-bar'),
icon = L.DomUtil.create('a', 'leaflet-control-geocoder-icon', container),
form = this._form = L.DomUtil.create('form', className + '-form', container),
input;
icon.innerHTML = ' ';
icon.href = 'javascript:void(0);';
this._map = map;
this._container = container;
input = this._input = L.DomUtil.create('input');
input.type = 'text';
input.placeholder = this.options.placeholder;
L.DomEvent.addListener(input, 'keydown', this._keydown, this);
//L.DomEvent.addListener(input, 'onpaste', this._clearResults, this);
//L.DomEvent.addListener(input, 'oninput', this._clearResults, this);
this._errorElement = document.createElement('div');
this._errorElement.className = className + '-form-no-error';
this._errorElement.innerHTML = this.options.errorMessage;
this._alts = L.DomUtil.create('ul', className + '-alternatives leaflet-control-geocoder-alternatives-minimized');
form.appendChild(input);
this._container.appendChild(this._errorElement);
container.appendChild(this._alts);
L.DomEvent.addListener(form, 'submit', this._geocode, this);
if (this.options.collapsed) {
if (this.options.expand === 'click') {
L.DomEvent.addListener(icon, 'click', function(e) {
// TODO: touch
if (e.button === 0 && e.detail !== 2) {
this._toggle();
}
}, this);
} else {
L.DomEvent.addListener(icon, 'mouseover', this._expand, this);
L.DomEvent.addListener(icon, 'mouseout', this._collapse, this);
this._map.on('movestart', this._collapse, this);
}
} else {
L.DomEvent.addListener(icon, 'click', function(e) {
this._geocode(e);
}, this);
this._expand();
}
L.DomEvent.disableClickPropagation(container);
return container;
},
_geocodeResult: function (results) {
L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber');
if (results.length === 1) {
this._geocodeResultSelected(results[0]);
} else if (results.length > 0) {
this._alts.innerHTML = '';
this._results = results;
L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
for (var i = 0; i < results.length; i++) {
this._alts.appendChild(this._createAlt(results[i], i));
}
} else {
L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error');
}
},
markGeocode: function(result) {
this._map.fitBounds(result.bbox);
if (this._geocodeMarker) {
this._map.removeLayer(this._geocodeMarker);
}
this._geocodeMarker = new L.Marker(result.center)
.bindPopup(result.html || result.name)
.addTo(this._map)
.openPopup();
return this;
},
_geocode: function(event) {
L.DomEvent.preventDefault(event);
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber');
this._clearResults();
this.options.geocoder.geocode(this._input.value, this._geocodeResult, this);
return false;
},
_geocodeResultSelected: function(result) {
if (this.options.collapsed) {
this._collapse();
} else {
this._clearResults();
}
this.markGeocode(result);
},
_toggle: function() {
if (this._container.className.indexOf('leaflet-control-geocoder-expanded') >= 0) {
this._collapse();
} else {
this._expand();
}
},
_expand: function () {
L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded');
this._input.select();
},
_collapse: function () {
this._container.className = this._container.className.replace(' leaflet-control-geocoder-expanded', '');
L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
},
_clearResults: function () {
L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized');
this._selection = null;
L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error');
},
_createAlt: function(result, index) {
var li = L.DomUtil.create('li', ''),
a = L.DomUtil.create('a', '', li),
icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null,
text = result.html ? undefined : document.createTextNode(result.name),
clickHandler = function clickHandler(e) {
L.DomEvent.preventDefault(e);
this._geocodeResultSelected(result);
};
if (icon) {
icon.src = result.icon;
}
li.setAttribute('data-result-index', index);
if (result.html) {
a.innerHTML = result.html;
} else {
a.appendChild(text);
}
L.DomEvent.addListener(li, 'click', clickHandler, this);
return li;
},
_keydown: function(e) {
var _this = this,
select = function select(dir) {
if (_this._selection) {
L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected');
_this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling'];
}
if (!_this._selection) {
_this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild'];
}
if (_this._selection) {
L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected');
}
};
switch (e.keyCode) {
// Escape
case 27:
if (this.options.collapsed) {
this._collapse();
}
break;
// Up
case 38:
select(-1);
L.DomEvent.preventDefault(e);
break;
// Up
case 40:
select(1);
L.DomEvent.preventDefault(e);
break;
// Enter
case 13:
if (this._selection) {
var index = parseInt(this._selection.getAttribute('data-result-index'), 10);
this._geocodeResultSelected(this._results[index]);
this._clearResults();
L.DomEvent.preventDefault(e);
}
}
return true;
}
}),
factory: function(options) {
return new L.Control.Geocoder(options);
}
};