this.L = this.L || {}; this.L.Control = this.L.Control || {}; this.L.Control.Geocoder = (function (L) { 'use strict'; L = L && L.hasOwnProperty('default') ? L['default'] : L; var lastCallbackId = 0; // Adapted from handlebars.js // https://github.com/wycats/handlebars.js/ var badChars = /[&<>"'`]/g; var possible = /[&<>"'`]/; var escape = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; function escapeChar(chr) { return escape[chr]; } function htmlEscape(string) { if (string == null) { return ''; } else if (!string) { return string + ''; } // Force a string conversion as this will be done by the append regardless and // the regex test will do this transparently behind the scenes, causing issues if // an object's to string has escaped characters in it. string = '' + string; if (!possible.test(string)) { return string; } return string.replace(badChars, escapeChar); } function jsonp(url, params, callback, context, jsonpParam) { var callbackId = '_l_geocoder_' + lastCallbackId++; params[jsonpParam || 'callback'] = callbackId; window[callbackId] = L.Util.bind(callback, context); var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url + L.Util.getParamString(params); script.id = callbackId; document.getElementsByTagName('head')[0].appendChild(script); } 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); } function template(str, data) { return str.replace(/\{ *([\w_]+) *\}/g, function(str, key) { var value = data[key]; if (value === undefined) { value = ''; } else if (typeof value === 'function') { value = value(data); } return htmlEscape(value); }); } var Control = { class: L.Control.extend({ options: { showResultIcons: false, collapsed: true, expand: 'touch', // options: touch, click, anythingelse position: 'topleft', placeholder: 'Cerca indirizzo...', errorMessage: 'Nessuna risultato trovato.', suggestMinLength: 3, suggestTimeout: 250, defaultMarkGeocode: true }, includes: L.Evented.prototype || L.Mixin.Events, initialize: function(options) { L.Util.setOptions(this, options); if (!this.options.geocoder) { this.options.geocoder = new arcgis.class(); } this._requestCount = 0; }, onAdd: function(map) { var className = 'leaflet-control-geocoder', container = L.DomUtil.create('div', className + ' leaflet-bar'), icon = L.DomUtil.create('button', className + '-icon', container), form = (this._form = L.DomUtil.create('div', className + '-form', container)), input; this._map = map; this._container = container; icon.innerHTML = ' '; icon.type = 'button'; input = this._input = L.DomUtil.create('input', '', form); input.type = 'text'; input.placeholder = this.options.placeholder; this._errorElement = L.DomUtil.create('div', className + '-form-no-error', container); this._errorElement.innerHTML = this.options.errorMessage; this._alts = L.DomUtil.create( 'ul', className + '-alternatives leaflet-control-geocoder-alternatives-minimized', container ); L.DomEvent.disableClickPropagation(this._alts); L.DomEvent.addListener(input, 'keydown', this._keydown, this); if (this.options.geocoder.suggest) { L.DomEvent.addListener(input, 'input', this._change, this); } L.DomEvent.addListener( input, 'blur', function() { if (this.options.collapsed && !this._preventBlurCollapse) { this._collapse(); } this._preventBlurCollapse = false; }, this ); if (this.options.collapsed) { if (this.options.expand === 'click') { L.DomEvent.addListener( container, 'click', function(e) { if (e.button === 0 && e.detail !== 2) { this._toggle(); } }, this ); } else if (L.Browser.touch && this.options.expand === 'touch') { L.DomEvent.addListener( container, 'touchstart mousedown', function(e) { this._toggle(); e.preventDefault(); // mobile: clicking focuses the icon, so UI expands and immediately collapses e.stopPropagation(); }, this ); } else { L.DomEvent.addListener(container, 'mouseover', this._expand, this); L.DomEvent.addListener(container, 'mouseout', this._collapse, this); this._map.on('movestart', this._collapse, this); } } else { this._expand(); if (L.Browser.touch) { L.DomEvent.addListener( container, 'touchstart', function() { this._geocode(); }, this ); } else { L.DomEvent.addListener( container, 'click', function() { this._geocode(); }, this ); } } if (this.options.defaultMarkGeocode) { this.on('markgeocode', this.markGeocode, this); } this.on( 'startgeocode', function() { L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-throbber'); }, this ); this.on( 'finishgeocode', function() { L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-throbber'); }, this ); L.DomEvent.disableClickPropagation(container); return container; }, _geocodeResult: function(results, suggest) { if (!suggest && results.length === 1) { this._geocodeResultSelected(results[0]); } else if (results.length > 0) { this._alts.innerHTML = ''; this._results = results; L.DomUtil.removeClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized'); for (var i = 0; i < results.length; i++) { this._alts.appendChild(this._createAlt(results[i], i)); } } else { L.DomUtil.addClass(this._errorElement, 'leaflet-control-geocoder-error'); } }, markGeocode: function(result) { result = result.geocode || result; this._map.fitBounds(result.bbox); if (this._geocodeMarker) { this._map.removeLayer(this._geocodeMarker); } this._geocodeMarker = new L.Marker(result.center) .bindPopup(result.html || result.name) .addTo(this._map) .openPopup(); return this; }, _geocode: function(suggest) { var requestCount = ++this._requestCount, mode = suggest ? 'suggest' : 'geocode', eventData = { input: this._input.value }; this._lastGeocode = this._input.value; if (!suggest) { this._clearResults(); } this.fire('start' + mode, eventData); this.options.geocoder[mode]( this._input.value, function(results) { if (requestCount === this._requestCount) { eventData.results = results; this.fire('finish' + mode, eventData); this._geocodeResult(results, suggest); } }, this ); }, _geocodeResultSelected: function(result) { this.fire('markgeocode', { geocode: result }); }, _toggle: function() { if (L.DomUtil.hasClass(this._container, 'leaflet-control-geocoder-expanded')) { this._collapse(); } else { this._expand(); } }, _expand: function() { L.DomUtil.addClass(this._container, 'leaflet-control-geocoder-expanded'); this._input.select(); this.fire('expand'); }, _collapse: function() { L.DomUtil.removeClass(this._container, 'leaflet-control-geocoder-expanded'); L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized'); L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error'); this._input.blur(); // mobile: keyboard shouldn't stay expanded this.fire('collapse'); }, _clearResults: function() { L.DomUtil.addClass(this._alts, 'leaflet-control-geocoder-alternatives-minimized'); this._selection = null; L.DomUtil.removeClass(this._errorElement, 'leaflet-control-geocoder-error'); }, _createAlt: function(result, index) { var li = L.DomUtil.create('li', ''), a = L.DomUtil.create('a', '', li), icon = this.options.showResultIcons && result.icon ? L.DomUtil.create('img', '', a) : null, text = result.html ? undefined : document.createTextNode(result.name), mouseDownHandler = function mouseDownHandler(e) { // In some browsers, a click will fire on the map if the control is // collapsed directly after mousedown. To work around this, we // wait until the click is completed, and _then_ collapse the // control. Messy, but this is the workaround I could come up with // for #142. this._preventBlurCollapse = true; L.DomEvent.stop(e); this._geocodeResultSelected(result); L.DomEvent.on( li, 'click', function() { if (this.options.collapsed) { this._collapse(); } else { this._clearResults(); } }, this ); }; if (icon) { icon.src = result.icon; } li.setAttribute('data-result-index', index); if (result.html) { a.innerHTML = a.innerHTML + result.html; } else { a.appendChild(text); } // Use mousedown and not click, since click will fire _after_ blur, // causing the control to have collapsed and removed the items // before the click can fire. L.DomEvent.addListener(li, 'mousedown touchstart', mouseDownHandler, this); return li; }, _keydown: function(e) { var _this = this, select = function select(dir) { if (_this._selection) { L.DomUtil.removeClass(_this._selection, 'leaflet-control-geocoder-selected'); _this._selection = _this._selection[dir > 0 ? 'nextSibling' : 'previousSibling']; } if (!_this._selection) { _this._selection = _this._alts[dir > 0 ? 'firstChild' : 'lastChild']; } if (_this._selection) { L.DomUtil.addClass(_this._selection, 'leaflet-control-geocoder-selected'); } }; switch (e.keyCode) { // Escape case 27: if (this.options.collapsed) { this._collapse(); } break; // Up case 38: select(-1); break; // Up case 40: select(1); break; // Enter case 13: if (this._selection) { var index = parseInt(this._selection.getAttribute('data-result-index'), 10); this._geocodeResultSelected(this._results[index]); this._clearResults(); } else { this._geocode(); } break; } }, _change: function() { var v = this._input.value; if (v !== this._lastGeocode) { clearTimeout(this._suggestTimeout); if (v.length >= this.options.suggestMinLength) { this._suggestTimeout = setTimeout( L.bind(function() { this._geocode(true); }, this), this.options.suggestTimeout ); } else { this._clearResults(); } } } }), factory: function(options) { return new L.Control.Geocoder(options); } }; var Google = { class: L.Class.extend({ options: { serviceUrl: 'https://maps.googleapis.com/maps/api/geocode/json', geocodingQueryParams: {}, reverseQueryParams: {} }, initialize: function(key, options) { this._key = key; L.setOptions(this, options); // Backwards compatibility this.options.serviceUrl = this.options.service_url || this.options.serviceUrl; }, geocode: function(query, cb, context) { var params = { address: query }; if (this._key && this._key.length) { params.key = this._key; } params = L.Util.extend(params, this.options.geocodingQueryParams); getJSON(this.options.serviceUrl, params, function(data) { var results = [], loc, latLng, latLngBounds; if (data.results && data.results.length) { for (var i = 0; i <= data.results.length - 1; i++) { loc = data.results[i]; latLng = L.latLng(loc.geometry.location); latLngBounds = L.latLngBounds( L.latLng(loc.geometry.viewport.northeast), L.latLng(loc.geometry.viewport.southwest) ); results[i] = { name: loc.formatted_address, bbox: latLngBounds, center: latLng, properties: loc.address_components }; } } cb.call(context, results); }); }, reverse: function(location, scale, cb, context) { var params = { latlng: encodeURIComponent(location.lat) + ',' + encodeURIComponent(location.lng) }; params = L.Util.extend(params, this.options.reverseQueryParams); if (this._key && this._key.length) { params.key = this._key; } getJSON(this.options.serviceUrl, params, function(data) { var results = [], loc, latLng, latLngBounds; if (data.results && data.results.length) { for (var i = 0; i <= data.results.length - 1; i++) { loc = data.results[i]; latLng = L.latLng(loc.geometry.location); latLngBounds = L.latLngBounds( L.latLng(loc.geometry.viewport.northeast), L.latLng(loc.geometry.viewport.southwest) ); results[i] = { name: loc.formatted_address, bbox: latLngBounds, center: latLng, properties: loc.address_components }; } } cb.call(context, results); }); } }), factory: function(key, options) { return new L.Control.Geocoder.Google(key, options); } }; var ArcGis = { class: L.Class.extend({ options: { service_url: 'http://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer' }, initialize: function(accessToken, options) { L.setOptions(this, options); this._accessToken = accessToken; }, geocode: function(query, cb, context) { var params = { SingleLine: query, outFields: 'Addr_Type', forStorage: false, maxLocations: 10, f: 'json' }; if (this._key && this._key.length) { params.token = this._key; } getJSON(this.options.service_url + '/findAddressCandidates', params, function(data) { var results = [], loc, latLng, latLngBounds; if (data.candidates && data.candidates.length) { for (var i = 0; i <= data.candidates.length - 1; i++) { loc = data.candidates[i]; latLng = L.latLng(loc.location.y, loc.location.x); latLngBounds = L.latLngBounds( L.latLng(loc.extent.ymax, loc.extent.xmax), L.latLng(loc.extent.ymin, loc.extent.xmin) ); results[i] = { name: loc.address, bbox: latLngBounds, center: latLng }; } } cb.call(context, results); }); }, suggest: function(query, cb, context) { return this.geocode(query, cb, context); }, reverse: function(location, scale, cb, context) { var params = { location: encodeURIComponent(location.lng) + ',' + encodeURIComponent(location.lat), distance: 100, f: 'json' }; getJSON(this.options.service_url + '/reverseGeocode', params, function(data) { var result = [], loc; if (data && !data.error) { loc = L.latLng(data.location.y, data.location.x); result.push({ name: data.address.Match_addr, center: loc, bounds: L.latLngBounds(loc, loc) }); } cb.call(context, result); }); } }), factory: function(accessToken, options) { return new L.Control.Geocoder.ArcGis(accessToken, options); } }; var Geocoder = L.Util.extend(Control.class, { Google: Google.class, google: Google.factory, ArcGis: ArcGis.class, arcgis: ArcGis.factory, }); L.Util.extend(L.Control, { Geocoder: Geocoder, geocoder: Control.factory }); return Geocoder; }(L)); //# sourceMappingURL=Control.Geocoder.js.map