mirror of
https://github.com/iiab/iiab.git
synced 2025-02-13 11:42:08 +00:00
425 lines
15 KiB
JavaScript
425 lines
15 KiB
JavaScript
/*
|
|
* L.Control.GeoSearch - search for an address and zoom to it's location
|
|
* https://github.com/smeijer/leaflet.control.geosearch
|
|
*/
|
|
|
|
L.GeoSearch = {};
|
|
L.GeoSearch.Provider = {};
|
|
|
|
// MSIE needs cors support
|
|
jQuery.support.cors = true;
|
|
|
|
L.GeoSearch.Result = function (x, y, label) {
|
|
this.X = x;
|
|
this.Y = y;
|
|
this.Label = label;
|
|
};
|
|
|
|
L.Control.GeoSearch = L.Control.extend({
|
|
options: {
|
|
position: 'topcenter'
|
|
},
|
|
|
|
initialize: function (options) {
|
|
this._config = {};
|
|
L.Util.extend(this.options, options);
|
|
this.setConfig(options);
|
|
},
|
|
|
|
setConfig: function (options) {
|
|
this._config = {
|
|
'country': options.country || '',
|
|
'provider': options.provider,
|
|
|
|
'searchLabel': options.searchLabel || 'search for address...',
|
|
'notFoundMessage' : options.notFoundMessage || 'Sorry, that address could not be found.',
|
|
'messageHideDelay': options.messageHideDelay || 3000,
|
|
'zoomLevel': options.zoomLevel || 18,
|
|
|
|
'maxMarkers': options.maxMarkers || 1,
|
|
'enableButtons': options.enableButtons || false,
|
|
|
|
'enableAutocomplete': options.enableAutocomplete || false,
|
|
'autocompleteMinQueryLen': options.autocompleteMinQueryLen || 3, // query length request threshold
|
|
'autocompleteQueryDelay_ms': options.autocompleteQueryDelay_ms || 800
|
|
};
|
|
},
|
|
|
|
onAdd: function (map) {
|
|
var $controlContainer = $(map._controlContainer);
|
|
|
|
if ($controlContainer.children('.leaflet-top.leaflet-center').length == 0) {
|
|
$controlContainer.append('<div class="leaflet-top leaflet-center"></div>');
|
|
map._controlCorners.topcenter = $controlContainer.children('.leaflet-top.leaflet-center').first()[0];
|
|
}
|
|
|
|
this._map = map;
|
|
this._container = L.DomUtil.create('div', 'leaflet-control-geosearch');
|
|
|
|
var searchbox = document.createElement('input');
|
|
searchbox.id = 'leaflet-control-geosearch-qry';
|
|
searchbox.type = 'text';
|
|
searchbox.placeholder = this._config.searchLabel;
|
|
this._searchbox = searchbox;
|
|
if (this._autocomplete) {
|
|
this._autocomplete.recordLastUserInput('');
|
|
}
|
|
|
|
if (this._config.enableButtons) {
|
|
var submitContainer = L.DomUtil.create('div', 'leaflet-control-geosearch-button-submit-container', this._container);
|
|
L.DomUtil.create('span', 'leaflet-geosearch-submit-button', submitContainer);
|
|
var cancelButton = L.DomUtil.create('span', 'leaflet-geosearch-cancel-button', this._container);
|
|
L.DomEvent.on(submitContainer, 'click', this._submitRequest, this);
|
|
L.DomEvent.on(cancelButton, 'click', this._clearUserSearchInput, this);
|
|
}
|
|
|
|
var msgbox = document.createElement('div');
|
|
msgbox.id = 'leaflet-control-geosearch-msg';
|
|
msgbox.className = 'leaflet-control-geosearch-msg';
|
|
this._msgbox = msgbox;
|
|
|
|
var resultslist = document.createElement('ul');
|
|
resultslist.id = 'leaflet-control-geosearch-results';
|
|
this._resultslist = resultslist;
|
|
|
|
$(this._msgbox).append(this._resultslist);
|
|
$(this._container).append(this._searchbox, this._msgbox);
|
|
|
|
if (this._config.enableAutocomplete) {
|
|
this._autocomplete = new L.AutoComplete(this.options).addTo(this._container, function (suggestionText) {
|
|
this._searchbox.value = suggestionText;
|
|
}.bind(this));
|
|
$(this._container).append(this._autocomplete);
|
|
}
|
|
|
|
// TODO This will result in duplicate processing of events. Options?
|
|
L.DomEvent
|
|
.addListener(this._container, 'click', L.DomEvent.stop)
|
|
.addListener(this._container, 'keyup', this._onKeyUp, this)
|
|
.addListener(this._container, 'change', this._onInputUpdate, this)
|
|
.addListener(this._container, 'paste', this._onPasteToInput, this);
|
|
|
|
L.DomEvent.disableClickPropagation(this._container);
|
|
|
|
return this._container;
|
|
},
|
|
|
|
geosearch: function (qry) {
|
|
this.geosearch_ext(qry, this._processResults.bind(this), this._printError.bind(this));
|
|
},
|
|
|
|
geosearch_ext: function(qry, onSuccess, onFailure) {
|
|
try {
|
|
var provider = this._config.provider;
|
|
|
|
if(typeof provider.GetLocations == 'function') {
|
|
var results = provider.GetLocations(qry, function(results) {
|
|
onSuccess(results);
|
|
}.bind(this));
|
|
}
|
|
else {
|
|
var url = provider.GetServiceUrl(qry);
|
|
|
|
$.getJSON(url, function (data) {
|
|
try {
|
|
var results = provider.ParseJSON(data);
|
|
onSuccess(results);
|
|
}
|
|
catch (error) {
|
|
onFailure(error);
|
|
}
|
|
}.bind(this));
|
|
}
|
|
}
|
|
catch (error) {
|
|
onFailure(error);
|
|
}
|
|
},
|
|
|
|
// qry may be a String or a function
|
|
geosearch_autocomplete: function (qry, requestDelay_ms) {
|
|
if (!this._config.enableAutocomplete) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout(this._autocompleteRequestTimer);
|
|
|
|
this._autocompleteRequestTimer = setTimeout(function () {
|
|
var q = qry;
|
|
if (typeof qry === 'function') {
|
|
q = qry();
|
|
}
|
|
if (q.length >= this._config.autocompleteMinQueryLen) {
|
|
this.geosearch_ext(q, this._autocomplete.show.bind(this._autocomplete), this._autocomplete.hide.bind(this._autocomplete));
|
|
} else {
|
|
this._autocomplete.hide();
|
|
}
|
|
}.bind(this), requestDelay_ms);
|
|
},
|
|
|
|
_processResults: function(results) {
|
|
if (results.length == 0)
|
|
throw this._config.notFoundMessage;
|
|
|
|
this._map.fireEvent('geosearch_foundlocations', {Locations: results});
|
|
this._showLocations(results);
|
|
},
|
|
|
|
_showLocations: function (results) {
|
|
if (typeof this._layer !== 'undefined') {
|
|
this._map.removeLayer(this._layer);
|
|
this._layer = null;
|
|
}
|
|
|
|
this._markerList = []
|
|
for (var ii=0; ii < results.length && ii < this._config.maxMarkers; ii++) {
|
|
var location = results[ii];
|
|
var marker = L.marker([location.Y, location.X]).bindPopup(location.Label);
|
|
this._markerList.push(marker);
|
|
}
|
|
this._layer = L.layerGroup(this._markerList).addTo(this._map);
|
|
this._printError('Displaying ' + Math.min(this._autocomplete._config.maxResultCount, results.length) + ' of ' + results.length +' results.');
|
|
|
|
var premierResult = results[0];
|
|
this._map.setView([premierResult.Y, premierResult.X], this._config.zoomLevel, false);
|
|
this._map.fireEvent('geosearch_showlocation', {Location: premierResult});
|
|
},
|
|
|
|
_printError: function(message) {
|
|
$(this._resultslist)
|
|
.html('<li>'+message+'</li>')
|
|
.fadeIn('slow').delay(this._config.messageHideDelay).fadeOut('slow',
|
|
function () { $(this).html(''); });
|
|
},
|
|
|
|
_submitRequest: function () {
|
|
var q = $('#leaflet-control-geosearch-qry').val();
|
|
if (q.length > 0) {
|
|
this._hideAutocomplete();
|
|
this.geosearch(q);
|
|
}
|
|
},
|
|
|
|
_hideAutocomplete: function () {
|
|
clearTimeout(this._autocompleteRequestTimer);
|
|
if (this._config.enableAutocomplete && this._autocomplete.isVisible()) {
|
|
this._autocomplete.hide();
|
|
return true;
|
|
}
|
|
return false;
|
|
},
|
|
|
|
_clearUserSearchInput: function () {
|
|
this._hideAutocomplete();
|
|
$('#leaflet-control-geosearch-qry').val('');
|
|
$('.leaflet-geosearch-cancel-button').hide();
|
|
},
|
|
|
|
_onPasteToInput: function () {
|
|
// onpaste requires callback to allow for input update do this by default.
|
|
setTimeout(this._onInputUpdate.bind(this), 0);
|
|
},
|
|
|
|
_onInputUpdate: function () {
|
|
// define function for requery of user input after delay
|
|
function getQuery() {
|
|
return $('#leaflet-control-geosearch-qry').val();
|
|
}
|
|
var qry = getQuery();
|
|
|
|
if (this._config.enableAutocomplete) {
|
|
this._autocomplete.recordLastUserInput(qry);
|
|
if (qry.length >= this._config.autocompleteMinQueryLen) {
|
|
this.geosearch_autocomplete(getQuery, this._config.autocompleteQueryDelay_ms);
|
|
} else {
|
|
this._autocomplete.hide();
|
|
}
|
|
}
|
|
|
|
if (qry.length > 0) {
|
|
$('.leaflet-geosearch-cancel-button').show();
|
|
} else {
|
|
$('.leaflet-geosearch-cancel-button').hide();
|
|
}
|
|
},
|
|
|
|
_onKeyUp: function (e) {
|
|
var REQ_DELAY_MS = 800;
|
|
var MIN_AUTOCOMPLETE_LEN = 3;
|
|
var enterKey = 13;
|
|
var shift = 16;
|
|
var ctrl = 17;
|
|
var escapeKey = 27;
|
|
var leftArrow = 37;
|
|
var upArrow = 38;
|
|
var rightArrow = 39;
|
|
var downArrow = 40;
|
|
|
|
switch (e.keyCode) {
|
|
case escapeKey:
|
|
// ESC first closes autocomplete if open. If closed then clears input.
|
|
if (!this._hideAutocomplete()) {
|
|
this._clearUserSearchInput();
|
|
}
|
|
break;
|
|
case enterKey:
|
|
this._submitRequest();
|
|
break;
|
|
case upArrow:
|
|
if (this._config.enableAutocomplete && this._autocomplete.isVisible()) {
|
|
this._autocomplete.moveUp();
|
|
}
|
|
break;
|
|
case downArrow:
|
|
if (this._config.enableAutocomplete && this._autocomplete.isVisible()) {
|
|
this._autocomplete.moveDown();
|
|
}
|
|
break;
|
|
case leftArrow:
|
|
case rightArrow:
|
|
case shift:
|
|
case ctrl:
|
|
break;
|
|
default:
|
|
this._onInputUpdate();
|
|
}
|
|
}
|
|
});
|
|
|
|
L.AutoComplete = L.Class.extend({
|
|
initialize: function (options) {
|
|
this._config = {};
|
|
this.setConfig(options);
|
|
},
|
|
|
|
setConfig: function (options) {
|
|
this._config = {
|
|
'maxResultCount': options.maxResultCount || 10,
|
|
'onMakeSuggestionHTML': options.onMakeSuggestionHTML || function (geosearchResult) {
|
|
return this._htmlEscape(geosearchResult.Label);
|
|
}.bind(this),
|
|
};
|
|
},
|
|
|
|
addTo: function (container, onSelectionCallback) {
|
|
this._container = container;
|
|
this._onSelection = onSelectionCallback;
|
|
return this._createUI(container, 'leaflet-geosearch-autocomplete');
|
|
},
|
|
|
|
recordLastUserInput: function (str) {
|
|
this._lastUserInput = str;
|
|
},
|
|
|
|
_createUI: function (container, className) {
|
|
this._tool = L.DomUtil.create('div', className, container);
|
|
this._tool.style.display = 'none';
|
|
L.DomEvent
|
|
.disableClickPropagation(this._tool)
|
|
// consider whether to make delayed hide onBlur.
|
|
// If so, consider canceling timer on mousewheel and mouseover.
|
|
.on(this._tool, 'blur', this.hide, this)
|
|
.on(this._tool, 'mousewheel', function(e) {
|
|
L.DomEvent.stopPropagation(e); // to prevent map zoom
|
|
if (e.axis === e.VERTICAL_AXIS) {
|
|
if (e.detail > 0) {
|
|
this.moveDown();
|
|
} else {
|
|
this.moveUp();
|
|
}
|
|
}
|
|
}, this);
|
|
return this;
|
|
},
|
|
|
|
|
|
show: function (results) {
|
|
this._tool.innerHTML = '';
|
|
this._tool.currentSelection = -1;
|
|
var count = 0;
|
|
while (count < results.length && count < this._config.maxResultCount) {
|
|
var entry = this._newSuggestion(results[count]);
|
|
this._tool.appendChild(entry);
|
|
++count;
|
|
}
|
|
if (count > 0) {
|
|
this._tool.style.display = 'block';
|
|
} else {
|
|
this.hide();
|
|
}
|
|
return count;
|
|
},
|
|
hide: function () {
|
|
this._tool.style.display = 'none';
|
|
this._tool.innerHTML = '';
|
|
},
|
|
|
|
isVisible: function() {
|
|
return this._tool.style.display !== 'none';
|
|
},
|
|
|
|
_htmlEscape: function (str) {
|
|
// implementation courtesy of http://stackoverflow.com/a/7124052
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
},
|
|
|
|
_newSuggestion: function (result) {
|
|
var tip = L.DomUtil.create('li', 'leaflet-geosearch-suggestion');
|
|
tip.innerHTML = this._config.onMakeSuggestionHTML(result);
|
|
tip._text = result.Label;
|
|
L.DomEvent
|
|
.disableClickPropagation(tip)
|
|
.on(tip, 'click', function(e) {
|
|
this._onSelection(tip._text);
|
|
}.bind(this), this);
|
|
return tip;
|
|
},
|
|
_onSelectedUpdate: function () {
|
|
var entries = this._tool.hasChildNodes() ? this._tool.childNodes : [];
|
|
for (var ii=0; ii < entries.length; ++ii) {
|
|
L.DomUtil.removeClass(entries[ii], 'leaflet-geosearch-suggestion-selected');
|
|
}
|
|
|
|
// if selection is -1, then show last user typed text
|
|
if (this._tool.currentSelection >= 0) {
|
|
L.DomUtil.addClass(entries[this._tool.currentSelection], 'leaflet-geosearch-suggestion-selected');
|
|
|
|
// scroll:
|
|
var tipOffsetTop = entries[this._tool.currentSelection].offsetTop;
|
|
if (tipOffsetTop + entries[this._tool.currentSelection].clientHeight >= this._tool.scrollTop + this._tool.clientHeight) {
|
|
this._tool.scrollTop = tipOffsetTop - this._tool.clientHeight + entries[this._tool.currentSelection].clientHeight;
|
|
}
|
|
else if (tipOffsetTop <= this._tool.scrollTop) {
|
|
this._tool.scrollTop = tipOffsetTop;
|
|
}
|
|
|
|
this._onSelection(entries[this._tool.currentSelection]._text);
|
|
} else {
|
|
this._onSelection(this._lastUserInput);
|
|
}
|
|
},
|
|
moveUp: function () {
|
|
// permit selection to decrement down to -1 (none selected)
|
|
if (this.isVisible() && this._tool.currentSelection >= 0) {
|
|
--this._tool.currentSelection;
|
|
this._onSelectedUpdate();
|
|
}
|
|
return this;
|
|
},
|
|
moveDown: function () {
|
|
if (this.isVisible()) {
|
|
this._tool.currentSelection = (this._tool.currentSelection + 1) % this.suggestionCount();
|
|
this._onSelectedUpdate();
|
|
}
|
|
return this;
|
|
},
|
|
suggestionCount: function () {
|
|
return this._tool.hasChildNodes() ? this._tool.childNodes.length : 0;
|
|
},
|
|
});
|
|
|