diff --git a/demo/index.html b/demo/index.html new file mode 100644 index 0000000..459542e --- /dev/null +++ b/demo/index.html @@ -0,0 +1,41 @@ + + + + + + + Leaflet.BAN.geocoder demo + + + + + + + + + +
+ + + + diff --git a/images/geocoder.png b/images/geocoder.png new file mode 100644 index 0000000..d82a017 Binary files /dev/null and b/images/geocoder.png differ diff --git a/src/leaflet.BAN.geocoder.css b/src/leaflet.BAN.geocoder.css new file mode 100644 index 0000000..b5da54e --- /dev/null +++ b/src/leaflet.BAN.geocoder.css @@ -0,0 +1,66 @@ +.leaflet-control-geocoder-ban { + border-radius: 4px; + background: white; + min-width: 26px; + min-height: 26px; +} + +.leaflet-control-geocoder-ban-icon { + border-radius: 4px; + width: 26px; + height: 26px; + border: none; + background-color: white; + background-image: url(../images/geocoder.png); + background-repeat: no-repeat; + background-position: center; +} + +.leaflet-control-geocoder-ban-form { + display: none; + vertical-align: middle; +} + +.leaflet-control-geocoder-ban-expanded .leaflet-control-geocoder-ban-form { + display: inline-block; +} + +.leaflet-control-geocoder-ban-form input { + font-size: 120%; + border: 0; + background-color: transparent; + width: 246px; +} + +.leaflet-control-geocoder-ban-alternatives { + display: block; + width: inherit; + list-style: none; + padding: 0; + margin: 0; +} + +.leaflet-control-geocoder-ban-alternatives-minimized { + display: none; + height: 0; +} + +.leaflet-control-geocoder-ban .leaflet-control-geocoder-ban-alternatives a { + width: inherit; + height: inherit; + line-height: inherit; + background-color: transparent; + text-align: left; + padding: 3px 0 3px 6px; +} + +.leaflet-control-geocoder-ban .leaflet-control-geocoder-ban-alternatives li { + width: inherit; + height: inherit; + cursor: pointer; + border-top: 1px solid #d0d0d0; +} + +.leaflet-control-geocoder-ban-selected { + background-color: rgba(0,0,255,0.2); +} \ No newline at end of file diff --git a/src/leaflet.BAN.geocoder.js b/src/leaflet.BAN.geocoder.js new file mode 100644 index 0000000..080d1a0 --- /dev/null +++ b/src/leaflet.BAN.geocoder.js @@ -0,0 +1,196 @@ +L.GeocoderBAN = L.Control.extend({ + options: { + position: 'topleft', + placeholder: 'adresse', + resultsNumber: 5, + collapsed: false, + serviceUrl: 'https://api-adresse.data.gouv.fr/search/', + minIntervalBetweenRequests: 250, + defaultMarkgeocode: true + }, + includes: L.Evented.prototype || L.Mixin.Events, + initialize: function(options) { + L.Util.setOptions(this, options); + }, + onAdd: function(map) { + var className = 'leaflet-control-geocoder-ban', + container = this._container = L.DomUtil.create('div', className + ' leaflet-bar'), + icon = this._icon = L.DomUtil.create('button', className + '-icon', container), + form = this._form = L.DomUtil.create('div', className + '-form', container), + input; + + this._map = map; + map.on('click', this._collapse, this); + + icon.innerHTML = ' '; + icon.type = 'button'; + + input = this._input = L.DomUtil.create('input', '', form); + input.type = 'text'; + input.placeholder = this.options.placeholder; + + this._alts = L.DomUtil.create('ul', + className + '-alternatives ' + className + '-alternatives-minimized', + container); + + L.DomEvent.on(icon, 'click', this._toggle, this); + L.DomEvent.addListener(input, 'keyup', this._keyup, this); + if (this.options.defaultMarkgeocode) { + this.on('markgeocode', this.markGeocode, this); + } + + L.DomEvent.disableScrollPropagation(container) + L.DomEvent.disableClickPropagation(container) + + if (!this.options.collapsed) { this._expand(); } + + return container; + }, + _toggle: function () { + if (this._container.classList.contains('leaflet-control-geocoder-ban-expanded')) { + this._collapse(); + } else { + this._expand(); + } + }, + _expand: function () { + L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-ban-expanded'); + if (this._geocodeMarker) { + this._map.removeLayer(this._geocodeMarker); + } + this._input.select(); + }, + _collapse: function () { + L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-ban-expanded'); + L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-ban-alternatives-minimized'); + this._input.blur(); + }, + _moveSelection: function(direction) { + var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected'); + var el; + if (!s.length) { + el = this._alts[direction < 0 ? 'firstChild' : 'lastChild']; + L.DomUtil.addClass(el, 'leaflet-control-geocoder-ban-selected'); + } else { + var currentSelection = s[0] + L.DomUtil.removeClass(currentSelection, 'leaflet-control-geocoder-ban-selected'); + el = direction > 0 ? currentSelection.previousElementSibling : currentSelection.nextElementSibling; + } + if (el) { + L.DomUtil.addClass(el, 'leaflet-control-geocoder-ban-selected'); + } + }, + _keyup: function(e) { + switch (e.keyCode) { + case 27: + // escape + this._collapse(); + break; + case 38: + // down + this._moveSelection(1); + L.DomEvent.preventDefault(e); + break; + case 40: + // up + this._moveSelection(-1); + L.DomEvent.preventDefault(e); + break; + case 13: + // enter + var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected') + if (s.length) { + this._geocodeResult(s[0]._geocodedFeatures); + } + break; + default: + if (this._input.value) { + var params = {q: this._input.value, limit: this.options.resultsNumber}; + var t = this; + if (this._setTimeout) { + clearTimeout(this._setTimeout); + } + //avoid responses collision if typing quickly + this._setTimeout = setTimeout(function () { + getJSON(t.options.serviceUrl, params, t._displayResults(t)); + }, this.options.minIntervalBetweenRequests); + } else { + this._clearResults(); + } + } + }, + _clearResults () { + while (this._alts.firstChild) { + this._alts.removeChild(this._alts.firstChild); + } + }, + _displayResults: function (t) { + t._clearResults(); + return function (res) { + var features = res.features; + L.DomUtil.removeClass(t._alts, 'leaflet-control-geocoder-ban-alternatives-minimized'); + for (var i = 0; i < Math.min(features.length, t.options.resultsNumber); i++) { + t._alts.appendChild(t._createAlt(features[i], i)); + } + } + }, + _createAlt: function (feature, index) { + var li = L.DomUtil.create('li', ''), + a = L.DomUtil.create('a', '', li); + li.setAttribute('data-result-index', index); + a.innerHTML = feature.properties.label; + li._geocodedFeatures = feature + clickHandler = function (e) { + this._collapse(); + this._geocodeResult(feature); + } + mouseOverHandler = function (e) { + var s = document.getElementsByClassName('leaflet-control-geocoder-ban-selected'); + if (s.length) { + L.DomUtil.removeClass(s[0], 'leaflet-control-geocoder-ban-selected'); + } + L.DomUtil.addClass(li, 'leaflet-control-geocoder-ban-selected'); + } + mouseOutHandler = function (e) { + L.DomUtil.removeClass(li, 'leaflet-control-geocoder-ban-selected'); + } + L.DomEvent.on(li, 'click', clickHandler, this); + L.DomEvent.on(li, 'mouseover', mouseOverHandler, this); + L.DomEvent.on(li, 'mouseout', mouseOutHandler, this); + return li; + }, + _geocodeResult: function (feature) { + this._collapse(); + this.fire('markgeocode', {geocode: feature}); + }, + markGeocode: function (result) { + var f = result.geocode; + var latlng = [f.geometry.coordinates[1], f.geometry.coordinates[0]] + this._map.setView(latlng, 14) + this._geocodeMarker = new L.Marker(latlng) + .bindPopup(f.properties.label) + .addTo(this._map) + .openPopup(); + } +}); + +L.geocoderBAN = function (options) { + return new L.GeocoderBAN(options); +}; + +function getJSON (url, params, callback) { + var xmlHttp = new XMLHttpRequest(); + xmlHttp.onreadystatechange = function () { + if (xmlHttp.readyState !== 4){ + return; + } + if (xmlHttp.status !== 200 && xmlHttp.status !== 304){ + callback(''); + return; + } + callback(JSON.parse(xmlHttp.response)); + }; + xmlHttp.open('GET', url + L.Util.getParamString(params), true); + xmlHttp.setRequestHeader('Accept', 'application/json'); + xmlHttp.send(null); +}