444 lines
13 KiB
JavaScript
444 lines
13 KiB
JavaScript
L.PhotonBase = L.Class.extend({
|
|
|
|
forEach: function (els, callback) {
|
|
Array.prototype.forEach.call(els, callback);
|
|
},
|
|
|
|
ajax: function (callback, thisobj) {
|
|
if (typeof this.xhr === 'object') {
|
|
this.xhr.abort();
|
|
}
|
|
this.xhr = new XMLHttpRequest();
|
|
var self = this;
|
|
this.xhr.open('GET', this.options.url + this.buildQueryString(this.getParams()), true);
|
|
|
|
this.xhr.onload = function(e) {
|
|
self.fire('ajax:return');
|
|
if (this.status === 200) {
|
|
if (callback) {
|
|
var raw = this.response;
|
|
raw = JSON.parse(raw);
|
|
callback.call(thisobj || this, raw);
|
|
}
|
|
}
|
|
delete this.xhr;
|
|
};
|
|
|
|
this.fire('ajax:send');
|
|
this.xhr.send();
|
|
},
|
|
|
|
buildQueryString: function (params) {
|
|
var queryString = [];
|
|
for (var key in params) {
|
|
if (params[key]) {
|
|
queryString.push(encodeURIComponent(key) + '=' + encodeURIComponent(params[key]));
|
|
}
|
|
}
|
|
return queryString.join('&');
|
|
},
|
|
|
|
featureToPopupContent: function (feature) {
|
|
var container = L.DomUtil.create('div', 'leaflet-photon-popup'),
|
|
title = L.DomUtil.create('h3', '', container);
|
|
title.innerHTML = feature.properties.label;
|
|
return container;
|
|
}
|
|
|
|
});
|
|
|
|
|
|
L.PhotonSearch = L.PhotonBase.extend({
|
|
|
|
includes: L.Mixin.Events,
|
|
|
|
options: {
|
|
url: 'http://photon.komoot.de/api/?',
|
|
placeholder: 'Start typing...',
|
|
minChar: 3,
|
|
limit: 5,
|
|
submitDelay: 300,
|
|
includePosition: true,
|
|
noResultLabel: 'No result',
|
|
feedbackEmail: 'photon@komoot.de', // Set to null to remove feedback box
|
|
feedbackLabel: 'Feedback'
|
|
},
|
|
|
|
CACHE: '',
|
|
RESULTS: [],
|
|
KEYS: {
|
|
LEFT: 37,
|
|
UP: 38,
|
|
RIGHT: 39,
|
|
DOWN: 40,
|
|
TAB: 9,
|
|
RETURN: 13,
|
|
ESC: 27,
|
|
APPLE: 91,
|
|
SHIFT: 16,
|
|
ALT: 17,
|
|
CTRL: 18
|
|
},
|
|
|
|
initialize: function (map, input, options) {
|
|
this.map = map;
|
|
this.input = input;
|
|
L.setOptions(this, options);
|
|
var CURRENT = null;
|
|
|
|
try {
|
|
Object.defineProperty(this, 'CURRENT', {
|
|
get: function () {
|
|
return CURRENT;
|
|
},
|
|
set: function (index) {
|
|
if (typeof index === 'object') {
|
|
index = this.resultToIndex(index);
|
|
}
|
|
CURRENT = index;
|
|
}
|
|
});
|
|
} catch (e) {
|
|
// Hello IE8
|
|
}
|
|
this.input.type = 'search';
|
|
this.input.placeholder = this.options.placeholder;
|
|
this.input.autocomplete = 'off';
|
|
this.input.autocorrect = 'off';
|
|
L.DomEvent.disableClickPropagation(this.input);
|
|
|
|
L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this);
|
|
L.DomEvent.on(this.input, 'input', this.onInput, this);
|
|
L.DomEvent.on(this.input, 'blur', this.onBlur, this);
|
|
L.DomEvent.on(this.input, 'focus', this.onFocus, this);
|
|
this.createResultsContainer();
|
|
},
|
|
|
|
createResultsContainer: function () {
|
|
this.resultsContainer = L.DomUtil.create('ul', 'photon-autocomplete', document.querySelector('body'));
|
|
},
|
|
|
|
resizeContainer: function()
|
|
{
|
|
var l = this.getLeft(this.input);
|
|
var t = this.getTop(this.input) + this.input.offsetHeight;
|
|
this.resultsContainer.style.left = l + 'px';
|
|
this.resultsContainer.style.top = t + 'px';
|
|
var width = this.options.width ? this.options.width : this.input.offsetWidth - 2;
|
|
this.resultsContainer.style.width = width + 'px';
|
|
},
|
|
|
|
onKeyDown: function (e) {
|
|
switch (e.keyCode) {
|
|
case this.KEYS.TAB:
|
|
if(this.CURRENT !== null)
|
|
{
|
|
this.setChoice();
|
|
}
|
|
L.DomEvent.stop(e);
|
|
break;
|
|
case this.KEYS.RETURN:
|
|
L.DomEvent.stop(e);
|
|
this.setChoice();
|
|
break;
|
|
case this.KEYS.ESC:
|
|
L.DomEvent.stop(e);
|
|
this.hide();
|
|
this.input.blur();
|
|
break;
|
|
case this.KEYS.DOWN:
|
|
if(this.RESULTS.length > 0) {
|
|
if(this.CURRENT !== null && this.CURRENT < this.RESULTS.length - 1) { // what if one resutl?
|
|
this.CURRENT++;
|
|
this.highlight();
|
|
}
|
|
else if(this.CURRENT === null) {
|
|
this.CURRENT = 0;
|
|
this.highlight();
|
|
}
|
|
}
|
|
break;
|
|
case this.KEYS.UP:
|
|
if(this.CURRENT !== null) {
|
|
L.DomEvent.stop(e);
|
|
}
|
|
if(this.RESULTS.length > 0) {
|
|
if(this.CURRENT > 0) {
|
|
this.CURRENT--;
|
|
this.highlight();
|
|
}
|
|
else if(this.CURRENT === 0) {
|
|
this.CURRENT = null;
|
|
this.highlight();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
},
|
|
|
|
onInput: function (e) {
|
|
if (typeof this.submitDelay === 'number') {
|
|
window.clearTimeout(this.submitDelay);
|
|
delete this.submitDelay;
|
|
}
|
|
this.submitDelay = window.setTimeout(L.Util.bind(this.search, this), this.options.submitDelay);
|
|
},
|
|
|
|
onBlur: function (e) {
|
|
this.fire('blur');
|
|
var self = this;
|
|
setTimeout(function () {
|
|
self.hide();
|
|
}, 100);
|
|
},
|
|
|
|
onFocus: function (e) {
|
|
this.fire('focus');
|
|
this.input.select();
|
|
this.search(); // In case we have a value from a previous search.
|
|
},
|
|
|
|
clear: function () {
|
|
this.RESULTS = [];
|
|
this.CURRENT = null;
|
|
this.CACHE = '';
|
|
this.resultsContainer.innerHTML = '';
|
|
},
|
|
|
|
hide: function() {
|
|
this.fire('hide');
|
|
this.clear();
|
|
this.resultsContainer.style.display = 'none';
|
|
},
|
|
|
|
setChoice: function (choice) {
|
|
choice = choice || this.RESULTS[this.CURRENT];
|
|
if (choice) {
|
|
this.hide();
|
|
this.fire('selected', {choice: choice.feature});
|
|
this.onSelected(choice.feature);
|
|
this.input.value = '';
|
|
}
|
|
},
|
|
|
|
search: function() {
|
|
var val = this.input.value;
|
|
var minChar = typeof this.options.minChar === 'function' ? this.options.minChar(val) : val.length >= this.options.minChar;
|
|
if (!val || !minChar) return this.clear();
|
|
if(val + '' === this.CACHE + '') return;
|
|
else this.CACHE = val;
|
|
this._doSearch();
|
|
},
|
|
|
|
_doSearch: function () {
|
|
this.ajax(this.handleResults, this);
|
|
},
|
|
|
|
_onSelected: function (feature) {
|
|
this.map.setView([feature.geometry.coordinates[1], feature.geometry.coordinates[0]], 16);
|
|
},
|
|
|
|
onSelected: function (choice) {
|
|
return (this.options.onSelected || this._onSelected).call(this, choice);
|
|
},
|
|
|
|
_formatResult: function (feature, el) {
|
|
var title = L.DomUtil.create('strong', '', el),
|
|
detailsContainer = L.DomUtil.create('small', '', el),
|
|
details = [],
|
|
type = this.formatType(feature);
|
|
title.innerHTML = feature.properties.name;
|
|
if (type) details.push(type);
|
|
if (feature.properties.city && feature.properties.city !== feature.properties.name) {
|
|
details.push(feature.properties.city);
|
|
}
|
|
if (feature.properties.country) details.push(feature.properties.country);
|
|
detailsContainer.innerHTML = details.join(', ');
|
|
},
|
|
|
|
formatResult: function (feature, el) {
|
|
return (this.options.formatResult || this._formatResult).call(this, feature, el);
|
|
},
|
|
|
|
formatType: function (feature) {
|
|
return (this.options.formatType || this._formatType).call(this, feature);
|
|
},
|
|
|
|
_formatType: function (feature) {
|
|
return feature.properties.osm_value;
|
|
},
|
|
|
|
createResult: function (feature) {
|
|
var el = L.DomUtil.create('li', '', this.resultsContainer);
|
|
this.formatResult(feature, el);
|
|
var result = {
|
|
feature: feature,
|
|
el: el
|
|
};
|
|
// Touch handling needed
|
|
L.DomEvent.on(el, 'mouseover', function (e) {
|
|
this.CURRENT = result;
|
|
this.highlight();
|
|
}, this);
|
|
L.DomEvent.on(el, 'mousedown', function (e) {
|
|
this.setChoice();
|
|
}, this);
|
|
return result;
|
|
},
|
|
|
|
resultToIndex: function (result) {
|
|
var out = null;
|
|
this.forEach(this.RESULTS, function (item, index) {
|
|
if (item === result) {
|
|
out = index;
|
|
return;
|
|
}
|
|
});
|
|
return out;
|
|
},
|
|
|
|
handleResults: function(geojson) {
|
|
var self = this;
|
|
this.clear();
|
|
this.resultsContainer.style.display = 'block';
|
|
this.resizeContainer();
|
|
this.forEach(geojson.features, function (feature) {
|
|
self.RESULTS.push(self.createResult(feature));
|
|
});
|
|
if (geojson.features.length === 0) {
|
|
var noresult = L.DomUtil.create('li', 'photon-no-result', this.resultsContainer);
|
|
noresult.innerHTML = this.options.noResultLabel;
|
|
}
|
|
if (this.options.feedbackEmail) {
|
|
var feedback = L.DomUtil.create('a', 'photon-feedback', this.resultsContainer);
|
|
feedback.href = 'mailto:' + this.options.feedbackEmail;
|
|
feedback.innerHTML = this.options.feedbackLabel;
|
|
}
|
|
this.CURRENT = 0;
|
|
this.highlight();
|
|
if (this.options.resultsHandler) {
|
|
this.options.resultsHandler(geojson);
|
|
}
|
|
},
|
|
|
|
highlight: function () {
|
|
var self = this;
|
|
this.forEach(this.RESULTS, function (item, index) {
|
|
if (index === self.CURRENT) {
|
|
L.DomUtil.addClass(item.el, 'on');
|
|
}
|
|
else {
|
|
L.DomUtil.removeClass(item.el, 'on');
|
|
}
|
|
});
|
|
},
|
|
|
|
getLeft: function (el) {
|
|
var tmp = el.offsetLeft;
|
|
el = el.offsetParent;
|
|
while(el) {
|
|
tmp += el.offsetLeft;
|
|
el = el.offsetParent;
|
|
}
|
|
return tmp;
|
|
},
|
|
|
|
getTop: function (el) {
|
|
var tmp = el.offsetTop;
|
|
el = el.offsetParent;
|
|
while(el) {
|
|
tmp += el.offsetTop;
|
|
el = el.offsetParent;
|
|
}
|
|
return tmp;
|
|
},
|
|
|
|
getParams: function () {
|
|
return {
|
|
q: this.CACHE,
|
|
lang: this.options.lang,
|
|
limit: this.options.limit,
|
|
lat: this.options.includePosition ? this.map.getCenter().lat : null,
|
|
lon: this.options.includePosition ? this.map.getCenter().lng : null
|
|
};
|
|
}
|
|
|
|
});
|
|
|
|
|
|
L.Control.Photon = L.Control.extend({
|
|
|
|
includes: L.Mixin.Events,
|
|
|
|
onAdd: function (map, options) {
|
|
this.map = map;
|
|
this.container = L.DomUtil.create('div', 'leaflet-photon');
|
|
|
|
this.options = L.Util.extend(this.options, options);
|
|
|
|
this.input = L.DomUtil.create('input', 'photon-input', this.container);
|
|
this.search = new L.PhotonSearch(map, this.input, this.options);
|
|
this.search.on('blur', this.forwardEvent, this);
|
|
this.search.on('focus', this.forwardEvent, this);
|
|
this.search.on('hide', this.forwardEvent, this);
|
|
this.search.on('selected', this.forwardEvent, this);
|
|
this.search.on('ajax:send', this.forwardEvent, this);
|
|
this.search.on('ajax:return', this.forwardEvent, this);
|
|
return this.container;
|
|
},
|
|
|
|
forwardEvent: function (e) {
|
|
this.fire(e.type, e);
|
|
}
|
|
|
|
|
|
});
|
|
|
|
L.Map.addInitHook(function () {
|
|
if (this.options.photonControl) {
|
|
this.photonControl = new L.Control.Photon(this.options.photonControlOptions || {});
|
|
this.addControl(this.photonControl);
|
|
}
|
|
});
|
|
|
|
L.PhotonReverse = L.PhotonBase.extend({
|
|
|
|
includes: L.Mixin.Events,
|
|
|
|
options: {
|
|
url: 'http://photon.komoot.de/reverse/?',
|
|
limit: 1,
|
|
handleResults: null
|
|
},
|
|
|
|
initialize: function (options) {
|
|
L.setOptions(this, options);
|
|
},
|
|
|
|
doReverse: function (latlng) {
|
|
latlng = L.latLng(latlng);
|
|
this.fire('reverse', {latlng: latlng});
|
|
this.latlng = latlng;
|
|
this.ajax(this.handleResults, this);
|
|
},
|
|
|
|
_handleResults: function (data) {
|
|
/*eslint-disable no-console */
|
|
console.log(data);
|
|
/*eslint-enable no-alert */
|
|
},
|
|
|
|
handleResults: function (data) {
|
|
return (this.options.handleResults || this._handleResults).call(this, data);
|
|
},
|
|
|
|
getParams: function () {
|
|
return {
|
|
lang: this.options.lang,
|
|
limit: this.options.limit,
|
|
lat: this.latlng.lat,
|
|
lon: this.latlng.lng
|
|
};
|
|
}
|
|
|
|
});
|