leaflet-routing-machine
Version:
212 lines (182 loc) • 6.04 kB
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) {
L.DomEvent.preventDefault(e);
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);
}
}
});
})();