check-in bower_components

This commit is contained in:
Norbert Renner 2014-01-27 18:46:51 +01:00
parent 4cc16bccd0
commit 9e08e74132
101 changed files with 90960 additions and 0 deletions

View file

@ -0,0 +1,22 @@
{
"name": "leaflet-search",
"version": "1.4.7",
"main": "leaflet-search.js",
"ignore": [
"**/.*",
"node_modules",
"components",
"bower_components",
"examples"
],
"homepage": "https://github.com/stefanocudini/leaflet-search",
"_release": "1.4.7",
"_resolution": {
"type": "version",
"tag": "v1.4.7",
"commit": "411f216e1f407da9ef61047832b227c19ed47fab"
},
"_source": "git://github.com/stefanocudini/leaflet-search.git",
"_target": "*",
"_originalSource": "leaflet-search"
}

12
bower_components/leaflet-search/BUGS vendored Normal file
View file

@ -0,0 +1,12 @@
. option condition problem {autoCollapse: true, markerLocation: true} not show location, row 61
. option condition problem {autoCollapse:false }, row 62
. problem with jsonp/ajax when remote filter has different behavior of this._filterRecords, row 322
. _handleAutoresize Should resize max search box size when map is resized., row 616
. if collapse in _handleSubmit hide _markerLoc!, row 684
. autoCollapse option hide this._markerLoc before that visualized!!, row 714

115
bower_components/leaflet-search/Gruntfile.js vendored Executable file
View file

