211 lines
5.9 KiB
JavaScript
211 lines
5.9 KiB
JavaScript
(function() {
|
|
'use strict';
|
|
|
|
var L = require('leaflet');
|
|
|
|
module.exports = L.Class.extend({
|
|
options: {
|
|
timeout: 500,
|
|
blurTimeout: 100,
|
|
noResultsMessage: 'No results found.'
|
|
},
|
|
|
|
initialize: function(elem, callback, context, options) {
|
|
L.setOptions(this, options);
|
|
|
|
this._elem = elem;
|
|
this._resultFn = options.resultFn ? L.Util.bind(options.resultFn, options.resultContext) : null;
|
|
this._autocomplete = options.autocompleteFn ? L.Util.bind(options.autocompleteFn, options.autocompleteContext) : null;
|
|
this._selectFn = L.Util.bind(callback, context);
|
|
this._container = L.DomUtil.create('div', 'leaflet-routing-geocoder-result');
|
|
this._resultTable = L.DomUtil.create('table', '', this._container);
|
|
|
|
// TODO: looks a bit like a kludge to register same for input and keypress -
|
|
// browsers supporting both will get duplicate events; just registering
|
|
// input will not catch enter, though.
|
|
L.DomEvent.addListener(this._elem, 'input', this._keyPressed, this);
|
|
L.DomEvent.addListener(this._elem, 'keypress', this._keyPressed, this);
|
|
L.DomEvent.addListener(this._elem, 'keydown', this._keyDown, this);
|
|
L.DomEvent.addListener(this._elem, 'blur', function() {
|
|
if (this._isOpen) {
|
|
this.close();
|
|
}
|
|
}, this);
|
|
},
|
|
|
|
close: function() {
|
|
L.DomUtil.removeClass(this._container, 'leaflet-routing-geocoder-result-open');
|
|
this._isOpen = false;
|
|
},
|
|
|
|
_open: function() {
|
|
var rect = this._elem.getBoundingClientRect();
|
|
if (!this._container.parentElement) {
|
|
// See notes section under https://developer.mozilla.org/en-US/docs/Web/API/Window/scrollX
|
|
// This abomination is required to support all flavors of IE
|
|
var scrollX = (window.pageXOffset !== undefined) ? window.pageXOffset
|
|
: (document.documentElement || document.body.parentNode || document.body).scrollLeft;
|
|
var scrollY = (window.pageYOffset !== undefined) ? window.pageYOffset
|
|
: (document.documentElement || document.body.parentNode || document.body).scrollTop;
|
|
this._container.style.left = (rect.left + scrollX) + 'px';
|
|
this._container.style.top = (rect.bottom + scrollY) + 'px';
|
|
this._container.style.width = (rect.right - rect.left) + 'px';
|
|
document.body.appendChild(this._container);
|
|
}
|
|
|
|
L.DomUtil.addClass(this._container, 'leaflet-routing-geocoder-result-open');
|
|
this._isOpen = true;
|
|
},
|
|
|
|
_setResults: function(results) {
|
|
var i,
|
|
tr,
|
|
td,
|
|
text;
|
|
|
|
delete this._selection;
|
|
this._results = results;
|
|
|
|
while (this._resultTable.firstChild) {
|
|
this._resultTable.removeChild(this._resultTable.firstChild);
|
|
}
|
|
|
|
for (i = 0; i < results.length; i++) {
|
|
tr = L.DomUtil.create('tr', '', this._resultTable);
|
|
tr.setAttribute('data-result-index', i);
|
|
td = L.DomUtil.create('td', '', tr);
|
|
text = document.createTextNode(results[i].name);
|
|
td.appendChild(text);
|
|
// mousedown + click because:
|
|
// http://stackoverflow.com/questions/10652852/jquery-fire-click-before-blur-event
|
|
L.DomEvent.addListener(td, 'mousedown', L.DomEvent.preventDefault);
|
|
L.DomEvent.addListener(td, 'click', this._createClickListener(results[i]));
|
|
}
|
|
|
|
if (!i) {
|
|
tr = L.DomUtil.create('tr', '', this._resultTable);
|
|
td = L.DomUtil.create('td', 'leaflet-routing-geocoder-no-results', tr);
|
|
td.innerHTML = this.options.noResultsMessage;
|
|
}
|
|
|
|
this._open();
|
|
|
|
if (results.length > 0) {
|
|
// Select the first entry
|
|
this._select(1);
|
|
}
|
|
},
|
|
|
|
_createClickListener: function(r) {
|
|
var resultSelected = this._resultSelected(r);
|
|
return L.bind(function() {
|
|
this._elem.blur();
|
|
resultSelected();
|
|
}, this);
|
|
},
|
|
|
|
_resultSelected: function(r) {
|
|
return L.bind(function() {
|
|
this.close();
|
|
this._elem.value = r.name;
|
|
this._lastCompletedText = r.name;
|
|
this._selectFn(r);
|
|
}, this);
|
|
},
|
|
|
|
_keyPressed: function(e) {
|
|
var index;
|
|
|
|
if (this._isOpen && e.keyCode === 13 && this._selection) {
|
|
index = parseInt(this._selection.getAttribute('data-result-index'), 10);
|
|
this._resultSelected(this._results[index])();
|
|
L.DomEvent.preventDefault(e);
|
|
return;
|
|
}
|
|
|
|
if (e.keyCode === 13) {
|
|
this._complete(this._resultFn, true);
|
|
return;
|
|
}
|
|
|
|
if (this._autocomplete && document.activeElement === this._elem) {
|
|
if (this._timer) {
|
|
clearTimeout(this._timer);
|
|
}
|
|
this._timer = setTimeout(L.Util.bind(function() { this._complete(this._autocomplete); }, this),
|
|
this.options.timeout);
|
|
return;
|
|
}
|
|
|
|
this._unselect();
|
|
},
|
|
|
|
_select: function(dir) {
|
|
var sel = this._selection;
|
|
if (sel) {
|
|
L.DomUtil.removeClass(sel.firstChild, 'leaflet-routing-geocoder-selected');
|
|
sel = sel[dir > 0 ? 'nextSibling' : 'previousSibling'];
|
|
}
|
|
if (!sel) {
|
|
sel = this._resultTable[dir > 0 ? 'firstChild' : 'lastChild'];
|
|
}
|
|
|
|
if (sel) {
|
|
L.DomUtil.addClass(sel.firstChild, 'leaflet-routing-geocoder-selected');
|
|
this._selection = sel;
|
|
}
|
|
},
|
|
|
|
_unselect: function() {
|
|
if (this._selection) {
|
|
L.DomUtil.removeClass(this._selection.firstChild, 'leaflet-routing-geocoder-selected');
|
|
}
|
|
delete this._selection;
|
|
},
|
|
|
|
_keyDown: function(e) {
|
|
if (this._isOpen) {
|
|
switch (e.keyCode) {
|
|
// Escape
|
|
case 27:
|
|
this.close();
|
|
L.DomEvent.preventDefault(e);
|
|
return;
|
|
// Up
|
|
case 38:
|
|
this._select(-1);
|
|
L.DomEvent.preventDefault(e);
|
|
return;
|
|
// Down
|
|
case 40:
|
|
this._select(1);
|
|
L.DomEvent.preventDefault(e);
|
|
return;
|
|
}
|
|
}
|
|
},
|
|
|
|
_complete: function(completeFn, trySelect) {
|
|
var v = this._elem.value;
|
|
function completeResults(results) {
|
|
this._lastCompletedText = v;
|
|
if (trySelect && results.length === 1) {
|
|
this._resultSelected(results[0])();
|
|
} else {
|
|
this._setResults(results);
|
|
}
|
|
}
|
|
|
|
if (!v) {
|
|
return;
|
|
}
|
|
|
|
if (v !== this._lastCompletedText) {
|
|
completeFn(v, completeResults, this);
|
|
} else if (trySelect) {
|
|
completeResults.call(this, this._results);
|
|
}
|
|
}
|
|
});
|
|
})();
|