update leaflet-search

This commit is contained in:
Norbert Renner 2014-05-14 15:31:54 +02:00
parent cec17a1c26
commit 09932b4b96
14 changed files with 284 additions and 124 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "leaflet-search", "name": "leaflet-search",
"version": "1.4.7", "version": "1.5.1",
"main": "leaflet-search.js", "main": "leaflet-search.js",
"ignore": [ "ignore": [
"**/.*", "**/.*",
@ -10,11 +10,11 @@
"examples" "examples"
], ],
"homepage": "https://github.com/stefanocudini/leaflet-search", "homepage": "https://github.com/stefanocudini/leaflet-search",
"_release": "1.4.7", "_release": "1.5.1",
"_resolution": { "_resolution": {
"type": "version", "type": "version",
"tag": "v1.4.7", "tag": "v1.5.1",
"commit": "411f216e1f407da9ef61047832b227c19ed47fab" "commit": "75ab103b838a966692cfb6cb3dc78c3b0f306eee"
}, },
"_source": "git://github.com/stefanocudini/leaflet-search.git", "_source": "git://github.com/stefanocudini/leaflet-search.git",
"_target": "*", "_target": "*",

View file

@ -1,16 +1,17 @@
Leaflet.Control.Search Leaflet.Control.Search
============ ============
#What A leaflet control that search markers/features location by custom property.<br />
A leaflet control that search markers/features location by cutstom property. With ajax/jsonp autocompletion and JSON fields filter/remap
With ajax/jsonp autocompletion and json fields re-mapping
Tested in Leaflet 0.6.4 Copyright 2014 [Stefano Cudini](http://labs.easyblog.it/stefano-cudini/)
Tested in Leaflet 0.7.2
#Where #Where
**Demos:** **Demo online:**
[labs.easyblog.it/maps/leaflet-search](http://labs.easyblog.it/maps/leaflet-search/) [labs.easyblog.it/maps/leaflet-search](http://labs.easyblog.it/maps/leaflet-search/)
**Source code:** **Source code:**
@ -19,36 +20,49 @@ Tested in Leaflet 0.6.4
[NPM](https://npmjs.org/package/leaflet-search) [NPM](https://npmjs.org/package/leaflet-search)
[Atmosphere](https://atmosphere.meteor.com/package/leaflet-search) [Atmosphere](https://atmosphere.meteor.com/package/leaflet-search)
#How #Build
Insert leaflet-search.css styles to your css page
Since Version 1.4.7 this plugin support [Grunt](http://gruntjs.com/) for building process.
Therefore the deployment require [NPM](https://npmjs.org/) installed in your system.
After you've made sure to have npm working, run this in command line:
```bash
npm install
grunt
```
#Examples
(require src/leaflet-search.css)
Adding the search control to the map: Adding the search control to the map:
```javascript
```
map.addControl( new L.Control.Search({layer: searchLayer}) ); map.addControl( new L.Control.Search({layer: searchLayer}) );
//searchLayer if a L.LayerGroup contains searched markers //searchLayer is a L.LayerGroup contains searched markers
```
short way:
``` ```
Short way:
```javascript
var map = new L.Map('map', { searchControl: {layer: searchLayer} }); var map = new L.Map('map', { searchControl: {layer: searchLayer} });
``` ```
other examples: #Advanced Examples
```
//ajax request to search.php for retrieve elements locations Ajax request to search.php for retrieve elements locations:
```javascript
map.addControl( new L.Control.Search({url: 'search.php?q={s}'}) ); map.addControl( new L.Control.Search({url: 'search.php?q={s}'}) );
```
Request to third party JSONP service, implements Geocode Searching using OSM API:
//jsonp request to 3rd party service, implements Geocode Searching using OSM API ```javascript
map.addControl( new L.Control.Search({ map.addControl( new L.Control.Search({
url: 'http://nominatim.openstreetmap.org/search?format=json&q={s}', url: 'http://nominatim.openstreetmap.org/search?format=json&q={s}',
jsonpParam: 'json_callback', jsonpParam: 'json_callback',
propertyName: 'display_name', propertyName: 'display_name',
propertyLoc: ['lat','lon'] propertyLoc: ['lat','lon']
}) ); }) );
```
Search and color features vector in GeoJSON layer:
//geojson layer, search and color feature vector ```javascript
var searchControl = new L.Control.Search({layer: geojsonLayer, circleLocation:false}); var searchControl = new L.Control.Search({layer: geojsonLayer, circleLocation:false});
searchControl.on('search_locationfound', function(e) { searchControl.on('search_locationfound', function(e) {
@ -63,3 +77,22 @@ searchControl.on('search_locationfound', function(e) {
map.addControl(searchControl); map.addControl(searchControl);
``` ```
Static data source:
```
var data = [
{"loc":[41.575330,13.102411], "title":"aquamarine"},
{"loc":[41.575730,13.002411], "title":"black"},
{"loc":[41.219190,13.062145], "title":"cyan"}
];
map.addControl(new L.Control.Search({
markerLocation: true,
callData: function(text, callResponse) {
//here can use custom criteria or merge data from multiple layers
callResponse(data);
}
}) );
```

View file

@ -1,27 +1,59 @@
Tasks found in: src/leaflet-search.js Tasks found in: src/leaflet-search.js
[Line: 22] [low] //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer [Line: 22] [low] //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
[Line: 23] [low] //TODO implement can do research on multiple sources [Line: 23] [low] //TODO implement can do research on multiple sources
[Line: 26] [low] //TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title" [Line: 38] [low] //TODO add option for persist markerLoc after collapse!
[Line: 48] [low] //TODO add option collapsed, like control.layers
[Line: 53] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 57] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 60] [low] //TODO change structure of _recordsCache
[Line: 342] [low] //TODO add option for case sesitive search, also showLocation
[Line: 345] [low] //TODO use .filter or .map
[Line: 407] [low] //TODO throw new Error("propertyName '"+propName+"' not found in JSON data");
[Line: 412] [low] //TODO remove script node after call run
[Line: 421] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp
[Line: 441] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax
[Line: 504] [low] //TODO implements autype without selection(useful for mobile device)
[Line: 604] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 608] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 611] [low] //TODO change structure of _recordsCache
[Line: 662] [low] //TODO refact _handleAutoresize now is not accurate
[Line: 758] [low] //TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
[Line: 779] [low] //TODO add custom icon!
[Line: 787] [low] //TODO add inner circle
[Line: 843] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
[Line: 865] [low] //TODO use create event 'animateEnd' in SearchMarker
[Line: 50] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
[Line: 51] [med] //FIXME option condition problem {autoCollapse: false }
[Line: 358] [med] //FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
[Line: 731] [med] //FIXME if collapse in _handleSubmit hide _markerLoc!
[Line: 761] [med] //FIXME autoCollapse option hide this._markerLoc before that visualized!!
Tasks found in: src/leaflet-search_collapsed.js
[Line: 22] [low] //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
[Line: 23] [low] //TODO implement can do research on multiple sources
[Line: 37] [low] //TODO add option for persist markerLoc after collapse! [Line: 37] [low] //TODO add option for persist markerLoc after collapse!
[Line: 47] [low] //TODO add option collapsed, like control.layers [Line: 47] [low] //TODO add option collapsed, like control.layers
[Line: 296] [low] //TODO add option for case sesitive search, also showLocation [Line: 52] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 299] [low] //TODO use .filter or .map [Line: 56] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 361] [low] //TODO verify json[n].hasOwnProperty(propName) [Line: 59] [low] //TODO change structure of _recordsCache
[Line: 367] [low] //TODO remove script node after call run [Line: 87] [low] // TODO: touch
[Line: 376] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp [Line: 358] [low] //TODO add option for case sesitive search, also showLocation
[Line: 396] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax [Line: 361] [low] //TODO use .filter or .map
[Line: 458] [low] //TODO implements autype without selection(useful for mobile device) [Line: 423] [low] //TODO throw new Error("propertyName '"+propName+"' not found in JSON data");
[Line: 558] [low] //TODO important optimization!!! always append data in this._recordsCache [Line: 428] [low] //TODO remove script node after call run
[Line: 562] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results.. [Line: 437] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp
[Line: 565] [low] //TODO change structure of _recordsCache [Line: 457] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax
[Line: 616] [low] //TODO refact _handleAutoresize now is not accurate [Line: 520] [low] //TODO implements autype without selection(useful for mobile device)
[Line: 712] [low] //TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')... [Line: 620] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 733] [low] //TODO add custom icon! [Line: 624] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 741] [low] //TODO add inner circle [Line: 627] [low] //TODO change structure of _recordsCache
[Line: 797] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs [Line: 678] [low] //TODO refact _handleAutoresize now is not accurate
[Line: 819] [low] //TODO use create event 'animateEnd' in SearchMarker [Line: 774] [low] //TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
[Line: 795] [low] //TODO add custom icon!
[Line: 803] [low] //TODO add inner circle
[Line: 859] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
[Line: 881] [low] //TODO use create event 'animateEnd' in SearchMarker
[Line: 49] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location [Line: 49] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
[Line: 50] [med] //FIXME option condition problem {autoCollapse: false } [Line: 50] [med] //FIXME option condition problem {autoCollapse: false }
[Line: 312] [med] //FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords [Line: 374] [med] //FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
[Line: 685] [med] //FIXME if collapse in _handleSubmit hide _markerLoc! [Line: 747] [med] //FIXME if collapse in _handleSubmit hide _markerLoc!
[Line: 715] [med] //FIXME autoCollapse option hide this._markerLoc before that visualized!! [Line: 777] [med] //FIXME autoCollapse option hide this._markerLoc before that visualized!!

View file

@ -1,6 +1,6 @@
{ {
"name": "leaflet-search", "name": "leaflet-search",
"version": "1.4.6", "version": "1.5.1",
"main": "leaflet-search.js", "main": "leaflet-search.js",
"ignore": [ "ignore": [
"**/.*", "**/.*",

View file

@ -1,5 +1,5 @@
/* /*
* Leaflet Search Control v1.4.7 - 2014-01-04 * Leaflet Search Control v1.5.1 - 2014-05-12
* *
* Copyright 2014 Stefano Cudini * Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com * stefano.cudini@gmail.com

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/* /*
* Leaflet Search Control v1.4.7 - 2014-01-04 * Leaflet Search Control v1.5.1 - 2014-05-12
* *
* Copyright 2014 Stefano Cudini * Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com * stefano.cudini@gmail.com

View file

@ -1,5 +1,5 @@
/* /*
* Leaflet Search Control v1.4.7 - 2014-01-04 * Leaflet Search Control v1.5.1 - 2014-05-12
* *
* Copyright 2014 Stefano Cudini * Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com * stefano.cudini@gmail.com

View file

@ -1,5 +1,5 @@
/* /*
* Leaflet Search Control v1.4.7 - 2014-01-04 * Leaflet Search Control v1.5.1 - 2014-05-12
* *
* Copyright 2014 Stefano Cudini * Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com * stefano.cudini@gmail.com

View file

@ -1,5 +1,5 @@
/* /*
* Leaflet Search Control v1.4.7 - 2014-01-04 * Leaflet Search Control v1.5.1 - 2014-05-12
* *
* Copyright 2014 Stefano Cudini * Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com * stefano.cudini@gmail.com
@ -14,32 +14,32 @@
* git@github.com:stefanocudini/leaflet-search.git * git@github.com:stefanocudini/leaflet-search.git
* *
*/ */
(function() { (function() {
L.Control.Search = L.Control.extend({ L.Control.Search = L.Control.extend({
includes: L.Mixin.Events, includes: L.Mixin.Events,
// //
// Name Data passed Description // Name Data passed Description
// //
//Managed Events: //Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation // search_locationfound {latlng, title, layer} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed // search_collapsed {} fired after control was collapsed
// //
//Public methods: //Public methods:
// setLayer() L.LayerGroup() set layer search at runtime // setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message // showAlert() 'Text message' Show alert message
// //
options: { options: {
wrapper: '', //container id to insert Search Control
url: '', //url for search by ajax request, ex: "search.php?q={s}" url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback" jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
layer: null, //layer where search markers(is a L.LayerGroup) layer: null, //layer where search markers(is a L.LayerGroup)
callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//TODO implement can do research on multiple sources //TODO implement can do research on multiple sources
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer,
propertyLoc: 'loc', //field name for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) propertyLoc: 'loc', //field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] )
//TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title" // support dotted format: 'prop.subprop.title'
callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param
filterJSON: null, //callback for filtering data to _recordsCache filterJSON: null, //callback for filtering data to _recordsCache
minLength: 1, //minimal text length for autocomplete minLength: 1, //minimal text length for autocomplete
@ -49,6 +49,7 @@ L.Control.Search = L.Control.extend({
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit. tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change autoResize: true, //autoresize on input change
collapsed: true, //collapse search control at startup
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit) autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse! //TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
@ -64,7 +65,18 @@ L.Control.Search = L.Control.extend({
}, },
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false } //FIXME option condition problem {autoCollapse: false }
//
//TODO important optimization!!! always append data in this._recordsCache
// now _recordsCache content is emptied and replaced with new data founded
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
//
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(callData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this mode every record can have a free structure of attributes, only 'loc' is required
initialize: function(options) { initialize: function(options) {
L.Util.setOptions(this, options || {}); L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10; this._inputMinSize = this.options.text ? this.options.text.length : 10;
@ -83,7 +95,10 @@ L.Control.Search = L.Control.extend({
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel'); this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button'); this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert'); this._alert = this._createAlert('search-alert');
if(this.options.collapsed===false)
this.expand();
if(this.options.circleLocation || this.options.markerLocation) if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
@ -91,10 +106,23 @@ L.Control.Search = L.Control.extend({
map.on({ map.on({
// 'layeradd': this._onLayerAddRemove, // 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove // 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize() 'resize': this._handleAutoresize
}, this); }, this);
return this._container; return this._container;
}, },
addTo: function (map) {
if(this.options.wrapper) {
this._container = this.onAdd(map);
this._wrapper = L.DomUtil.get(this.options.wrapper);
this._wrapper.style.position = 'relative';
this._wrapper.appendChild(this._container);
}
else
L.Control.prototype.addTo.call(this, map);
return this;
},
onRemove: function(map) { onRemove: function(map) {
this._recordsCache = {}; this._recordsCache = {};
@ -111,7 +139,22 @@ L.Control.Search = L.Control.extend({
// if( L.stamp(e.layer) != L.stamp(this._layer) ) // if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer); // this.setLayer(e.layer);
// }, // },
_getPath: function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
len = parts.length,
cur = parts[0],
i = 1;
if(len > 0)
while((obj = obj[cur]) && i < len)
cur = parts[i++];
if(obj)
return obj[last];
},
setLayer: function(layer) { //set search layer at runtime setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer() //this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer; this._layer = layer;
@ -147,7 +190,7 @@ L.Control.Search = L.Control.extend({
return this; return this;
}, },
expand: function() { expand: function() {
this._input.style.display = 'block'; this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp'); L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus(); this._input.focus();
@ -159,12 +202,15 @@ L.Control.Search = L.Control.extend({
this._hideTooltip(); this._hideTooltip();
this.cancel(); this.cancel();
this._alert.style.display = 'none'; this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur(); this._input.blur();
this._cancel.style.display = 'none'; if(this.options.collapsed)
L.DomUtil.removeClass(this._container, 'search-exp'); {
//this._markerLoc.hide();//maybe unuseful this._input.style.display = 'none';
this._map.off('dragstart', this.collapse, this); this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
//this._markerLoc.hide();//maybe unuseful
this._map.off('dragstart', this.collapse, this);
}
this.fire('search_collapsed'); this.fire('search_collapsed');
return this; return this;
}, },
@ -364,18 +410,17 @@ L.Control.Search = L.Control.extend({
}, },
_defaultFilterJSON: function(json) { //default callback for filter data _defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {}, var jsonret = {}, i,
propName = this.options.propertyName, propName = this.options.propertyName,
propLoc = this.options.propertyLoc; propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) ) if( L.Util.isArray(propLoc) )
for(var i in json) for(i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] ); jsonret[ this._getPath(json[i],propName) ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else else
for(var n in json) for(i in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] ); jsonret[ this._getPath(json[i],propName) ]= L.latLng( this._getPath(json[i],propLoc) );
//TODO verify json[n].hasOwnProperty(propName) //TODO throw new Error("propertyName '"+propName+"' not found in JSON data");
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret; return jsonret;
}, },
@ -426,7 +471,8 @@ L.Control.Search = L.Control.extend({
}, },
_recordsFromLayer: function() { //return table: key,value from layer _recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {}, var that = this,
retRecords = {},
propName = this.options.propertyName, propName = this.options.propertyName,
loc; loc;
@ -436,17 +482,17 @@ L.Control.Search = L.Control.extend({
if(layer instanceof L.Marker) if(layer instanceof L.Marker)
{ {
if(layer.options.hasOwnProperty(propName)) if(that._getPath(layer.options,propName))
{ {
loc = layer.getLatLng(); loc = layer.getLatLng();
loc.layer = layer; loc.layer = layer;
retRecords[ layer.options[propName] ] = loc; retRecords[ that._getPath(layer.options,propName) ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){ }else if(that._getPath(layer.feature.properties,propName)){
loc = layer.getLatLng(); loc = layer.getLatLng();
loc.layer = layer; loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc; retRecords[ that._getPath(layer.feature.properties,propName) ] = loc;
}else{ }else{
console.log("propertyName '"+propName+"' not found in marker", layer); console.log("propertyName '"+propName+"' not found in marker", layer);
@ -587,7 +633,7 @@ L.Control.Search = L.Control.extend({
L.DomUtil.addClass(this._container, 'search-load'); L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING) if(this.options.callData) //CUSTOM SEARCH CALLBACK
{ {
that = this; that = this;
this.options.callData(inputText, function(jsonraw) { this.options.callData(inputText, function(jsonraw) {
@ -837,7 +883,7 @@ var SearchMarker = L.Marker.extend({
}, tInt); }, tInt);
return this; return this;
} }
}); });
L.Map.addInitHook(function () { L.Map.addInitHook(function () {

View file

@ -37,24 +37,26 @@
<li>Customize tooltip menu</li> <li>Customize tooltip menu</li>
<li>Many options to customize the behavior</li> <li>Many options to customize the behavior</li>
<li>Support search in features collection</li> <li>Support search in features collection</li>
<li>Render Search Box Outside the Leaflet Map</li>
</ul> </ul>
</div> </div>
<div class="contents"> <div class="contents">
<h4>Examples</h4> <h4>Examples</h4>
<ul id="examples"> <ul id="examples">
<li><a href="examples/simple.html">Simple</a></li> <li><a href="examples/simple.html">Simple</a></li>
<li><a href="examples/outside.html">Outside the Map</a></li>
<li><a href="examples/geojson-layer.html">GeoJSON features</a></li> <li><a href="examples/geojson-layer.html">GeoJSON features</a></li>
<li><a href="examples/ajax.html">Ajax</a></li> <li><a href="examples/ajax.html">Ajax</a></li>
<li><a href="examples/jsonp.html">Jsonp</a></li> <li><a href="examples/jsonp.html">Jsonp</a></li>
<li><a href="examples/ajax-jquery.html">Ajax by jQuery</a></li> <li><a href="examples/ajax-jquery.html">Ajax by jQuery</a></li>
<li><a href="examples/calldata.html">Static data</a></li>
<li><a href="examples/jsonp-filtered.html">Jsonp Filtered</a></li> <li><a href="examples/jsonp-filtered.html">Jsonp Filtered</a></li>
<li><a href="examples/ajax-bulk.html">Bulk data</a></li> <li><a href="examples/ajax-bulk.html">Bulk data</a></li>
<li><a href="examples/custom-tip.html">Custom Tip</a></li> <li><a href="examples/custom-tip.html">Custom Tip Item</a></li>
<li><a href="examples/google-geocoding.html">GeoCode Search - Google Geocoding API</a></li> <li><a href="examples/google-geocoding.html">GeoCode Search - Google Geocoding API</a></li>
<li><a href="examples/nominatim.html">GeoCode Search - OSM Nominatim API</a></li> <li><a href="examples/nominatim.html">GeoCode Search - OSM Nominatim API</a></li>
<li><a href="examples/cloudmade.html">GeoCode Search - Cloudmade API</a></li> <li><a href="examples/cloudmade.html">GeoCode Search - Cloudmade API</a></li>
<li><a href="examples/mobile.html">Mobile styled</a></li> <li><a href="examples/mobile.html">Mobile styled</a></li>
<li><a href="examples/twitter.html">Twitter API</a></li>
</ul> </ul>
</div> </div>
<div class="contents"> <div class="contents">

View file

@ -1,6 +1,6 @@
{ {
"name": "leaflet-search", "name": "leaflet-search",
"version": "1.4.7", "version": "1.5.1",
"description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax", "description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax",
"repository": { "repository": {
"type": "git", "type": "git",
@ -23,6 +23,7 @@
}, },
"devDependencies": { "devDependencies": {
"grunt": "~0.4.2", "grunt": "~0.4.2",
"grunt-cli": "~0.1.11",
"grunt-contrib-uglify": "~0.2.7", "grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-concat": "~0.3.0", "grunt-contrib-concat": "~0.3.0",
"grunt-contrib-clean": "~0.5.0", "grunt-contrib-clean": "~0.5.0",

View file

@ -3,6 +3,6 @@
"description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax", "description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax",
"homepage": "http://labs.easyblog.it/maps/leaflet-search/", "homepage": "http://labs.easyblog.it/maps/leaflet-search/",
"author": "Stefano Cudini <stefano.cudini@gmail.com>", "author": "Stefano Cudini <stefano.cudini@gmail.com>",
"version": "1.4.7", "version": "1.5.1",
"git": "https://github.com/stefanocudini/leaflet-search.git" "git": "https://github.com/stefanocudini/leaflet-search.git"
} }

View file

@ -1,29 +1,29 @@
(function() { (function() {
L.Control.Search = L.Control.extend({ L.Control.Search = L.Control.extend({
includes: L.Mixin.Events, includes: L.Mixin.Events,
// //
// Name Data passed Description // Name Data passed Description
// //
//Managed Events: //Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation // search_locationfound {latlng, title, layer} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed // search_collapsed {} fired after control was collapsed
// //
//Public methods: //Public methods:
// setLayer() L.LayerGroup() set layer search at runtime // setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message // showAlert() 'Text message' Show alert message
// //
options: { options: {
wrapper: '', //container id to insert Search Control
url: '', //url for search by ajax request, ex: "search.php?q={s}" url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback" jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
layer: null, //layer where search markers(is a L.LayerGroup) layer: null, //layer where search markers(is a L.LayerGroup)
callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second callData: null, //function that fill _recordsCache, passed searching text by first param and callback in second
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer //TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//TODO implement can do research on multiple sources //TODO implement can do research on multiple sources
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer,
propertyLoc: 'loc', //field name for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) propertyLoc: 'loc', //field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] )
//TODO implement sub property filter for propertyName,propertyLoc like this: "prop.subprop.title" // support dotted format: 'prop.subprop.title'
callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param callTip: null, //function that return row tip html node(or html string), receive text tooltip in first param
filterJSON: null, //callback for filtering data to _recordsCache filterJSON: null, //callback for filtering data to _recordsCache
minLength: 1, //minimal text length for autocomplete minLength: 1, //minimal text length for autocomplete
@ -33,6 +33,7 @@ L.Control.Search = L.Control.extend({
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit. tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change autoResize: true, //autoresize on input change
collapsed: true, //collapse search control at startup
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit) autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse! //TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
@ -48,7 +49,18 @@ L.Control.Search = L.Control.extend({
}, },
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false } //FIXME option condition problem {autoCollapse: false }
//
//TODO important optimization!!! always append data in this._recordsCache
// now _recordsCache content is emptied and replaced with new data founded
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
//
//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(callData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this mode every record can have a free structure of attributes, only 'loc' is required
initialize: function(options) { initialize: function(options) {
L.Util.setOptions(this, options || {}); L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10; this._inputMinSize = this.options.text ? this.options.text.length : 10;
@ -67,7 +79,10 @@ L.Control.Search = L.Control.extend({
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel'); this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button'); this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert'); this._alert = this._createAlert('search-alert');
if(this.options.collapsed===false)
this.expand();
if(this.options.circleLocation || this.options.markerLocation) if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
@ -75,10 +90,23 @@ L.Control.Search = L.Control.extend({
map.on({ map.on({
// 'layeradd': this._onLayerAddRemove, // 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove // 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize() 'resize': this._handleAutoresize
}, this); }, this);
return this._container; return this._container;
}, },
addTo: function (map) {
if(this.options.wrapper) {
this._container = this.onAdd(map);
this._wrapper = L.DomUtil.get(this.options.wrapper);
this._wrapper.style.position = 'relative';
this._wrapper.appendChild(this._container);
}
else
L.Control.prototype.addTo.call(this, map);
return this;
},
onRemove: function(map) { onRemove: function(map) {
this._recordsCache = {}; this._recordsCache = {};
@ -95,7 +123,22 @@ L.Control.Search = L.Control.extend({
// if( L.stamp(e.layer) != L.stamp(this._layer) ) // if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer); // this.setLayer(e.layer);
// }, // },
_getPath: function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
len = parts.length,
cur = parts[0],
i = 1;
if(len > 0)
while((obj = obj[cur]) && i < len)
cur = parts[i++];
if(obj)
return obj[last];
},
setLayer: function(layer) { //set search layer at runtime setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer() //this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer; this._layer = layer;
@ -131,7 +174,7 @@ L.Control.Search = L.Control.extend({
return this; return this;
}, },
expand: function() { expand: function() {
this._input.style.display = 'block'; this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp'); L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus(); this._input.focus();
@ -143,12 +186,15 @@ L.Control.Search = L.Control.extend({
this._hideTooltip(); this._hideTooltip();
this.cancel(); this.cancel();
this._alert.style.display = 'none'; this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur(); this._input.blur();
this._cancel.style.display = 'none'; if(this.options.collapsed)
L.DomUtil.removeClass(this._container, 'search-exp'); {
//this._markerLoc.hide();//maybe unuseful this._input.style.display = 'none';
this._map.off('dragstart', this.collapse, this); this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
//this._markerLoc.hide();//maybe unuseful
this._map.off('dragstart', this.collapse, this);
}
this.fire('search_collapsed'); this.fire('search_collapsed');
return this; return this;
}, },
@ -348,18 +394,17 @@ L.Control.Search = L.Control.extend({
}, },
_defaultFilterJSON: function(json) { //default callback for filter data _defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {}, var jsonret = {}, i,
propName = this.options.propertyName, propName = this.options.propertyName,
propLoc = this.options.propertyLoc; propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) ) if( L.Util.isArray(propLoc) )
for(var i in json) for(i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] ); jsonret[ this._getPath(json[i],propName) ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else else
for(var n in json) for(i in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] ); jsonret[ this._getPath(json[i],propName) ]= L.latLng( this._getPath(json[i],propLoc) );
//TODO verify json[n].hasOwnProperty(propName) //TODO throw new Error("propertyName '"+propName+"' not found in JSON data");
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret; return jsonret;
}, },
@ -410,7 +455,8 @@ L.Control.Search = L.Control.extend({
}, },
_recordsFromLayer: function() { //return table: key,value from layer _recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {}, var that = this,
retRecords = {},
propName = this.options.propertyName, propName = this.options.propertyName,
loc; loc;
@ -420,17 +466,17 @@ L.Control.Search = L.Control.extend({
if(layer instanceof L.Marker) if(layer instanceof L.Marker)
{ {
if(layer.options.hasOwnProperty(propName)) if(that._getPath(layer.options,propName))
{ {
loc = layer.getLatLng(); loc = layer.getLatLng();
loc.layer = layer; loc.layer = layer;
retRecords[ layer.options[propName] ] = loc; retRecords[ that._getPath(layer.options,propName) ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){ }else if(that._getPath(layer.feature.properties,propName)){
loc = layer.getLatLng(); loc = layer.getLatLng();
loc.layer = layer; loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc; retRecords[ that._getPath(layer.feature.properties,propName) ] = loc;
}else{ }else{
console.log("propertyName '"+propName+"' not found in marker", layer); console.log("propertyName '"+propName+"' not found in marker", layer);
@ -571,7 +617,7 @@ L.Control.Search = L.Control.extend({
L.DomUtil.addClass(this._container, 'search-load'); L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING) if(this.options.callData) //CUSTOM SEARCH CALLBACK
{ {
that = this; that = this;
this.options.callData(inputText, function(jsonraw) { this.options.callData(inputText, function(jsonraw) {
@ -821,7 +867,7 @@ var SearchMarker = L.Marker.extend({
}, tInt); }, tInt);
return this; return this;
} }
}); });
L.Map.addInitHook(function () { L.Map.addInitHook(function () {