diff --git a/assets/src/modules/Search.js b/assets/src/modules/Search.js index 0885778aa8..547f2f32f6 100644 --- a/assets/src/modules/Search.js +++ b/assets/src/modules/Search.js @@ -5,6 +5,8 @@ * @license MPL-2.0 */ +const AUTOCOMPLETE_MIN_LENGTH = 3; + /** * @class * @name Search @@ -55,6 +57,20 @@ export default class Search { $('#lizmap-search, #lizmap-search-close').removeClass('open'); } + /** + * Returns a debounced version of fn that fires after delay ms of inactivity + * @param {Function} fn + * @param {number} delay - milliseconds + * @returns {Function} + */ + _debounce(fn, delay) { + let timer; + return (...args) => { + clearTimeout(timer); + timer = setTimeout(() => fn(...args), delay); + }; + } + /** * Start the external search */ @@ -94,10 +110,57 @@ export default class Search { } /** - * PRIVATE method: addExternalSearch - * add external search capability + * PRIVATE method: fire a lizmapFts search request and display results * @param {object} searchConfig - search configuration - * @returns {boolean} external search is in the user interface + * @param {OpenLayers.Bounds} extent - WGS84 map extent for filtering + */ + _performFtsSearch(searchConfig, extent) { + this._startExternalSearch(); + + var labrex = this._getHighlightRegEx(); + $.get(searchConfig.url + , { + "repository": globalThis['lizUrls'].params.repository, + "project": globalThis['lizUrls'].params.project, + "query": $('#search-query').val(), + } + , (results) => { + var text = ''; + var count = 0; + + for (var ftsId in results) { + var ftsLayerResult = results[ftsId]; + text += '
  • ' + ftsLayerResult.search_name + ''; + text += '
  • '; + } + + if (count != 0 && text != '') { + this._updateExternalSearch(text); + } + else { + this._updateExternalSearch('
  • ' + lizDict['externalsearch.mapdata'] + '
  • '); + } + }, 'json'); + } + + /** + * PRIVATE method: add lizmapFts search capability with autocomplete + * @param {object} searchConfig - search configuration + * @returns {boolean} search is in the user interface */ _addSearch(searchConfig) { if (searchConfig.type == 'externalSearch') { @@ -112,58 +175,158 @@ export default class Search { var extent = new OpenLayers.Bounds(this._lizmap3.map.maxExtent.toArray()); extent.transform(this._map.getView().getProjection().getCode(), wgs84); + const autoSearch = () => { + if ($('#search-query').val().length < AUTOCOMPLETE_MIN_LENGTH) { + $('#lizmap-search .items').html(''); + $('#lizmap-search, #lizmap-search-close').removeClass('open'); + return; + } + this._performFtsSearch(searchConfig, extent); + }; + $('#nominatim-search').submit(() => { - this._startExternalSearch(); - - // Format answers to highlight searched keywords - var labrex = this._getHighlightRegEx(); - $.get(searchConfig.url - , { - "repository": globalThis['lizUrls'].params.repository, - "project": globalThis['lizUrls'].params.project, - "query": $('#search-query').val(), - } - , (results) => { - var text = ''; - var count = 0; - - // Loop through results - for (var ftsId in results) { - var ftsLayerResult = results[ftsId]; - text += '
  • ' + ftsLayerResult.search_name + ''; - text += '
  • '; - } - - if (count != 0 && text != '') { - this._updateExternalSearch(text); + if (count == 0 || text == '') { + text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; + } + this._updateExternalSearch('
  • OpenStreetMap
  • '); + }, 'json'); + break; + case 'ign': { + if (searchQuery.length < 3 || searchQuery.length > 200) { + lizMap.addMessage(lizDict['externalsearch.ignlimit'], 'warning', true); + break; + } + $.get(service + , { + "text": searchQuery, + "type": 'StreetAddress', + "maximumResponses": 10, + "bbox": extent.toBBOX() } - else { - this._updateExternalSearch('
  • ' + lizDict['externalsearch.mapdata'] + '
  • '); + , data => { + let text = ''; + let count = 0; + for (const result of data.results) { + var lab = result.fulltext.replace(labrex, '$1'); + text += ` +
  • + + ${lab} + +
  • `; + count++; + } + if (count == 0 || text == '') { + text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; + } + this._updateExternalSearch('
  • IGN
  • '); + }, 'json'); + break; + } + case 'google': + service.geocode({ + 'address': searchQuery, + 'bounds': new google.maps.LatLngBounds( + new google.maps.LatLng(extent.top, extent.left), + new google.maps.LatLng(extent.bottom, extent.right) + ) + }, (results, status) => { + if (status == google.maps.GeocoderStatus.OK) { + var text = ''; + var count = 0; + for (const address of results) { + if (count > 9) { + return false; + } + var bbox = []; + if (address.geometry.viewport) { + bbox = [ + address.geometry.viewport.getSouthWest().lng(), + address.geometry.viewport.getSouthWest().lat(), + address.geometry.viewport.getNorthEast().lng(), + address.geometry.viewport.getNorthEast().lat() + ]; + } else if (address.geometry.bounds) { + bbox = [ + address.geometry.bounds.getSouthWest().lng(), + address.geometry.bounds.getSouthWest().lat(), + address.geometry.bounds.getNorthEast().lng(), + address.geometry.bounds.getNorthEast().lat() + ]; + } + if (bbox.length != 4) { + return false; + } + bbox = new OpenLayers.Bounds(bbox); + if (extent.intersectsBounds(bbox)) { + var lab = address.formatted_address.replace(labrex, '$1'); + var wkt = 'POINT(' + address.geometry.location.lng() + ' ' + address.geometry.location.lat() + ')'; + text += '
  • ' + lab + '
  • '; + count++; + } + } + if (count == 0 || text == '') { + text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; + } + this._updateExternalSearch('
  • Google
  • '); + } else { + this._updateExternalSearch('
  • Google
  • '); } - }, 'json'); - return false; - }); - - return true; + }); + break; + } } /** - * PRIVATE method: addExternalSearch - * add external search capability + * PRIVATE method: add external geocoder search capability with autocomplete * @param {object} searchConfig - search configuration * @returns {boolean} external search is in the user interface */ @@ -201,133 +364,22 @@ export default class Search { return false; } - $('#nominatim-search').submit(() => { - this._startExternalSearch(); - - // Format answers to highlight searched keywords - var labrex = this._getHighlightRegEx(); - const searchQuery = document.getElementById('search-query').value; - switch (searchConfig.service) { - case 'nominatim': - $.get(service - , { "query": searchQuery, "bbox": extent.toBBOX() } - , data => { - var text = ''; - var count = 0; - for (const address of data) { - if (count > 9) { - return false; - } - if (!address.boundingbox) { - return true; - } - - var bbox = [ - address.boundingbox[2], - address.boundingbox[0], - address.boundingbox[3], - address.boundingbox[1] - ]; - bbox = new OpenLayers.Bounds(bbox); - if (extent.intersectsBounds(bbox)) { - var lab = address.display_name.replace(labrex, '$1'); - text += `
  • ${lab}
  • `; - count++; - } - } - if (count == 0 || text == '') { - text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; - } - this._updateExternalSearch('
  • OpenStreetMap
  • '); - }, 'json'); - break; - case 'ign': { - if (searchQuery.length < 3 || searchQuery.length > 200) { - lizMap.addMessage(lizDict['externalsearch.ignlimit'], 'warning', true); - break; - } - $.get(service - , { - "text": searchQuery, - "type": 'StreetAddress', - "maximumResponses": 10, - "bbox": extent.toBBOX() - } - , data => { - let text = ''; - let count = 0; - for (const result of data.results) { - var lab = result.fulltext.replace(labrex, '$1'); - text += ` -
  • - - ${lab} - -
  • `; - count++; - } - if (count == 0 || text == '') { - text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; - } - this._updateExternalSearch('
  • IGN
  • '); - }, 'json'); - break; - } - case 'google': - service.geocode({ - 'address': searchQuery, - 'bounds': new google.maps.LatLngBounds( - new google.maps.LatLng(extent.top, extent.left), - new google.maps.LatLng(extent.bottom, extent.right) - ) - }, (results, status) => { - if (status == google.maps.GeocoderStatus.OK) { - var text = ''; - var count = 0; - for (const address of results) { - if (count > 9) { - return false; - } - var bbox = []; - if (address.geometry.viewport) { - bbox = [ - address.geometry.viewport.getSouthWest().lng(), - address.geometry.viewport.getSouthWest().lat(), - address.geometry.viewport.getNorthEast().lng(), - address.geometry.viewport.getNorthEast().lat() - ]; - } else if (address.geometry.bounds) { - bbox = [ - address.geometry.bounds.getSouthWest().lng(), - address.geometry.bounds.getSouthWest().lat(), - address.geometry.bounds.getNorthEast().lng(), - address.geometry.bounds.getNorthEast().lat() - ]; - } - if (bbox.length != 4) { - return false; - } - bbox = new OpenLayers.Bounds(bbox); - if (extent.intersectsBounds(bbox)) { - var lab = address.formatted_address.replace(labrex, '$1'); - var wkt = 'POINT(' + address.geometry.location.lng() + ' ' + address.geometry.location.lat() + ')'; - text += '
  • ' + lab + '
  • '; - count++; - } - } - if (count == 0 || text == '') { - text = '
  • ' + lizDict['externalsearch.notfound'] + '
  • '; - } - this._updateExternalSearch('
  • Google
  • '); - } else { - this._updateExternalSearch('
  • Google
  • '); - } - }); - break; + const autoSearch = () => { + if ($('#search-query').val().length < AUTOCOMPLETE_MIN_LENGTH) { + $('#lizmap-search .items').html(''); + $('#lizmap-search, #lizmap-search-close').removeClass('open'); + return; } + this._performExternalSearch(searchConfig, service, extent); + }; + + $('#nominatim-search').submit(() => { + this._performExternalSearch(searchConfig, service, extent); return false; }); + $('#search-query').on('input', this._debounce(autoSearch, 100)); + return true; } diff --git a/lizmap/modules/view/templates/map_headermenu.tpl b/lizmap/modules/view/templates/map_headermenu.tpl index 558fac7a40..9fba6f7312 100644 --- a/lizmap/modules/view/templates/map_headermenu.tpl +++ b/lizmap/modules/view/templates/map_headermenu.tpl @@ -2,7 +2,7 @@