@ -0,0 +1,115 @@
'use strict';
module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-clean');
grunt.loadNpmTasks('grunt-contrib-cssmin');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-todos');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
meta: {
banner:
'/* \n'+
' * Leaflet Search Control v<%= pkg.version %> - <%= grunt.template.today("yyyy-mm-dd") %> \n'+
' * \n'+
' * Copyright 2014 <%= pkg.author.name %> \n'+
' * <%= pkg.author.email %> \n'+
' * <%= pkg.author.url %> \n'+
' * \n'+
' * Licensed under the <%= pkg.license %> license. \n'+
' * \n'+
' * Demo: \n'+
' * <%= pkg.homepage %> \n'+
' * \n'+
' * Source: \n'+
' * <%= pkg.repository.url %> \n'+
' * \n'+
' */\n'
},
clean: {
dist: {
src: ['dist/*']
}
},
jshint: {
options: {
globals: {
console: true,
module: true
},
"-W099": true, //ignora tabs e space warning
"-W033": true,
"-W044": true //ignore regexp
},
files: ['src/*.js']
},
concat: {
//TODO cut out SearchMarker
options: {
banner: '<%= meta.banner %>'
},
dist: {
files: {
'dist/leaflet-search.src.js': ['src/leaflet-search.js'],
'dist/leaflet-search.src.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.src.css': ['src/leaflet-search.mobile.css']
}
}
},
uglify: {
options: {
banner: '<%= meta.banner %>'
},
dist: {
files: {
'dist/leaflet-search.min.js': ['dist/leaflet-search.src.js']
}
}
},
cssmin: {
combine: {
files: {
'dist/leaflet-search.min.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css']
}
},
options: {
banner: '<%= meta.banner %>'
},
minify: {
expand: true,
cwd: 'dist/',
files: {
'dist/leaflet-search.min.css': ['src/leaflet-search.css'],
'dist/leaflet-search.mobile.min.css': ['src/leaflet-search.mobile.css']
}
}
},
todos: {
options: { verbose: false },
TODO: ['src/*.js'],
},
watch: {
dist: {
options: { livereload: true },
files: ['src/*'],
tasks: ['clean','concat','cssmin','jshint']
}
}
});
grunt.registerTask('default', [
'clean',
'concat',
'cssmin',
'jshint',
'uglify',
'todos'
]);
};

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Stefano Cudini
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,65 @@
Leaflet.Control.Search
============
#What
A leaflet control that search markers/features location by cutstom property.
With ajax/jsonp autocompletion and json fields re-mapping
Tested in Leaflet 0.6.4
#Where
**Demos:**
[labs.easyblog.it/maps/leaflet-search](http://labs.easyblog.it/maps/leaflet-search/)
**Source code:**
[Github](https://github.com/stefanocudini/leaflet-search)
[Bitbucket](https://bitbucket.org/zakis_/leaflet-search)
[NPM](https://npmjs.org/package/leaflet-search)
[Atmosphere](https://atmosphere.meteor.com/package/leaflet-search)
#How
Insert leaflet-search.css styles to your css page
Adding the search control to the map:
```
map.addControl( new L.Control.Search({layer: searchLayer}) );
//searchLayer if a L.LayerGroup contains searched markers
```
short way:
```
var map = new L.Map('map', { searchControl: {layer: searchLayer} });
```
other examples:
```
//ajax request to search.php for retrieve elements locations
map.addControl( new L.Control.Search({url: 'search.php?q={s}'}) );
//jsonp request to 3rd party service, implements Geocode Searching using OSM API
map.addControl( new L.Control.Search({
url: 'http://nominatim.openstreetmap.org/search?format=json&q={s}',
jsonpParam: 'json_callback',
propertyName: 'display_name',
propertyLoc: ['lat','lon']
}) );
//geojson layer, search and color feature vector
var searchControl = new L.Control.Search({layer: geojsonLayer, circleLocation:false});
searchControl.on('search_locationfound', function(e) {
e.layer.setStyle({fillColor: '#3f0'});
}).on('search_collapsed', function(e) {
featuresLayer.eachLayer(function(layer) {
featuresLayer.resetStyle(layer);
});
});
map.addControl(searchControl);
```

27
bower_components/leaflet-search/TODO vendored Normal file
View file

@ -0,0 +1,27 @@
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: 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: 37] [low] //TODO add option for persist markerLoc after collapse!
[Line: 47] [low] //TODO add option collapsed, like control.layers
[Line: 296] [low] //TODO add option for case sesitive search, also showLocation
[Line: 299] [low] //TODO use .filter or .map
[Line: 361] [low] //TODO verify json[n].hasOwnProperty(propName)
[Line: 367] [low] //TODO remove script node after call run
[Line: 376] [low] //TODO add rnd param or randomize callback name! in recordsFromJsonp
[Line: 396] [low] //TODO add rnd param or randomize callback name! in recordsFromAjax
[Line: 458] [low] //TODO implements autype without selection(useful for mobile device)
[Line: 558] [low] //TODO important optimization!!! always append data in this._recordsCache
[Line: 562] [low] //TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results..
[Line: 565] [low] //TODO change structure of _recordsCache
[Line: 616] [low] //TODO refact _handleAutoresize now is not accurate
[Line: 712] [low] //TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
[Line: 733] [low] //TODO add custom icon!
[Line: 741] [low] //TODO add inner circle
[Line: 797] [low] //TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
[Line: 819] [low] //TODO use create event 'animateEnd' in SearchMarker
[Line: 49] [med] //FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
[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: 685] [med] //FIXME if collapse in _handleSubmit hide _markerLoc!
[Line: 715] [med] //FIXME autoCollapse option hide this._markerLoc before that visualized!!

View file

@ -0,0 +1,12 @@
{
"name": "leaflet-search",
"version": "1.4.6",
"main": "leaflet-search.js",
"ignore": [
"**/.*",
"node_modules",
"components",
"bower_components",
"examples"
]
}

View file

@ -0,0 +1,18 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-container .leaflet-control-search{position:relative;float:left;background:#fff;color:#1978cf;-moz-border-radius:4px;-webkit-border-radius:4px;border-radius:4px;background-color:rgba(255,255,255,.8);z-index:1000;box-shadow:0 1px 7px rgba(0,0,0,.65);margin-left:10px;margin-top:10px}.leaflet-control-search.search-exp{box-shadow:0 1px 7px #999;background:#fff}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:18px;padding:0 18px 0 2px;margin:3px 0 3px 3px}.leaflet-control-search.search-load .search-input{background:url(../images/loader.gif) no-repeat center right #fff}.leaflet-control-search.search-load .search-cancel{visibility:hidden}.leaflet-control-search .search-cancel{display:block;width:22px;height:18px;position:absolute;right:22px;margin:3px 0;background:url(../images/search-icon.png) no-repeat 0 -46px;text-decoration:none;filter:alpha(opacity=80);opacity:.8}.leaflet-control-search .search-cancel:hover{filter:alpha(opacity=100);opacity:1}.leaflet-control-search .search-cancel span{display:none;font-size:18px;line-height:20px;color:#ccc;font-weight:700}.leaflet-control-search .search-cancel:hover span{color:#aaa}.leaflet-control-search .search-button{display:block;float:left;width:26px;height:26px;background:url(../images/search-icon.png) no-repeat 2px 2px;border-radius:4px}.leaflet-control-search .search-button:hover{background:url(../images/search-icon.png) no-repeat 2px -22px}.leaflet-control-search .search-tooltip{position:absolute;top:100%;left:0;float:left;min-width:80px;max-height:106px;box-shadow:0 0 8px rgba(0,0,0,.4);-webkit-border-radius:5px;-webkit-border-top-left-radius:0;-moz-border-radius:5px;-moz-border-radius-topleft:0;border-radius:5px;border-top-left-radius:0;background-color:rgba(0,0,0,.25);z-index:1010;overflow-y:auto;overflow-x:hidden}.leaflet-control-search .search-tip{margin:2px;padding:2px;display:block;color:#000;background:#ddd;border-radius:.25em;text-decoration:none;white-space:nowrap;font-size:.85em;vertical-align:center}.leaflet-control-search .search-button:hover,.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{cursor:pointer;clear:both;font-size:.75em;margin-bottom:5px;padding:0 .25em;color:#e00;font-weight:700;border-radius:.25em}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,18 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-control.leaflet-control-search{z-index:2000}.leaflet-control-search .search-input{display:block;float:left;background:#fff;border:1px solid #666;border-radius:2px;height:24px;font-size:1.25em;padding:0 .125em;margin:3px;padding-right:30px}.leaflet-control-search .search-button,.leaflet-control-search .search-button:hover{background-image:url(../images/search-icon-mobile.png);-webkit-border-radius:4px;border-radius:4px;background-position:1px 1px;width:32px;height:32px}.leaflet-control-search.search-load .search-input{background:url(../images/loader.gif) no-repeat center right #fff}.leaflet-control-search .search-cancel{background-image:url(../images/search-icon-mobile.png);-webkit-border-radius:4px;border-radius:4px;background-position:0 -62px;width:26px;height:26px;right:34px;margin:3px}.leaflet-control-search .search-tooltip{max-height:142px}.leaflet-control-search .search-tip{font-size:1em;margin:2px;padding:2px;display:block;color:#000;background:rgba(255,255,255,.8);border-radius:.25em;text-decoration:none;white-space:nowrap;vertical-align:center}.leaflet-control-search .search-tip .climbo-icon-mini{float:right;display:block;white-space:nowrap}.leaflet-control-search .search-button:hover,.leaflet-control-search .search-tip-select,.leaflet-control-search .search-tip:hover{background-color:#fff}.leaflet-control-search .search-alert{font-size:1.2em}

View file

@ -0,0 +1,83 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
/* SEARCH */
.leaflet-control.leaflet-control-search {
z-index:2000;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:24px;
font-size:1.25em;
padding:0 .125em;
margin:3px;
padding-right:30px;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-button {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 1px 1px;
width:32px;
height:32px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search .search-cancel {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 0px -62px;
width:26px;
height:26px;
right:34px;
margin:3px;
}
.leaflet-control-search .search-tooltip {
max-height:142px;/*(.search-tip height * 5)*/
}
.leaflet-control-search .search-tip {
font-size:1em;
margin:2px;
padding:2px;
display:block;
color:black;
background: rgba(255,255,255,0.8);
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
vertical-align:center;
}
.leaflet-control-search .search-tip .climbo-icon-mini {
float:right;
display:block;
white-space:nowrap;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
font-size:1.2em;
}

View file

@ -0,0 +1,137 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
.leaflet-container .leaflet-control-search {
position:relative;
float:left;
background:#fff;
color:#1978cf;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.8);
z-index:1000;
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
margin-left: 10px;
margin-top: 10px;
}
.leaflet-control-search.search-exp {/*expanded*/
box-shadow: 0 1px 7px #999;
background: #fff;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:18px;
padding:0 18px 0 2px;
margin:3px 0 3px 3px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search.search-load .search-cancel {
visibility:hidden;
}
.leaflet-control-search .search-cancel {
display:block;
width:22px;
height:18px;
position:absolute;
right:22px;
margin:3px 0;
background: url('../images/search-icon.png') no-repeat 0 -46px;
text-decoration:none;
filter: alpha(opacity=80);
opacity: 0.8;
}
.leaflet-control-search .search-cancel:hover {
filter: alpha(opacity=100);
opacity: 1;
}
.leaflet-control-search .search-cancel span {
display:none;/* comment for cancel button imageless */
font-size:18px;
line-height:20px;
color:#ccc;
font-weight:bold;
}
.leaflet-control-search .search-cancel:hover span {
color:#aaa;
}
.leaflet-control-search .search-button {
display:block;
float:left;
width:26px;
height:26px;
background: url('../images/search-icon.png') no-repeat 2px 2px;
border-radius:4px;
}
.leaflet-control-search .search-button:hover {
background: url('../images/search-icon.png') no-repeat 2px -22px;
}
.leaflet-control-search .search-tooltip {
position:absolute;
top:100%;
left:0;
float:left;
min-width:80px;
max-height:106px;/*(.search-tip height * 5)*/
box-shadow: 0 0 8px rgba(0,0,0,0.4);
-webkit-border-radius: 5px;
-webkit-border-top-left-radius: 0;
-moz-border-radius: 5px;
-moz-border-radius-topleft: 0;
border-radius: 5px;
border-top-left-radius: 0;
background-color: rgba(0, 0, 0, 0.25);
z-index:1010;
overflow-y:auto;
overflow-x:hidden;
}
.leaflet-control-search .search-tip {
font-size:.85em;
margin:2px;
padding:2px;
display:block;
color:black;
background: #ddd;
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
font-size:.85em;
vertical-align:center;
}
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover,
.leaflet-control-search .search-button:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
cursor:pointer;
clear:both;
font-size:.75em;
margin-bottom:5px;
padding:0 .25em;
color:#e00;
font-weight:bold;
border-radius:.25em;
}

View file

@ -0,0 +1,855 @@
/*
* Leaflet Search Control v1.4.7 - 2014-01-04
*
* Copyright 2014 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
(function() {
L.Control.Search = L.Control.extend({
includes: L.Mixin.Events,
//
// Name Data passed Description
//
//Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed
//
//Public methods:
// setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message
//
options: {
url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
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
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//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
propertyLoc: 'loc', //field name 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"
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
minLength: 1, //minimal text length for autocomplete
initial: true, //search elements only by initial text
autoType: true, //complete input with first suggested result and select this filled-in text.
delayType: 400, //delay while typing for show tooltip
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
animateLocation: true, //animate a circle over location found
circleLocation: true, //draw a circle in location found
markerLocation: false, //draw a marker in location found
zoom: null, //zoom after pan to location found, default: map.getZoom()
text: 'Search...', //placeholder value
textCancel: 'Cancel', //title in cancel button
textErr: 'Location not found', //error message
position: 'topleft'
//TODO add option collapsed, like control.layers
},
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false }
initialize: function(options) {
L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10;
this._layer = this.options.layer || new L.LayerGroup();
this._filterJSON = this.options.filterJSON || this._defaultFilterJSON;
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
this._countertips = 0; //number of tips items
this._recordsCache = {}; //key,value table! that store locations! format: key,latlng
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-search');
this._input = this._createInput(this.options.text, 'search-input');
this._tooltip = this._createTooltip('search-tooltip');
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert');
if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
this.setLayer( this._layer );
map.on({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize()
}, this);
return this._container;
},
onRemove: function(map) {
this._recordsCache = {};
// map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
// }, this);
},
// _onLayerAddRemove: function(e) {
// //console.info('_onLayerAddRemove');
// //without this, run setLayer also for each Markers!! to optimize!
// if(e.layer instanceof L.LayerGroup)
// if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer);
// },
setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer;
this._layer.addTo(this._map);
if(this._markerLoc)
this._layer.addLayer(this._markerLoc);
return this;
},
showAlert: function(text) {
text = text || this.options.textErr;
this._alert.style.display = 'block';
this._alert.innerHTML = text;
clearTimeout(this.timerAlert);
var that = this;
this.timerAlert = setTimeout(function() {
that.hideAlert();
},this.options.autoCollapseTime);
return this;
},
hideAlert: function() {
this._alert.style.display = 'none';
return this;
},
cancel: function() {
this._input.value = '';
this._handleKeypress({keyCode:8});//simulate backspace keypress
this._input.size = this._inputMinSize;
this._input.focus();
this._cancel.style.display = 'none';
return this;
},
expand: function() {
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus();
this._map.on('dragstart', this.collapse, this);
return this;
},
collapse: function() {
this._hideTooltip();
this.cancel();
this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur();
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');
return this;
},
collapseDelayed: function() { //collapse after delay, used on_input blur
if (!this.options.autoCollapse) return this;
var that = this;
clearTimeout(this.timerCollapse);
this.timerCollapse = setTimeout(function() {
that.collapse();
}, this.options.autoCollapseTime);
return this;
},
collapseDelayedStop: function() {
clearTimeout(this.timerCollapse);
return this;
},
////start DOM creations
_createAlert: function(className) {
var alert = L.DomUtil.create('div', className, this._container);
alert.style.display = 'none';
L.DomEvent
.on(alert, 'click', L.DomEvent.stop, this)
.on(alert, 'click', this.hideAlert, this);
return alert;
},
_createInput: function (text, className) {
var input = L.DomUtil.create('input', className, this._container);
input.type = 'text';
input.size = this._inputMinSize;
input.value = '';
input.autocomplete = 'off';
input.placeholder = text;
input.style.display = 'none';
L.DomEvent
.disableClickPropagation(input)
.on(input, 'keyup', this._handleKeypress, this)
.on(input, 'keydown', this._handleAutoresize, this)
.on(input, 'blur', this.collapseDelayed, this)
.on(input, 'focus', this.collapseDelayedStop, this);
return input;
},
_createCancel: function (title, className) {
var cancel = L.DomUtil.create('a', className, this._container);
cancel.href = '#';
cancel.title = title;
cancel.style.display = 'none';
cancel.innerHTML = "<span>&otimes;</span>";//imageless(see css)
L.DomEvent
.on(cancel, 'click', L.DomEvent.stop, this)
.on(cancel, 'click', this.cancel, this);
return cancel;
},
_createButton: function (title, className) {
var button = L.DomUtil.create('a', className, this._container);
button.href = '#';
button.title = title;
L.DomEvent
.on(button, 'click', L.DomEvent.stop, this)
.on(button, 'click', this._handleSubmit, this)
.on(button, 'focus', this.collapseDelayedStop, this)
.on(button, 'blur', this.collapseDelayed, this);
return button;
},
_createTooltip: function(className) {
var tool = L.DomUtil.create('div', className, this._container);
tool.style.display = 'none';
var that = this;
L.DomEvent
.disableClickPropagation(tool)
.on(tool, 'blur', this.collapseDelayed, this)
.on(tool, 'mousewheel', function(e) {
that.collapseDelayedStop();
L.DomEvent.stopPropagation(e);//disable zoom map
}, this)
.on(tool, 'mouseover', function(e) {
that.collapseDelayedStop();
}, this);
return tool;
},
_createTip: function(text, val) {//val is object in recordCache, usually is Latlng
var tip;
if(this.options.callTip)
{
tip = this.options.callTip(text,val); //custom tip node or html string
if(typeof tip === 'string')
{
var tmpNode = L.DomUtil.create('div');
tmpNode.innerHTML = tip;
tip = tmpNode.firstChild;
}
}
else
{
tip = L.DomUtil.create('a', '');
tip.href = '#';
tip.innerHTML = text;
}
L.DomUtil.addClass(tip, 'search-tip');
tip._text = text; //value replaced in this._input and used by _autoType
L.DomEvent
.disableClickPropagation(tip)
.on(tip, 'click', L.DomEvent.stop, this)
.on(tip, 'click', function(e) {
this._input.value = text;
this._handleAutoresize();
this._input.focus();
this._hideTooltip();
if(this.options.tipAutoSubmit)//go to location at once
this._handleSubmit();
}, this);
return tip;
},
//////end DOM creations
_filterRecords: function(text) { //Filter this._recordsCache case insensitive and much more..
var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'), //remove . * | ( ) ] [
I, regSearch,
frecords = {};
text = text.replace(regFilter,''); //sanitize text
I = this.options.initial ? '^' : ''; //search only initial text
//TODO add option for case sesitive search, also showLocation
regSearch = new RegExp(I + text,'i');
//TODO use .filter or .map
for(var key in this._recordsCache)
if( regSearch.test(key) )
frecords[key]= this._recordsCache[key];
return frecords;
},
showTooltip: function() {
var filteredRecords, newTip;
this._countertips = 0;
//FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
if(this.options.layer)
filteredRecords = this._filterRecords( this._input.value );
else
filteredRecords = this._recordsCache;
this._tooltip.innerHTML = '';
this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect()
for(var key in filteredRecords)//fill tooltip
{
if(++this._countertips == this.options.tooltipLimit) break;
newTip = this._createTip(key, filteredRecords[key] );
this._tooltip.appendChild(newTip);
}
if(this._countertips > 0)
{
this._tooltip.style.display = 'block';
if(this._autoTypeTmp)
this._autoType();
this._autoTypeTmp = this.options.autoType;//reset default value
}
else
this._hideTooltip();
this._tooltip.scrollTop = 0;
return this._countertips;
},
_hideTooltip: function() {
this._tooltip.style.display = 'none';
this._tooltip.innerHTML = '';
return 0;
},
_defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {},
propName = this.options.propertyName,
propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) )
for(var i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else
for(var n in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] );
//TODO verify json[n].hasOwnProperty(propName)
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret;
},
_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service
//TODO remove script node after call run
var that = this;
L.Control.Search.callJsonp = function(data) { //jsonp callback
var fdata = that._filterJSON(data);//_filterJSON defined in inizialize...
callAfter(fdata);
}
var script = L.DomUtil.create('script','search-jsonp', document.getElementsByTagName('body')[0] ),
url = L.Util.template(this.options.url+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromJsonp
script.type = 'text/javascript';
script.src = url;
return this;
//may be return {abort: function() { script.parentNode.removeChild(script); } };
},
_recordsFromAjax: function(text, callAfter) { //Ajax request
if (window.XMLHttpRequest === undefined) {
window.XMLHttpRequest = function() {
try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); }
catch (e1) {
try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); }
catch (e2) { throw new Error("XMLHttpRequest is not supported"); }
}
};
}
var request = new XMLHttpRequest(),
url = L.Util.template(this.options.url, {s: text}), //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromAjax
response = {};
request.open("GET", url);
var that = this;
request.onreadystatechange = function() {
if(request.readyState === 4 && request.status === 200) {
response = JSON.parse(request.responseText);
var fdata = that._filterJSON(response);//_filterJSON defined in inizialize...
callAfter(fdata);
}
};
request.send();
return this;
},
_recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {},
propName = this.options.propertyName,
loc;
this._layer.eachLayer(function(layer) {
if(layer instanceof SearchMarker) return;
if(layer instanceof L.Marker)
{
if(layer.options.hasOwnProperty(propName))
{
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.options[propName] ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}else{
console.log("propertyName '"+propName+"' not found in marker", layer);
}
}
else if(layer.hasOwnProperty('feature'))//GeoJSON layer
{
if(layer.feature.properties.hasOwnProperty(propName))
{
loc = layer.getBounds().getCenter();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}
else
console.log("propertyName '"+propName+"' not found in feature", layer);
}
},this);
return retRecords;
},
_autoType: function() {
//TODO implements autype without selection(useful for mobile device)
var start = this._input.value.length,
firstRecord = this._tooltip.firstChild._text,
end = firstRecord.length;
if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match
this._input.value = firstRecord;
this._handleAutoresize();
if (this._input.createTextRange) {
var selRange = this._input.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
}
else if(this._input.setSelectionRange) {
this._input.setSelectionRange(start, end);
}
else if(this._input.selectionStart) {
this._input.selectionStart = start;
this._input.selectionEnd = end;
}
}
},
_hideAutoType: function() { // deselect text:
var sel;
if ((sel = this._input.selection) && sel.empty) {
sel.empty();
}
else if (this._input.createTextRange) {
sel = this._input.createTextRange();
sel.collapse(true);
var end = this._input.value.length;
sel.moveStart('character', end);
sel.moveEnd('character', end);
sel.select();
}
else {
if (this._input.getSelection) {
this._input.getSelection().removeAllRanges();
}
this._input.selectionStart = this._input.selectionEnd;
}
},
_handleKeypress: function (e) { //run _input keyup event
switch(e.keyCode)
{
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if(this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38://Up
this._handleArrowSelect(-1);
break;
case 40://Down
this._handleArrowSelect(1);
break;
case 37://Left
case 39://Right
case 16://Shift
case 17://Ctrl
//case 32://Space
break;
case 8://backspace
case 46://delete
this._autoTypeTmp = false;//disable temporarily autoType
break;
default://All keys
if(this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if(this._input.value.length >= this.options.minLength)
{
var that = this;
clearTimeout(this.timerKeypress); //cancel last search request while type in
this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request
that._fillRecordsCache();
}, this.options.delayType);
}
else
this._hideTooltip();
}
},
_fillRecordsCache: function() {
//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
var inputText = this._input.value,
that;
L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
{
that = this;
this.options.callData(inputText, function(jsonraw) {
that._recordsCache = that._filterJSON(jsonraw);
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else if(this.options.url) //JSONP/AJAX REQUEST
{
if(this.options.jsonpParam)
{
that = this;
this._recordsFromJsonp(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else
{
that = this;
this._recordsFromAjax(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
}
else if(this.options.layer) //SEARCH ELEMENTS IN PRELOADED LAYER
{
this._recordsCache = this._recordsFromLayer(); //fill table key,value from markers into layer
this.showTooltip();
L.DomUtil.removeClass(this._container, 'search-load');
}
},
_handleAutoresize: function() { //autoresize this._input
//TODO refact _handleAutoresize now is not accurate
if (this._input.style.maxWidth != this._map._container.offsetWidth) //If maxWidth isn't the same as when first set, reset to current Map width
this._input.style.maxWidth = L.DomUtil.getStyle(this._map._container, 'width');
if(this.options.autoResize && (this._container.offsetWidth + 45 < this._map._container.offsetWidth))
this._input.size = this._input.value.length<this._inputMinSize ? this._inputMinSize : this._input.value.length;
},
_handleArrowSelect: function(velocity) {
var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [];
for (i=0; i<searchTips.length; i++)
L.DomUtil.removeClass(searchTips[i], 'search-tip-select');
if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list.
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
}
else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box.
this._tooltip.currentSelection = -1;
}
else if (this._tooltip.style.display != 'none') { // regular up/down
this._tooltip.currentSelection += velocity;
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
this._input.value = searchTips[this._tooltip.currentSelection]._text;
// scroll:
var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) {
this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
}
else if (tipOffsetTop <= this._tooltip.scrollTop) {
this._tooltip.scrollTop = tipOffsetTop;
}
}
},
_handleSubmit: function() { //button and tooltip click and enter submit
this._hideAutoType();
this.hideAlert();
this._hideTooltip();
if(this._input.style.display == 'none') //on first click show _input only
this.expand();
else
{
if(this._input.value === '') //hide _input only
this.collapse();
else
{
var loc = this._getLocation(this._input.value);
if(loc===false)
this.showAlert();
else
{
this.showLocation(loc, this._input.value);
this.fire('search_locationfound', {
latlng: loc,
text: this._input.value,
layer: loc.layer ? loc.layer : null
});
}
//this.collapse();
//FIXME if collapse in _handleSubmit hide _markerLoc!
}
}
},
_getLocation: function(key) { //extract latlng from _recordsCache
if( this._recordsCache.hasOwnProperty(key) )
return this._recordsCache[key];//then after use .loc attribute
else
return false;
},
showLocation: function(latlng, title) { //set location on map from _recordsCache
if(this.options.zoom)
this._map.setView(latlng, this.options.zoom);
else
this._map.panTo(latlng);
if(this._markerLoc)
{
this._markerLoc.setLatLng(latlng); //show circle/marker in location found
this._markerLoc.setTitle(title);
this._markerLoc.show();
if(this.options.animateLocation)
this._markerLoc.animate();
//TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
}
//FIXME autoCollapse option hide this._markerLoc before that visualized!!
if(this.options.autoCollapse)
this.collapse();
return this;
}
});
var SearchMarker = L.Marker.extend({
includes: L.Mixin.Events,
options: {
radius: 10,
weight: 3,
color: '#e03',
stroke: true,
fill: false,
title: '',
//TODO add custom icon!
marker: false //show icon optional, show only circleLoc
},
initialize: function (latlng, options) {
L.setOptions(this, options);
L.Marker.prototype.initialize.call(this, latlng, options);
this._circleLoc = new L.CircleMarker(latlng, this.options);
//TODO add inner circle
},
onAdd: function (map) {
L.Marker.prototype.onAdd.call(this, map);
map.addLayer(this._circleLoc);
this.hide();
},
onRemove: function (map) {
L.Marker.prototype.onRemove.call(this, map);
map.removeLayer(this._circleLoc);
},
setLatLng: function (latlng) {
L.Marker.prototype.setLatLng.call(this, latlng);
this._circleLoc.setLatLng(latlng);
return this;
},
setTitle: function(title) {
title = title || '';
this.options.title = title;
if(this._icon)
this._icon.title = title;
return this;
},
show: function() {
if(this.options.marker)
{
if(this._icon)
this._icon.style.display = 'block';
if(this._shadow)
this._shadow.style.display = 'block';
//this._bringToFront();
}
if(this._circleLoc)
{
this._circleLoc.setStyle({fill: this.options.fill, stroke: this.options.stroke});
//this._circleLoc.bringToFront();
}
return this;
},
hide: function() {
if(this._icon)
this._icon.style.display = 'none';
if(this._shadow)
this._shadow.style.display = 'none';
if(this._circleLoc)
this._circleLoc.setStyle({fill: false, stroke: false});
return this;
},
animate: function() {
//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
var circle = this._circleLoc,
tInt = 200, //time interval
ss = 10, //frames
mr = parseInt(circle._radius/ss),
oldrad = this.options.radius,
newrad = circle._radius * 2.5,
acc = 0;
circle._timerAnimLoc = setInterval(function() {
acc += 0.5;
mr += acc; //adding acceleration
newrad -= mr;
circle.setRadius(newrad);
if(newrad<oldrad)
{
clearInterval(circle._timerAnimLoc);
circle.setRadius(oldrad);//reset radius
//if(typeof afterAnimCall == 'function')
//afterAnimCall();
//TODO use create event 'animateEnd' in SearchMarker
}
}, tInt);
return this;
}
});
L.Map.addInitHook(function () {
if (this.options.searchControl) {
this.searchControl = L.control.search(this.options.searchControl);
this.addControl(this.searchControl);
}
});
L.control.search = function (options) {
return new L.Control.Search(options);
};
}).call(this);

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -0,0 +1,102 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Leaflet.Control.Search</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<link rel="stylesheet" href="style.css" />
</head>
<body id="home">
<h2>Leaflet.Control.Search</h2>
<div id="desc">
A Leaflet Control for search markers/features location by attribute<br />
and much more.
<div style="position:absolute;top:0;right:-120px">
<iframe src="http://ghbtns.com/github-btn.html?user=stefanocudini&amp;repo=leaflet-search&amp;type=watch&amp;count=true" allowtransparency="true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
</div>
<br />
Other useful stuff for <a href="http://labs.easyblog.it/maps/">Web Mapping...</a>
</div>
<div style="clear:both"></div>
<div class="contents">
<h4>Features</h4>
<ul id="ff">
<li>Autocomplete</li>
<li>No require external Ajax libs</li>
<li>Retrieve data locations by Ajax/Jsonp</li>
<li>Pre-filtering data from Ajax/Jsonp</li>
<li>Complete fields remapping for remote Jsonp service</li>
<li>Data source callback support</li>
<li>Localization placeholder and text alert</li>
<li>Autozoom on location founded</li>
<li>Autoresize textbox</li>
<li>Customize tooltip menu</li>
<li>Many options to customize the behavior</li>
<li>Support search in features collection</li>
</ul>
</div>
<div class="contents">
<h4>Examples</h4>
<ul id="examples">
<li><a href="examples/simple.html">Simple</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/jsonp.html">Jsonp</a></li>
<li><a href="examples/ajax-jquery.html">Ajax by jQuery</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/custom-tip.html">Custom Tip</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/cloudmade.html">GeoCode Search - Cloudmade API</a></li>
<li><a href="examples/mobile.html">Mobile styled</a></li>
<li><a href="examples/twitter.html">Twitter API</a></li>
</ul>
</div>
<div class="contents">
<h4>Code repositories</h4>
<a target="_blank" href="https://github.com/stefanocudini/leaflet-search">Github.com</a>
<br />
<a target="_blank" href="https://bitbucket.org/zakis_/leaflet-search">Bitbucket.org</a>
<br />
<a target="_blank" href="https://npmjs.org/package/leaflet-search">Node Packaged Module</a>
<br />
<a target="_blank" href="https://atmosphere.meteor.com/package/leaflet-search">Atmosphere Meteor JS</a>
<br />
<h4>Website</h4>
<a href="http://labs.easyblog.it/maps/leaflet-search/">labs.easyblog.it/maps/leaflet-search</a>
<br />
<h4>Download</h4>
<ul>
<li><a href="https://github.com/stefanocudini/leaflet-search/archive/master.zip">Dev Pack (.zip)</a></li>
<li><a href="dist/leaflet-search.src.js">Source Code (.js)</a></li>
<li><a href="dist/leaflet-search.min.js">Compressed (.min.js)</a></li>
</ul>
</div>
<div id="copy"><a href="http://labs.easyblog.it/">Labs</a> &bull; <a rel="author" href="http://labs.easyblog.it/stefano-cudini/">Stefano Cudini</a></div>
<a href="https://github.com/stefanocudini/leaflet-search"><img id="ribbon" src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png" alt="Fork me on GitHub"></a>
<div style="clear:both;font-size:.85em;margin-bottom:1em">
<b>For questions and bugs</b> I recommend you to <a href="https://github.com/stefanocudini/leaflet-search/issues">create New Issue</a> on Github repository.</strong><br />
Or to obtain a fast response consult <a href="https://groups.google.com/forum/?hl=it&fromgroups=#!forum/leaflet-js">Official Leaflet community forum</a>.<br />
<br />
This is a micro discussion area for methods of implementation.<br />
</div>
<div id="comments">
<div id="disqus_thread"></div>
</div>
<script>var disqus_shortname = 'easyblog-it'</script>
<script type="text/javascript" src="/labs-common.js"></script>
</body>
</html>

View file

@ -0,0 +1,11 @@
Package.describe({
summary: "Leaflet Control Search"
});
Package.on_use(function (api, where) {
api.add_files('dist/leaflet-search.min.js', 'client');
api.add_files('dist/leaflet-search.min.css', 'client');
api.add_files('images/search-icon.png', 'client');
api.add_files('images/loader.gif', 'client');
//TODO server-side searching...
});

View file

@ -0,0 +1,34 @@
{
"name": "leaflet-search",
"version": "1.4.7",
"description": "Leaflet Control for searching markers/features by attribute on map or remote searching in jsonp/ajax",
"repository": {
"type": "git",
"url": "git@github.com:stefanocudini/leaflet-search.git"
},
"homepage": "http://labs.easyblog.it/maps/leaflet-search/",
"author": {
"name": "Stefano Cudini",
"email": "stefano.cudini@gmail.com",
"url": "http://labs.easyblog.it/"
},
"license": "MIT",
"keywords": [
"gis",
"map",
"leaflet"
],
"dependencies": {
"leaflet": "*"
},
"devDependencies": {
"grunt": "~0.4.2",
"grunt-contrib-uglify": "~0.2.7",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-cssmin": "~0.7.0",
"grunt-contrib-jshint": "~0.7.2",
"grunt-contrib-watch": "~0.5.3",
"grunt-todos": "~0.2.0"
}
}

View file

@ -0,0 +1,8 @@
{
"name": "leaflet-search",
"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/",
"author": "Stefano Cudini <stefano.cudini@gmail.com>",
"version": "1.4.7",
"git": "https://github.com/stefanocudini/leaflet-search.git"
}

View file

@ -0,0 +1,121 @@
.leaflet-container .leaflet-control-search {
position:relative;
float:left;
background:#fff;
color:#1978cf;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.8);
z-index:1000;
box-shadow: 0 1px 7px rgba(0,0,0,0.65);
margin-left: 10px;
margin-top: 10px;
}
.leaflet-control-search.search-exp {/*expanded*/
box-shadow: 0 1px 7px #999;
background: #fff;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:18px;
padding:0 18px 0 2px;
margin:3px 0 3px 3px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search.search-load .search-cancel {
visibility:hidden;
}
.leaflet-control-search .search-cancel {
display:block;
width:22px;
height:18px;
position:absolute;
right:22px;
margin:3px 0;
background: url('../images/search-icon.png') no-repeat 0 -46px;
text-decoration:none;
filter: alpha(opacity=80);
opacity: 0.8;
}
.leaflet-control-search .search-cancel:hover {
filter: alpha(opacity=100);
opacity: 1;
}
.leaflet-control-search .search-cancel span {
display:none;/* comment for cancel button imageless */
font-size:18px;
line-height:20px;
color:#ccc;
font-weight:bold;
}
.leaflet-control-search .search-cancel:hover span {
color:#aaa;
}
.leaflet-control-search .search-button {
display:block;
float:left;
width:26px;
height:26px;
background: url('../images/search-icon.png') no-repeat 2px 2px;
border-radius:4px;
}
.leaflet-control-search .search-button:hover {
background: url('../images/search-icon.png') no-repeat 2px -22px;
}
.leaflet-control-search .search-tooltip {
position:absolute;
top:100%;
left:0;
float:left;
min-width:80px;
max-height:106px;/*(.search-tip height * 5)*/
box-shadow: 0 0 8px rgba(0,0,0,0.4);
-webkit-border-radius: 5px;
-webkit-border-top-left-radius: 0;
-moz-border-radius: 5px;
-moz-border-radius-topleft: 0;
border-radius: 5px;
border-top-left-radius: 0;
background-color: rgba(0, 0, 0, 0.25);
z-index:1010;
overflow-y:auto;
overflow-x:hidden;
}
.leaflet-control-search .search-tip {
font-size:.85em;
margin:2px;
padding:2px;
display:block;
color:black;
background: #ddd;
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
font-size:.85em;
vertical-align:center;
}
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover,
.leaflet-control-search .search-button:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
cursor:pointer;
clear:both;
font-size:.75em;
margin-bottom:5px;
padding:0 .25em;
color:#e00;
font-weight:bold;
border-radius:.25em;
}

View file

@ -0,0 +1,839 @@
(function() {
L.Control.Search = L.Control.extend({
includes: L.Mixin.Events,
//
// Name Data passed Description
//
//Managed Events:
// search_locationfound {latlng, title} fired after moved and show markerLocation
// search_collapsed {} fired after control was collapsed
//
//Public methods:
// setLayer() L.LayerGroup() set layer search at runtime
// showAlert() 'Text message' Show alert message
//
options: {
url: '', //url for search by ajax request, ex: "search.php?q={s}"
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
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
//TODO important! implements uniq option 'sourceData' that recognizes source type: url,array,callback or layer
//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
propertyLoc: 'loc', //field name 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"
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
minLength: 1, //minimal text length for autocomplete
initial: true, //search elements only by initial text
autoType: true, //complete input with first suggested result and select this filled-in text.
delayType: 400, //delay while typing for show tooltip
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit.
tipAutoSubmit: true, //auto map panTo when click on tooltip
autoResize: true, //autoresize on input change
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
//TODO add option for persist markerLoc after collapse!
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
animateLocation: true, //animate a circle over location found
circleLocation: true, //draw a circle in location found
markerLocation: false, //draw a marker in location found
zoom: null, //zoom after pan to location found, default: map.getZoom()
text: 'Search...', //placeholder value
textCancel: 'Cancel', //title in cancel button
textErr: 'Location not found', //error message
position: 'topleft'
//TODO add option collapsed, like control.layers
},
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false }
initialize: function(options) {
L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.text ? this.options.text.length : 10;
this._layer = this.options.layer || new L.LayerGroup();
this._filterJSON = this.options.filterJSON || this._defaultFilterJSON;
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
this._countertips = 0; //number of tips items
this._recordsCache = {}; //key,value table! that store locations! format: key,latlng
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-search');
this._input = this._createInput(this.options.text, 'search-input');
this._tooltip = this._createTooltip('search-tooltip');
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.text, 'search-button');
this._alert = this._createAlert('search-alert');
if(this.options.circleLocation || this.options.markerLocation)
this._markerLoc = new SearchMarker([0,0], {marker: this.options.markerLocation});//see below
this.setLayer( this._layer );
map.on({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize':this._handleAutoresize()
}, this);
return this._container;
},
onRemove: function(map) {
this._recordsCache = {};
// map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
// }, this);
},
// _onLayerAddRemove: function(e) {
// //console.info('_onLayerAddRemove');
// //without this, run setLayer also for each Markers!! to optimize!
// if(e.layer instanceof L.LayerGroup)
// if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer);
// },
setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer;
this._layer.addTo(this._map);
if(this._markerLoc)
this._layer.addLayer(this._markerLoc);
return this;
},
showAlert: function(text) {
text = text || this.options.textErr;
this._alert.style.display = 'block';
this._alert.innerHTML = text;
clearTimeout(this.timerAlert);
var that = this;
this.timerAlert = setTimeout(function() {
that.hideAlert();
},this.options.autoCollapseTime);
return this;
},
hideAlert: function() {
this._alert.style.display = 'none';
return this;
},
cancel: function() {
this._input.value = '';
this._handleKeypress({keyCode:8});//simulate backspace keypress
this._input.size = this._inputMinSize;
this._input.focus();
this._cancel.style.display = 'none';
return this;
},
expand: function() {
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
this._input.focus();
this._map.on('dragstart', this.collapse, this);
return this;
},
collapse: function() {
this._hideTooltip();
this.cancel();
this._alert.style.display = 'none';
this._input.style.display = 'none';
this._input.blur();
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');
return this;
},
collapseDelayed: function() { //collapse after delay, used on_input blur
if (!this.options.autoCollapse) return this;
var that = this;
clearTimeout(this.timerCollapse);
this.timerCollapse = setTimeout(function() {
that.collapse();
}, this.options.autoCollapseTime);
return this;
},
collapseDelayedStop: function() {
clearTimeout(this.timerCollapse);
return this;
},
////start DOM creations
_createAlert: function(className) {
var alert = L.DomUtil.create('div', className, this._container);
alert.style.display = 'none';
L.DomEvent
.on(alert, 'click', L.DomEvent.stop, this)
.on(alert, 'click', this.hideAlert, this);
return alert;
},
_createInput: function (text, className) {
var input = L.DomUtil.create('input', className, this._container);
input.type = 'text';
input.size = this._inputMinSize;
input.value = '';
input.autocomplete = 'off';
input.placeholder = text;
input.style.display = 'none';
L.DomEvent
.disableClickPropagation(input)
.on(input, 'keyup', this._handleKeypress, this)
.on(input, 'keydown', this._handleAutoresize, this)
.on(input, 'blur', this.collapseDelayed, this)
.on(input, 'focus', this.collapseDelayedStop, this);
return input;
},
_createCancel: function (title, className) {
var cancel = L.DomUtil.create('a', className, this._container);
cancel.href = '#';
cancel.title = title;
cancel.style.display = 'none';
cancel.innerHTML = "<span>&otimes;</span>";//imageless(see css)
L.DomEvent
.on(cancel, 'click', L.DomEvent.stop, this)
.on(cancel, 'click', this.cancel, this);
return cancel;
},
_createButton: function (title, className) {
var button = L.DomUtil.create('a', className, this._container);
button.href = '#';
button.title = title;
L.DomEvent
.on(button, 'click', L.DomEvent.stop, this)
.on(button, 'click', this._handleSubmit, this)
.on(button, 'focus', this.collapseDelayedStop, this)
.on(button, 'blur', this.collapseDelayed, this);
return button;
},
_createTooltip: function(className) {
var tool = L.DomUtil.create('div', className, this._container);
tool.style.display = 'none';
var that = this;
L.DomEvent
.disableClickPropagation(tool)
.on(tool, 'blur', this.collapseDelayed, this)
.on(tool, 'mousewheel', function(e) {
that.collapseDelayedStop();
L.DomEvent.stopPropagation(e);//disable zoom map
}, this)
.on(tool, 'mouseover', function(e) {
that.collapseDelayedStop();
}, this);
return tool;
},
_createTip: function(text, val) {//val is object in recordCache, usually is Latlng
var tip;
if(this.options.callTip)
{
tip = this.options.callTip(text,val); //custom tip node or html string
if(typeof tip === 'string')
{
var tmpNode = L.DomUtil.create('div');
tmpNode.innerHTML = tip;
tip = tmpNode.firstChild;
}
}
else
{
tip = L.DomUtil.create('a', '');
tip.href = '#';
tip.innerHTML = text;
}
L.DomUtil.addClass(tip, 'search-tip');
tip._text = text; //value replaced in this._input and used by _autoType
L.DomEvent
.disableClickPropagation(tip)
.on(tip, 'click', L.DomEvent.stop, this)
.on(tip, 'click', function(e) {
this._input.value = text;
this._handleAutoresize();
this._input.focus();
this._hideTooltip();
if(this.options.tipAutoSubmit)//go to location at once
this._handleSubmit();
}, this);
return tip;
},
//////end DOM creations
_filterRecords: function(text) { //Filter this._recordsCache case insensitive and much more..
var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'), //remove . * | ( ) ] [
I, regSearch,
frecords = {};
text = text.replace(regFilter,''); //sanitize text
I = this.options.initial ? '^' : ''; //search only initial text
//TODO add option for case sesitive search, also showLocation
regSearch = new RegExp(I + text,'i');
//TODO use .filter or .map
for(var key in this._recordsCache)
if( regSearch.test(key) )
frecords[key]= this._recordsCache[key];
return frecords;
},
showTooltip: function() {
var filteredRecords, newTip;
this._countertips = 0;
//FIXME problem with jsonp/ajax when remote filter has different behavior of this._filterRecords
if(this.options.layer)
filteredRecords = this._filterRecords( this._input.value );
else
filteredRecords = this._recordsCache;
this._tooltip.innerHTML = '';
this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect()
for(var key in filteredRecords)//fill tooltip
{
if(++this._countertips == this.options.tooltipLimit) break;
newTip = this._createTip(key, filteredRecords[key] );
this._tooltip.appendChild(newTip);
}
if(this._countertips > 0)
{
this._tooltip.style.display = 'block';
if(this._autoTypeTmp)
this._autoType();
this._autoTypeTmp = this.options.autoType;//reset default value
}
else
this._hideTooltip();
this._tooltip.scrollTop = 0;
return this._countertips;
},
_hideTooltip: function() {
this._tooltip.style.display = 'none';
this._tooltip.innerHTML = '';
return 0;
},
_defaultFilterJSON: function(json) { //default callback for filter data
var jsonret = {},
propName = this.options.propertyName,
propLoc = this.options.propertyLoc;
if( L.Util.isArray(propLoc) )
for(var i in json)
jsonret[ json[i][propName] ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
else
for(var n in json)
jsonret[ json[n][propName] ]= L.latLng( json[n][ propLoc ] );
//TODO verify json[n].hasOwnProperty(propName)
//throw new Error("propertyName '"+propName+"' not found in JSON data");
return jsonret;
},
_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service
//TODO remove script node after call run
var that = this;
L.Control.Search.callJsonp = function(data) { //jsonp callback
var fdata = that._filterJSON(data);//_filterJSON defined in inizialize...
callAfter(fdata);
}
var script = L.DomUtil.create('script','search-jsonp', document.getElementsByTagName('body')[0] ),
url = L.Util.template(this.options.url+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromJsonp
script.type = 'text/javascript';
script.src = url;
return this;
//may be return {abort: function() { script.parentNode.removeChild(script); } };
},
_recordsFromAjax: function(text, callAfter) { //Ajax request
if (window.XMLHttpRequest === undefined) {
window.XMLHttpRequest = function() {
try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); }
catch (e1) {
try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); }
catch (e2) { throw new Error("XMLHttpRequest is not supported"); }
}
};
}
var request = new XMLHttpRequest(),
url = L.Util.template(this.options.url, {s: text}), //parsing url
//rnd = '&_='+Math.floor(Math.random()*10000);
//TODO add rnd param or randomize callback name! in recordsFromAjax
response = {};
request.open("GET", url);
var that = this;
request.onreadystatechange = function() {
if(request.readyState === 4 && request.status === 200) {
response = JSON.parse(request.responseText);
var fdata = that._filterJSON(response);//_filterJSON defined in inizialize...
callAfter(fdata);
}
};
request.send();
return this;
},
_recordsFromLayer: function() { //return table: key,value from layer
var retRecords = {},
propName = this.options.propertyName,
loc;
this._layer.eachLayer(function(layer) {
if(layer instanceof SearchMarker) return;
if(layer instanceof L.Marker)
{
if(layer.options.hasOwnProperty(propName))
{
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.options[propName] ] = loc;
}else if(layer.feature.properties.hasOwnProperty(propName)){
loc = layer.getLatLng();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}else{
console.log("propertyName '"+propName+"' not found in marker", layer);
}
}
else if(layer.hasOwnProperty('feature'))//GeoJSON layer
{
if(layer.feature.properties.hasOwnProperty(propName))
{
loc = layer.getBounds().getCenter();
loc.layer = layer;
retRecords[ layer.feature.properties[propName] ] = loc;
}
else
console.log("propertyName '"+propName+"' not found in feature", layer);
}
},this);
return retRecords;
},
_autoType: function() {
//TODO implements autype without selection(useful for mobile device)
var start = this._input.value.length,
firstRecord = this._tooltip.firstChild._text,
end = firstRecord.length;
if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match
this._input.value = firstRecord;
this._handleAutoresize();
if (this._input.createTextRange) {
var selRange = this._input.createTextRange();
selRange.collapse(true);
selRange.moveStart('character', start);
selRange.moveEnd('character', end);
selRange.select();
}
else if(this._input.setSelectionRange) {
this._input.setSelectionRange(start, end);
}
else if(this._input.selectionStart) {
this._input.selectionStart = start;
this._input.selectionEnd = end;
}
}
},
_hideAutoType: function() { // deselect text:
var sel;
if ((sel = this._input.selection) && sel.empty) {
sel.empty();
}
else if (this._input.createTextRange) {
sel = this._input.createTextRange();
sel.collapse(true);
var end = this._input.value.length;
sel.moveStart('character', end);
sel.moveEnd('character', end);
sel.select();
}
else {
if (this._input.getSelection) {
this._input.getSelection().removeAllRanges();
}
this._input.selectionStart = this._input.selectionEnd;
}
},
_handleKeypress: function (e) { //run _input keyup event
switch(e.keyCode)
{
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if(this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38://Up
this._handleArrowSelect(-1);
break;
case 40://Down
this._handleArrowSelect(1);
break;
case 37://Left
case 39://Right
case 16://Shift
case 17://Ctrl
//case 32://Space
break;
case 8://backspace
case 46://delete
this._autoTypeTmp = false;//disable temporarily autoType
break;
default://All keys
if(this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if(this._input.value.length >= this.options.minLength)
{
var that = this;
clearTimeout(this.timerKeypress); //cancel last search request while type in
this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request
that._fillRecordsCache();
}, this.options.delayType);
}
else
this._hideTooltip();
}
},
_fillRecordsCache: function() {
//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
var inputText = this._input.value,
that;
L.DomUtil.addClass(this._container, 'search-load');
if(this.options.callData) //CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
{
that = this;
this.options.callData(inputText, function(jsonraw) {
that._recordsCache = that._filterJSON(jsonraw);
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else if(this.options.url) //JSONP/AJAX REQUEST
{
if(this.options.jsonpParam)
{
that = this;
this._recordsFromJsonp(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
else
{
that = this;
this._recordsFromAjax(inputText, function(data) {// is async request then it need callback
that._recordsCache = data;
that.showTooltip();
L.DomUtil.removeClass(that._container, 'search-load');
});
}
}
else if(this.options.layer) //SEARCH ELEMENTS IN PRELOADED LAYER
{
this._recordsCache = this._recordsFromLayer(); //fill table key,value from markers into layer
this.showTooltip();
L.DomUtil.removeClass(this._container, 'search-load');
}
},
_handleAutoresize: function() { //autoresize this._input
//TODO refact _handleAutoresize now is not accurate
if (this._input.style.maxWidth != this._map._container.offsetWidth) //If maxWidth isn't the same as when first set, reset to current Map width
this._input.style.maxWidth = L.DomUtil.getStyle(this._map._container, 'width');
if(this.options.autoResize && (this._container.offsetWidth + 45 < this._map._container.offsetWidth))
this._input.size = this._input.value.length<this._inputMinSize ? this._inputMinSize : this._input.value.length;
},
_handleArrowSelect: function(velocity) {
var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : [];
for (i=0; i<searchTips.length; i++)
L.DomUtil.removeClass(searchTips[i], 'search-tip-select');
if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list.
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
}
else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box.
this._tooltip.currentSelection = -1;
}
else if (this._tooltip.style.display != 'none') { // regular up/down
this._tooltip.currentSelection += velocity;
L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
this._input.value = searchTips[this._tooltip.currentSelection]._text;
// scroll:
var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) {
this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
}
else if (tipOffsetTop <= this._tooltip.scrollTop) {
this._tooltip.scrollTop = tipOffsetTop;
}
}
},
_handleSubmit: function() { //button and tooltip click and enter submit
this._hideAutoType();
this.hideAlert();
this._hideTooltip();
if(this._input.style.display == 'none') //on first click show _input only
this.expand();
else
{
if(this._input.value === '') //hide _input only
this.collapse();
else
{
var loc = this._getLocation(this._input.value);
if(loc===false)
this.showAlert();
else
{
this.showLocation(loc, this._input.value);
this.fire('search_locationfound', {
latlng: loc,
text: this._input.value,
layer: loc.layer ? loc.layer : null
});
}
//this.collapse();
//FIXME if collapse in _handleSubmit hide _markerLoc!
}
}
},
_getLocation: function(key) { //extract latlng from _recordsCache
if( this._recordsCache.hasOwnProperty(key) )
return this._recordsCache[key];//then after use .loc attribute
else
return false;
},
showLocation: function(latlng, title) { //set location on map from _recordsCache
if(this.options.zoom)
this._map.setView(latlng, this.options.zoom);
else
this._map.panTo(latlng);
if(this._markerLoc)
{
this._markerLoc.setLatLng(latlng); //show circle/marker in location found
this._markerLoc.setTitle(title);
this._markerLoc.show();
if(this.options.animateLocation)
this._markerLoc.animate();
//TODO showLocation: start animation after setView or panTo, maybe with map.on('moveend')...
}
//FIXME autoCollapse option hide this._markerLoc before that visualized!!
if(this.options.autoCollapse)
this.collapse();
return this;
}
});
var SearchMarker = L.Marker.extend({
includes: L.Mixin.Events,
options: {
radius: 10,
weight: 3,
color: '#e03',
stroke: true,
fill: false,
title: '',
//TODO add custom icon!
marker: false //show icon optional, show only circleLoc
},
initialize: function (latlng, options) {
L.setOptions(this, options);
L.Marker.prototype.initialize.call(this, latlng, options);
this._circleLoc = new L.CircleMarker(latlng, this.options);
//TODO add inner circle
},
onAdd: function (map) {
L.Marker.prototype.onAdd.call(this, map);
map.addLayer(this._circleLoc);
this.hide();
},
onRemove: function (map) {
L.Marker.prototype.onRemove.call(this, map);
map.removeLayer(this._circleLoc);
},
setLatLng: function (latlng) {
L.Marker.prototype.setLatLng.call(this, latlng);
this._circleLoc.setLatLng(latlng);
return this;
},
setTitle: function(title) {
title = title || '';
this.options.title = title;
if(this._icon)
this._icon.title = title;
return this;
},
show: function() {
if(this.options.marker)
{
if(this._icon)
this._icon.style.display = 'block';
if(this._shadow)
this._shadow.style.display = 'block';
//this._bringToFront();
}
if(this._circleLoc)
{
this._circleLoc.setStyle({fill: this.options.fill, stroke: this.options.stroke});
//this._circleLoc.bringToFront();
}
return this;
},
hide: function() {
if(this._icon)
this._icon.style.display = 'none';
if(this._shadow)
this._shadow.style.display = 'none';
if(this._circleLoc)
this._circleLoc.setStyle({fill: false, stroke: false});
return this;
},
animate: function() {
//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs
var circle = this._circleLoc,
tInt = 200, //time interval
ss = 10, //frames
mr = parseInt(circle._radius/ss),
oldrad = this.options.radius,
newrad = circle._radius * 2.5,
acc = 0;
circle._timerAnimLoc = setInterval(function() {
acc += 0.5;
mr += acc; //adding acceleration
newrad -= mr;
circle.setRadius(newrad);
if(newrad<oldrad)
{
clearInterval(circle._timerAnimLoc);
circle.setRadius(oldrad);//reset radius
//if(typeof afterAnimCall == 'function')
//afterAnimCall();
//TODO use create event 'animateEnd' in SearchMarker
}
}, tInt);
return this;
}
});
L.Map.addInitHook(function () {
if (this.options.searchControl) {
this.searchControl = L.control.search(this.options.searchControl);
this.addControl(this.searchControl);
}
});
L.control.search = function (options) {
return new L.Control.Search(options);
};
}).call(this);

View file

@ -0,0 +1,67 @@
/* SEARCH */
.leaflet-control.leaflet-control-search {
z-index:2000;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:24px;
font-size:1.25em;
padding:0 .125em;
margin:3px;
padding-right:30px;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-button {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 1px 1px;
width:32px;
height:32px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search .search-cancel {
background-image: url('../images/search-icon-mobile.png');
-webkit-border-radius: 4px;
border-radius: 4px;
background-position: 0px -62px;
width:26px;
height:26px;
right:34px;
margin:3px;
}
.leaflet-control-search .search-tooltip {
max-height:142px;/*(.search-tip height * 5)*/
}
.leaflet-control-search .search-tip {
font-size:1em;
margin:2px;
padding:2px;
display:block;
color:black;
background: rgba(255,255,255,0.8);
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
vertical-align:center;
}
.leaflet-control-search .search-tip .climbo-icon-mini {
float:right;
display:block;
white-space:nowrap;
}
.leaflet-control-search .search-button:hover,
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
font-size:1.2em;
}

View file

@ -0,0 +1,97 @@
body {
background:#b5d0d0;
color:#285585;
font-family:Arial;
}
body#home {
background:url('images/back.png') no-repeat top left #b5d0d0;
margin-left:150px;
}
a {
color:#1978cf;
}
a:hover {
color:#fff;
}
h2, h3, h4 {
white-space:nowrap;
margin:1em 0 0 0;
}
h3 a,
h3 a:hover {
text-decoration:none;
}
#desc {
float: left;
margin-bottom: 1em;
position: relative;
white-space:nowrap;
font-size:1em;
}
#map {
border-radius:.125em;
border:2px solid #1978cf;
box-shadow: 0 0 8px #999;
float:left;
width:600px;
height:400px;
}
ul {
font-size:.85em;
margin:0;
padding:0;
}
li {
margin:0 0 2px 18px;
}
#post-it {
width:9em;
height:9em;
margin-left:2em;
padding:1em;
float:left;
background:#fbf5bf;
border:1px solid #c6bb58;
box-shadow: 2px 2px 6px #999;
color:#666;
}
#copy {
position:fixed;
z-index:1000;
right:150px;
top:-8px;
font-size:.85em;
padding:8px 8px 2px 8px;
background: #323b44;
border: 2px solid #737c85;
border-radius:.7em;
opacity: 0.9;
box-shadow:0 0 8px #5f7182;
color:#eee
}
#copy a {
color:#ccc;
text-decoration:none
}
#copy a:hover {
color:#fff
}
#ribbon {
position: absolute;
top: 0;
right: 0;
border: 0;
filter: alpha(opacity=80);
-khtml-opacity: .8;
-moz-opacity: .8;
opacity: .8;
}
.contents {
float:left;
margin:0 2em 2em 0;
}
#comments {
clear:both;
}