Commits

Andreas Wagner committed 3ac4260 Draft Merge

Multisection tooltip can take a tipHierarchy array. Other examples broken at the moment.

Comments (0)

Files changed (10)

-. option condition problem {autoCollapse: true, markerLocation: true} not show location, row 138
+. option condition problem {autoCollapse: true, markerLocation: true} not show location, row 141
 
-. option condition problem {autoCollapse:false }, row 139
+. option condition problem {autoCollapse:false }, row 142
 
-. _autoType find a way without innerHTML that also guarantees correct order (application developer may want images in tooltip), row 406
+. removeClass .search-load apparently executed before searchCall!! A BIG MYSTERY!, row 518
 
-. removeClass .search-load apparently executed before searchCall!! A BIG MYSTERY!, row 501
+. _handleAutoresize Should resize max search box size when map is resized., row 537
 
-. _handleAutoresize Should resize max search box size when map is resized., row 520
+. implement case insesitive test for _recordsCache keys, row 634
 
-. autoCollapse option hide this._markerLoc before that visualized!!, row 632
+. autoCollapse option hide this._markerLoc before that visualized!!, row 663
 
 . start L.Control.SearchMarker.animation after setView or panTo, maybe with map.on('moveend')..., row 15
 
-. refact L.Control.SearchMarker.animate() more smooth! and use bringToFront(), row 80
+. add custom icon!		, row 25
 
-. use create event animateEnd in L.Control.SearchMarker , row 102
+. refact L.Control.SearchMarker.animate() more smooth! and use bringToFront(), row 81
 
-. add option searchLoc or searchLat,searchLon for remapping fields, row 117
+. use create event animateEnd in L.Control.SearchMarker , row 103
 
-. add option for persist markerLoc after autoCollapse!, row 128
+. add option searchLoc or searchLat,searchLon for remapping json data fields, row 118
 
-. optional markerLoc.hide in collapse(), row 201
+. add event callback onFound(latlng), row 119
 
-. add new option: the callback for building the tip content from text argument, row 291
+. add option for persist markerLoc after collapse!, row 131
 
-. use: throw new Error("my message");on error, row 363
+. use: throw new Error("my message");on error, row 386
 
-. add rnd param or randomize callback name! in recordsFromJsonp, row 378
+. add rnd param or randomize callback name! in recordsFromJsonp, row 401
 
-. bind _recordsFromLayer to map events: layeradd layerremove update ecc, row 389
+. bind _recordsFromLayer to map events: layeradd layerremove update ecc, row 411
 
-. implement filter by element type: marker|polyline|circle..., row 390
+. implement filter by element type: marker|polyline|circle..., row 412
 
-. caching retRecords while layerSearch not change, controlling on 'load' event, row 391
+. caching retRecords while layerSearch not change, controlling on 'load' event, row 413
 
-. return also marker! in _recordsFromLayer, row 392
+. return also marker! in _recordsFromLayer, row 414
 
-. important optimization!!! always append data in this._recordsCache, row 479
+. important optimization!!! always append data in this._recordsCache, row 497
 
-. here insert function that search inputText FIRST in _recordsCache keys and if not find results.. , row 483
+. here insert function that search inputText FIRST in _recordsCache keys and if not find results.. , row 500
 
-. change structure of _recordsCache, row 487
+. change structure of _recordsCache, row 503
 
-. refact _handleAutoresize now is not accurate, row 522
+. refact _handleAutoresize now is not accurate, row 539
 
-. refact _animateCircle more smooth!, row 590
+. refact _animateCircle more smooth!, row 608
 

demos/custom-tip.html

+<!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></title> 
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
+<link rel="stylesheet" href="/leaflet/leaflet.css" />
+<link rel="stylesheet" href="../leaflet-search.css" />
+<link rel="stylesheet" href="../style.css" />
+</head>
+
+<body>
+<h3><a href="../">Leaflet.Control.Search</a></h3>
+
+<h4>Custom Tip Example: <em>customize each tip in menu</em></h4>
+<div id="map"></div>
+
+<div id="post-it">
+<b>Search values:</b><br />
+aquamarine, black, blue, cyan, darkblue, darkred, darkgray, dodgerblue, gray, green, red, skyblue, yellow, white ...
+</div>
+
+<script src="/leaflet/leaflet.js"></script>
+<script src="../leaflet-search.js"></script>
+<script>
+
+	//sample data values for populate map
+	var data = [
+		{"loc":[41.575330,13.102411], "title":"aquamarine"},
+		{"loc":[41.575730,13.002411], "title":"black"},
+		{"loc":[41.807149,13.162994], "title":"blue"},
+		{"loc":[41.507149,13.172994], "title":"chocolate"},
+		{"loc":[41.847149,14.132994], "title":"coral"},
+		{"loc":[41.219190,13.062145], "title":"cyan"},
+		{"loc":[41.344190,13.242145], "title":"darkblue"},	
+		{"loc":[41.679190,13.122145], "title":"darkred"},
+		{"loc":[41.329190,13.192145], "title":"darkgray"},
+		{"loc":[41.379290,13.122545], "title":"dodgerblue"},
+		{"loc":[41.409190,13.362145], "title":"gray"},
+		{"loc":[41.794008,12.583884], "title":"green"},	
+		{"loc":[41.805008,12.982884], "title":"greenyellow"},
+		{"loc":[41.536175,13.273590], "title":"red"},
+		{"loc":[41.516175,13.373590], "title":"rosybrown"},
+		{"loc":[41.506175,13.173590], "title":"royalblue"},
+		{"loc":[41.836175,13.673590], "title":"salmon"},
+		{"loc":[41.796175,13.570590], "title":"seagreen"},
+		{"loc":[41.436175,13.573590], "title":"seashell"},
+		{"loc":[41.336175,13.973590], "title":"silver"},
+		{"loc":[41.236175,13.273590], "title":"skyblue"},
+		{"loc":[41.546175,13.473590], "title":"yellow"},
+		{"loc":[41.239190,13.032145], "title":"white"}
+	];
+
+	var map = new L.Map('map', {zoom: 9, center: new L.latLng(data[0].loc) });	//set center from first location
+
+	map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));	//base layer
+
+	var markersLayer = new L.LayerGroup();	//layer contain searched elements
+	map.addLayer(markersLayer);
+	
+	function customTip(record)
+	{
+		var tip = L.DomUtil.create('a', 'colortip');
+		console.log("record is ",record);
+
+		tip.href = "#"+record.title;
+		tip.innerHTML = record.title;
+		
+		var subtip = L.DomUtil.create('em', 'subtip', tip);
+		subtip.style.display = 'inline-block';
+		subtip.style.float = 'right';
+		subtip.style.width = '14px';
+		subtip.style.height = '14px';
+		subtip.style.backgroundColor = record.title;
+		
+		return tip;
+	}
+
+	map.addControl( new L.Control.Search({layer: markersLayer, callTip: customTip, autoType: false }) );  //inizialize search control
+
+	////////////populate map with markers from sample data
+	for(i in data) {
+		var title = data[i].title,	//value searched
+			loc = data[i].loc,		//position found
+			marker = new L.Marker(new L.latLng(loc), {title: title} );//se property searched
+		marker.bindPopup('title: '+ title );
+		markersLayer.addLayer(marker);
+	}
+
+</script>
+
+<div id="copy">powered by <a href="mailto:stefano.cudini@gmail.com">Stefano Cudini</a></div>
+</body>
+</html>

demos/jsonp-filtered.html

 <link rel="stylesheet" href="/leaflet/leaflet.css" />
 <link rel="stylesheet" href="../leaflet-search.css" />
 <link rel="stylesheet" href="../style.css" />
+<style>
+#jsonresp {
+	width:10em;
+	min-height:10.5em;
+	margin-left:1em;
+	padding:.25em;
+	overflow-y:scroll;
+	float:left;
+	background:#eee;
+	border:1px solid #aaa;
+	box-shadow: 2px 2px 6px #999;
+	color:#666;
+}
+</style>
 </head>
 
 <body>
 <small><a href="http://open.mapquestapi.com/nominatim/">open.mapquestapi.com</a></small>
 </div>
 
+<div id="jsonresp">native JSON</div>
+
 <script src="/leaflet/leaflet.js"></script>
 <script src="../leaflet-search.js"></script>
 <script>
 	var map = new L.Map('map', {zoom: 9, center: new L.latLng([41.575730,13.002411]) });
 	map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));	//base layer
 	
-	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}&format=json&osm_type=N&limit=100';
+	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}'+
+				   '&format=json&osm_type=N&limit=100&addressdetails=0';
 	//third party jsonp service	
 
 	function filterJSONCall(rawjson) {	//callback that remap fields name
 		var json = {},
-			key, loc;
-		
-		//console.log(rawjson);
-	
+			key, loc, disp = [];
+			
 		for(var i in rawjson)
-		{
-			key = rawjson[i].display_name;	//search key
-			section = rawjson[i].class;	//search key
+		{	
+			disp = rawjson[i].display_name.split(',');	
+
+			key = disp[0] +', '+ disp[1];
+			
 			loc = L.latLng( rawjson[i].lat, rawjson[i].lon );
-			json[ key ] = loc;
-			//json[ key ].formatted = "<b>" + key + "</b> - " + section; // Formatted tooltip (icons, thumbnails etc.)
-			json[ key ].section = section;
+			
+			json[ key ]= loc;	//key,value format
 		}
 		
 		return json;
 			filterJSON: filterJSONCall,
 			animateLocation: false,
 			markerLocation: true,
-			zoom:10,
+			zoom: 10,
 			minLength: 2,
 			autoType: false
 		};

demos/multisection.html

 <body>
 <h3><a href="../">Leaflet.Control.Search</a></h3>
 
-<h4>Multi-Section Tooltip Example: <em>display results under headers by type of geographic feature.</em></h4>
+<h4>Multi-Section Tooltip Example: <em>display results under sections by type of geographic feature.</em></h4>
 <div id="map"></div>
 
 <div id="post-it">
 <small><a href="http://open.mapquestapi.com/nominatim/">open.mapquestapi.com</a></small>
 </div>
 
+<div id="jsonresp">native JSON</div>
+
 <script src="/leaflet/leaflet.js"></script>
 <script src="../leaflet-search.js"></script>
 <script>
 	var map = new L.Map('map', {zoom: 9, center: new L.latLng([41.575730,13.002411]) });
 	map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));	//base layer
 	
-	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}&format=json&osm_type=N&limit=100';
+	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}'+
+				   '&format=json&osm_type=N&limit=100&addressdetails=0';
 	//third party jsonp service	
 
-	function filter(rawjson) {	//callback that remap fields name
+	function customTip(record)
+	{
+		var tip = L.DomUtil.create('a', 'colortip');
+
+		tip.href = "#"+record.key;
+		tip.innerHTML = "<img src=" + record.icon + ">" + record.key;
+		
+		return tip;
+	}
+
+
+	function filterJSONCall(rawjson) {	//callback that remap fields name
 		var json = {},
-			key, loc, section;
-		
-		for(var i in rawjson)
-		{
-			key = rawjson[i].display_name;	//search key
+			key, loc, disp = [];
+
+		for(var i in rawjson) {
+			disp = rawjson[i].display_name.split(',');
+
+			key = disp[0] +', '+ disp[1];
+
 			loc = L.latLng( rawjson[i].lat, rawjson[i].lon );
-			section = rawjson[i].class;
+
 			json[ key ]= loc;	//key,value format
-			json[key].section = section;	//search key
-			//json[key].formatted = key + ' -- <b>' + section + '</b>';
+
+			json[ key ].section = rawjson[i].type; //tip category
+			json[ key ].key = key; //tip text
+			json[ key ].icon = rawjson[i].icon; //tip icon
 		}
-	
+
 		return json;
 	}
 
-	map.addControl( new L.Control.Search({searchJsonpUrl: jsonpurl, searchJsonpFilter: filter, searchLimit: 5 }) ); // TODO: pass hierarchy array here..or somewhere.
+	var searchOpts = {
+			jsonpUrl: jsonpurl,
+			filterJSON: filterJSONCall,
+			animateLocation: false,
+			markerLocation: true,
+			zoom: 10,
+			minLength: 2,
+			autoType: true,
+			tipHierarchy: [ "key", "section" ],
+			callTip: customTip,
+			tooltipLimit: 5
+		};
+
+	map.addControl( new L.Control.Search(searchOpts) );
 
 </script>
 
 
 $JSON = json_encode($fdata,true);
 
-#if($_SERVER['REMOTE_ADDR']=='127.0.0.1') sleep(1);
+if($_SERVER['REMOTE_ADDR']=='127.0.0.1') sleep(1);
 //simulate connection latency for localhost tests
 @header("Content-type: application/json; charset=utf-8");
 
 </div>
 
 <div class="maptest_wrap">
-<h4>searchCall ajax</h4>
+<h4>searchCall AJAX</h4>
 <div id="map_ajax" class="maptest"></div>
 <script>
 
 					resultsraw = json;
 			}
 		});
-	
+
 		for(i in resultsraw)	//reformatting results in key,value
 			results[ resultsraw[i].title ] = resultsraw[i].loc;
 
 </div>
 
 <div class="maptest_wrap">
-<h4>jsonpUrl, filterJSON</h4>
+<h4>jsonpUrl, filterJSON, Cities</h4>
 <div id="map_filterjson" class="maptest"></div>
 <script>
 
 	var map = new L.Map('map_filterjson', {zoom: 9, center: new L.latLng([41.575730,13.002411]) });
 	map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));	//base layer
 	
-	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}&format=json&osm_type=N&limit=100';
+	var jsonpurl = 'http://open.mapquestapi.com/nominatim/v1/search.php?json_callback={c}&q={s}'+
+				   '&format=json&osm_type=N&limit=100&addressdetails=0';
 	//third party jsonp service	
 
 	function filterJSONCall(rawjson) {	//callback that remap fields name
 		var json = {},
-			key, loc;
-		
-		//console.log(rawjson);
-	
+			key, loc, disp = [];
+			
 		for(var i in rawjson)
-		{
-			key = rawjson[i].display_name;	//search key
+		{	
+			disp = rawjson[i].display_name.split(',');	
+
+			key = disp[0] +', '+ disp[1];
+			
 			loc = L.latLng( rawjson[i].lat, rawjson[i].lon );
+			
 			json[ key ]= loc;	//key,value format
 		}
 		
 </div>
 
 <div class="maptest_wrap">
-<h4>searchCall ajax bulk</h4>
+<h4>searchCall ajax bulk, Cities</h4>
 <div id="map_ajaxbulk" class="maptest"></div>
 <script>
 
 </script>
 </div>
 
+
+<div class="maptest_wrap">
+<h4>search in layer, Custom Tip</h4>
+<div id="map_customtip" class="maptest"></div>
+<script>
+
+	var map = new L.Map('map_customtip', {zoom: 9, center: new L.latLng(data[0].loc) });	//set center from first location
+
+	map.addLayer(new L.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'));	//base layer
+
+	var markersLayer = new L.LayerGroup();	//layer contain searched elements
+	map.addLayer(markersLayer);
+
+	function customTip(text)
+	{
+		var tip = L.DomUtil.create('a', 'colortip');
+
+		tip.href = "#"+text;
+		tip.innerHTML = text;
+		
+		var subtip = L.DomUtil.create('em', 'subtip', tip);
+		subtip.style.display = 'inline-block';
+		subtip.style.float = 'right';
+		subtip.style.width = '14px';
+		subtip.style.height = '14px';
+		subtip.style.backgroundColor = text;
+		
+		return tip;
+	}
+
+	map.addControl( new L.Control.Search({layer: markersLayer, callTip: customTip, autoType: false }) );  //inizialize search control
+
+	////////////populate map with markers from sample data
+	for(i in data) {
+		var title = data[i].title,	//value searched
+			loc = data[i].loc,		//position found
+			marker = new L.Marker(new L.latLng(loc), {title: title} );//se property searched
+		marker.bindPopup('title: '+ title );
+		markersLayer.addLayer(marker);
+	}
+</script>
+</div>
+
 <div id="copy">powered by <a href="mailto:stefano.cudini@gmail.com">Stefano Cudini</a></div>
 </body>
 </html>
 		<li>Autozoom on location founded</li>
 		<li>Autoresize textbox</li>
 		<li>many options to customize the behavior</li>
+		<li>customize tip in autocomplete menu</li>
 	</ul>
 
 	<h4>Demos</h4>
 		<li><a href="demos/ajax.html">Ajax</a></li>
 		<li><a href="demos/jsonp.html">Jsonp</a></li>
 		<li><a href="demos/jsonp-filtered.html">Jsonp Filtered</a></li>
+		<li><a href="demos/ajax-bulk.html">Bulk data</a></li>
 		<li><a href="demos/multisection.html">Multi-Section Tooltip</a></li>
-		<li><a href="demos/ajax-bulk.html">Bulk data</a></li>	
+		<li><a href="demos/custom-tip.html">Custom Tip</a></li>	
 	</ul>
 </div>
 

leaflet-search.css

 	background-color: #fff;
 }
 .leaflet-control-search .search-alert {
+	cursor:pointer;
 	clear:both;
 	font-size:.75em;
 	margin-bottom:5px;
 	padding:0 .25em;
 	color:#e00;
-	background-color: rgba(255, 255, 255, 0.75);
+	font-weight:bold;
 	border-radius:.25em;
 }
 

leaflet-search.js

  * Copyright 2013, Stefano Cudini - stefano.cudini@gmail.com
  * Licensed under the MIT license.
  */
+(function() {//closure for hide SearchMarker
 
-L.Control.SearchMarker = L.Marker.extend({
+var SearchMarker = L.Marker.extend({
 //extended L.Marker for create new type of marker has animated circle around
 //and has new methods: .hide() .show() .setTitle() .animate()
-//TODO start L.Control.SearchMarker.animation after setView or panTo, maybe with map.on('moveend')...
+//TODO start animation after setView or panTo, maybe with map.on('moveend')...
 	includes: L.Mixin.Events,
 	
 	options: {
 		stroke: true,
 		fill: false,
 		title: '',
+//TODO add custom icon!		
 		marker: false	//show icon optional, show only circleLoc
 	},
 	
 	},
 
 	animate: function() {
-	//TODO refact L.Control.SearchMarker.animate() more smooth! and use bringToFront()
+	//TODO refact animate() more smooth! and use bringToFront()
 		var circle = this._circleLoc,
 			tInt = 200,	//time interval
 			ss = 10,	//frames
 			mr = parseInt(circle._radius/ss),
-			oldrad = circle._radius,
-			newrad = circle._radius * 2,
+			oldrad = this.options.radius,
+			newrad = circle._radius * 2.5,
 			acc = 0;
 
 		circle._timerAnimLoc = setInterval(function() {
 			if(newrad<oldrad)
 			{
 				clearInterval(circle._timerAnimLoc);
-				circle.setRadius(circle.options.radius);//reset radius
+				circle.setRadius(oldrad);//reset radius
 				//if(typeof afterAnimCall == 'function')
 					//afterAnimCall();
-					//TODO use create event animateEnd in L.Control.SearchMarker 
+					//TODO use create event 'animateEnd' in SearchMarker 
 			}
 		}, tInt);
 		
 		layer: null,				//layer where search markers
 		propertyName: 'title',		//property in marker.options trough filter elements in layer
 		//TODO add option searchLoc or searchLat,searchLon for remapping json data fields
-		searchCall: null,			//callback that fill _recordsCache with key,value table
+		//TODO add event callback onFound(latlng)
+		searchCall: null,			//function that fill _recordsCache, receive searching text in first param
+		callTip: null,				//function that return tip html node, receive text tooltip in first param
 		jsonpUrl: '',				//url for search by jsonp service, ex: "search.php?q={s}&callback={c}"
 		filterJSON: null,			//callback for filtering data to _recordsCache
 		minLength: 1,				//minimal text length for autocomplete
 		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 tooltip if enabled tipAutoSubmit)
-		//TODO add option for persist markerLoc after autoCollapse!
+		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
 		markerLocation: false,		//draw a marker in location found
+		tipHierarchy: ["key"],
 		zoom: null,					//zoom after pan to location found, default: map.getZoom()
 		text: 'Search...',			//placeholder value	
 		textCancel: 'Cancel',		//title in cancel button
 		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._delayType = 300;		//delay after searchCall
+		this._delayType = 350;
 		this._recordsCache = {};	//key,value table! that store locations! format: key,latlng
 	},
 
 	onAdd: function (map) {
 		this._map = map;
-		this._markerLoc = new L.Control.SearchMarker([0,0],{marker: this.options.markerLocation});
+		this._markerLoc = new SearchMarker([0,0],{marker: this.options.markerLocation});
 		this._layer.addLayer(this._markerLoc);
 		this._layer.addTo(map);
-		
 		this._container = L.DomUtil.create('div', 'leaflet-control-search');
-		this._alert = this._createAlert('search-alert');		
 		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._createButton(this.options.text, 'search-button');
+		this._alert = this._createAlert('search-alert');		
 		return this._container;
 	},
 
 	showAlert: function(text) {
 		this._alert.style.display = 'block';
 		this._alert.innerHTML = text;
-		var that = this;
 		clearTimeout(this.timerAlert);
+		var that = this;		
 		this.timerAlert = setTimeout(function() {
-			that._alert.style.display = 'none';
+			that.hideAlert();
 		},this.options.autoCollapseTime);
 	},
 	
+	hideAlert: function() {
+		this._alert.style.display = 'none';
+	},
+		
 	cancel: function() {
 		this._input.value = '';
 		this._handleKeypress({keyCode:8});//simulate backspace keypress
 		this._input.style.display = 'block';
 		L.DomUtil.addClass(this._container, 'search-exp');	
 		this._input.focus();
+		this._map.on('dragstart', this.collapse, this);		
 	},
 
 	collapse: function() {
 		this._cancel.style.display = 'none';
 		L.DomUtil.removeClass(this._container, 'search-exp');		
 		this._markerLoc.hide();
-		//TODO optional markerLoc.hide in collapse()
-		this._map._container.focus();
+		this._map.off('dragstart', this.collapse, this);		
 	},
 	
 	collapseDelayed: function() {	//collapse after delay, used on_input blur
 		var that = this;
+		clearTimeout(this.timerCollapse);
 		this.timerCollapse = setTimeout(function() {
 			that.collapse();
 		}, this.options.autoCollapseTime);
 	_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;
 	},
 
 		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)
 				L.DomEvent.stopPropagation(e);//disable zoom map
 			}, this)
 			.on(tool, 'mouseover', function(e) {
-				that._input.focus();//collapseDelayedStop
+				that.collapseDelayedStop();
 			}, this);
 		return tool;
 	},
 
 	_createTip: function(text) {
-		var tip = L.DomUtil.create('a', 'search-tip');
-			tip.href = '#',
-			tip.innerHTML = this._recordsCache[ text ].formatted ? this._recordsCache[ text ].formatted : text; // Default to plain text if no formatted text is available.
-		//TODO add new option: the callback for building the tip content from text argument
+		var tip;
 
-		this._tooltip.currentSelection = -1;  //inizialized for _handleArrowSelect()
+		if(this.options.callTip)
+			tip = this.options.callTip.call(this, this._recordsCache[text]); //custom tip content
+		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();
-				this._handleAutoresize();	
+				this._hideTooltip();	
 				if(this.options.tipAutoSubmit)//go to location at once
 					this._handleSubmit();
 			}, this);
 	},
 
 //////end DOM creations
-	_groupBy: function( arr, by ) {
+	_groupBy: function( records, tipHierarchy ) {
 		var groups = {},
 		group,
 		values,
-		i = arr.length,
+		i,
 		j,
 		key;
 
-		// make sure we specified how we want it grouped
-		if( !by ) { return arr; } // TODO: If hierarchy is not defined or is of length 1, just return flat json with no section.
-		while( i-- ) {
+		if( !tipHierarchy ) { return records; } // TODO: If hierarchy is not defined or is of length 1, just return flat json with no section.
+		for ( var key in records ) {
+				values = [];
+				i = tipHierarchy.length;
 
-			// find out group values for this item
-			values = ( typeof(by) === "function" && by( arr[i] ) || 
-					typeof arr[i] === "object" && arr[i][by] || 
-					arr[i] );
-
-			// make sure our group values are an array
-			values = values instanceof Array && values || [ values ]; // If not an array put them in an array.
-
+			while (--i >= 0) {
+				values.push(records[key][this.options.tipHierarchy[i]]);
+			}
 			// group
 			group = groups;
 			for( j = 0; j < values.length; j++ ) { // Vertical search. # of sections
 				key = values[j];
 				group = ( group[key] || ( group[key] = j === values.length - 1 && [] || {} ) );
 			}
-			// for the last group, push the actual item onto the array
-			group = ( group instanceof Array && group || [] ).push( arr[i] );
+			// for the last group, push the actual record onto the array
+			group = ( group instanceof Array && group || [] ).push( records[key] );
 		}
 
 		return groups;
-	},	
+	},
 
 	_toTree: function(treeObj) {
-		var ul = document.createElement("div");
+		var section = document.createElement("div");
 		for(var obj in treeObj) {
-			if (treeObj[obj] instanceof Array) { // leaf.
-				ul.appendChild(this._createTip(obj));
-				continue;
+			if(this._ntip != this.options.tooltipLimit) {
+				if (treeObj[obj] instanceof Array) { // leaf.
+					section.appendChild(this._createTip(obj));
+					++this._ntip;
+					continue;
+				}
+				else { // branch
+					section.innerHTML += obj;
+					section.className = "tooltip-section";
+				}
+				section.appendChild(this._toTree(treeObj[obj]));
 			}
-			else { // branch
-				ul.innerHTML += obj;
-				ul.className = "tooltip-section";
-			}
-			ul.appendChild(this._toTree(treeObj[obj]));
 		}
-		return ul;
+		return section;
 	},
 
-	_showTooltip: function() {	//Filter this._recordsCache with this._input.values and show tooltip
+	_filterRecords: function(text) {	//Filter this._recordsCache case insensitive and much more..
+	
+		var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'),	//remove . * | ( ) ] [
+			text = text.replace(regFilter,''),	  //sanitize text
+			I = this.options.initial ? '^' : '',  //search only initial text
+			regSearch = new RegExp(I + text,'i'),
+			//TODO add option for case sesitive search, also _showLocation
+			frecords = {};
 
-		if(this._input.value.length < this.options.minLength)
-			return this._hideTooltip();
+		for(var key in this._recordsCache)//use .filter or .map
+			if( regSearch.test(key) )
+				frecords[key]= this._recordsCache[key];
+		
+		return frecords;
+	},
+	
+	_showTooltip: function() {
+		
+		var filteredRecords = this._filterRecords( this._input.value );
+			this._ntip = 0;
 
-		var regFilter = new RegExp("^[.]$|[\[\]|()*]",'g'),	//remove . * | ( ) ] [
-			text = this._input.value.replace(regFilter,''),		//sanitize text
-			I = this.options.initial ? '^' : '',  //search for initial text
-			regSearch = new RegExp(I + text,'i'),	//for search in _recordsCache
-			ntip = 0;
-		
 		this._tooltip.innerHTML = '';
-		
-		var recordsArray = [];
-		for (var key in this._recordsCache) {
-			if(regSearch.test(key))//search in records
-			{
-				if (ntip == this.options.tooltipLimit) break;
-				this._recordsCache[key].key = key;
-				recordsArray.push(this._recordsCache[key]);
-				this._recordsCache[ key ]._index = ntip;
-				ntip++;
-			}
-		}
+		this._tooltip.currentSelection = -1;  //inizialized for _handleArrowSelect()
 
-		var groups = this._groupBy( recordsArray, function( item ) { return [ item.section, item.key ]; } );
+		var groups = this._groupBy( filteredRecords, this.options.tipHierarchy );
 		this._tooltip.appendChild(this._toTree(groups));
 		
-		if(ntip > 0) {
+		if(this._ntip > 0)
+		{
 			this._tooltip.style.display = 'block';
 			if(this._autoTypeTmp)
 				this._autoType();
 			this._hideTooltip();
 
 		this._tooltip.scrollTop = 0;
-		return ntip;
 	},
 
 	_hideTooltip: function() {
 		//TODO use: throw new Error("my message");on error
 		return jsonret;
 	},
-	
-	_recordsFromJsonp: function(inputText, callAfter) {  //extract searched records from remote jsonp service
+	//TODO make new method for ajax requestes using XMLHttpRequest
+	_recordsFromJsonp: function(text, callAfter) {  //extract searched records from remote jsonp service
 		
 		var that = this;
 		L.Control.Search.callJsonp = function(data) {	//jsonp callback
 			var fdata = that._filterJSON.apply(that,[data]);//defined in inizialize...
 			callAfter(fdata);
 		}
-		var scriptNode = L.DomUtil.create('script','', document.getElementsByTagName('body')[0] ),			
-			url = L.Util.template(this.options.jsonpUrl, {s: inputText, c:"L.Control.Search.callJsonp"});
+		var script = L.DomUtil.create('script','search-jsonp', document.getElementsByTagName('body')[0] ),			
+			url = L.Util.template(this.options.jsonpUrl, {s: text, c:"L.Control.Search.callJsonp"});
 			//parsing url
 			//rnd = '&_='+Math.floor(Math.random()*10000);
 			//TODO add rnd param or randomize callback name! in recordsFromJsonp
-
-		scriptNode.type = 'text/javascript';
-		scriptNode.src = url;
-		return callAfter;
+		script.type = 'text/javascript';
+		script.src = url;
+		return this;
 	},
 
 	_recordsFromLayer: function() {	//return table: key,value from layer
 
 	_autoType: function() {
 		
-		var start = this._input.value.length;
-		for (var key in this._recordsCache) if (this._recordsCache[ key ]._index == 0) firstRecord = key;
-		var end = firstRecord.length;
+		//TODO implements autype without selection(useful for mobile device)
+		
+		var start = this._input.value.length,
+			//firstRecord = this._tooltip.firstChild._text,
+			firstRecord = this._tooltip.getElementsByClassName('search-tip')[0],
+			end = firstRecord.length;
 			
-		this._input.value = firstRecord;
+		this._input.value = firstRecord.text;
 		this._handleAutoresize();
 		
 		if (this._input.createTextRange) {
 		}
 	},
 
+	_hideAutoType: function() {	// deselect text:
+
+		var sel;
+		if ((sel = this._input.selection) && sel.empty) {
+			sel.empty();
+		}
+		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)
 	},
 	
 	_fillRecordsCache: function() {
-	
-		var inputText = this._input.value;
-
 //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(searchCall,jsonpUrl or options.layer)
-		//and run this._showTooltip
-
+//TODO here insert function that search inputText FIRST in _recordsCache keys and if not find results.. 
+//run one of callbacks search(searchCall,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;
+		
 		L.DomUtil.addClass(this._container, 'search-load');
 
-		if(this.options.searchCall)	//PERSONAL SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
+		if(this.options.searchCall)	//CUSTOM SEARCH CALLBACK(USUALLY FOR AJAX SEARCHING)
 		{
-			this._recordsCache = this.options.searchCall.apply(this,[inputText]);
-			
-			if(this._recordsCache)
-				this._showTooltip();
+			this._recordsCache = this.options.searchCall.apply(this, [inputText] );
+
+			this._showTooltip();
 
 			L.DomUtil.removeClass(this._container, 'search-load');
 			//FIXME removeClass .search-load apparently executed before searchCall!! A BIG MYSTERY!
 		}
 	},
 	
-	// TODO: Should resize max search box size when map is resized.
+	//FIXME _handleAutoresize Should resize max search box size when map is resized.
 	_handleAutoresize: function() {	//autoresize this._input
 	//TODO refact _handleAutoresize now is not accurate
 		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;
 	},
 
-	// FIXME: Scrolling Up should work better with multisection tooltips.
 	_handleArrowSelect: function(velocity) {
-	
-		var searchTips = this._tooltip.getElementsByTagName('a');
-		
-		for (i=0; i<searchTips.length; i++) {	// Erase all highlighting
+		var searchTips = this._tooltip.getElementsByClassName('search-tip');
+			
+		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');
 			
 			L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select');
 			
-			for (var key in this._recordsCache)
-				if (this._recordsCache[ key ]._index == this._tooltip.currentSelection)
-					this._input.value = key;
+			this._input.value = this._tooltip.getElementsByClassName('search-tip-select')[0].text;
 
 			// scroll:
 			var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop;
 				this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight;
 			}
 			else if (tipOffsetTop <= this._tooltip.scrollTop) {
-				this._tooltip.scrollTop = tipOffsetTop;
+				this._tooltip.scrollTop = tipOffsetTop - 20; // TODO: Calculate total height of section headers and use it instead of "20"
 			}
 		}
 	},
 
-	_handleSubmit: function() {
+	_handleSubmit: function() {	//button and tooltip click and enter submit
 
-		// deselect text:
-		var sel;
-		if ((sel = this._input.selection) && sel.empty) {
-			sel.empty();
-		}
-		else {
-			if (this._input.getSelection) {
-				this._input.getSelection().removeAllRanges();
-			}
-			this._input.selectionStart = this._input.selectionEnd;
-		}
+		this._hideAutoType();
+		
+		this.hideAlert();
+		this._hideTooltip();
 
 		if(this._input.style.display == 'none')	//on first click show _input only
 			this.expand();
 				this.collapse();
 			else
 			{
-				if( this._findLocation(this._input.value)===false )
-					this.showAlert( this.options.textErr );//location not found, alert!
+				var loc = this.getLocation(this._input.value);
+				
+				if(loc)
+					this._showLocation(loc,this._input.value);
+				else
+					this.showAlert();
+				//this.collapse();
+				//FIXME if collapse in _handleSubmit hide _markerLoc!
 			}
 		}
-		this._input.focus();	//block collapseDelayed after _button blur
 	},
-	
-	_animateCircle: function(circle, afterAnimCall) {
-	//TODO refact _animateCircle more smooth!
 
-		var tInt = 200,//time interval
-			ss = 10,//animation frames
-			mr = parseInt(circle._radius/ss),
-			newrad = circle._radius * 2,
-			acc = 0;
+	getLocation: function(key) {	//extract latlng from _recordsCache
 
-		circle._timerAnimLoc = setInterval(function() {  //animation
-			acc += 0.5;
-			mr += acc;	//adding acceleration
-			newrad -= mr;
+		if( this._recordsCache.hasOwnProperty(key) )
+			return this._recordsCache[this._input.value];//then after use .loc attribute
+		else
+			return false;
+	},
+
+	_showLocation: function(latlng, title) {	//set location on map from _recordsCache
 			
-			circle.setRadius(newrad);
+		if(this.options.zoom)
+			this._map.setView(latlng, this.options.zoom);
+		else
+			this._map.panTo(latlng);
 
-			if(newrad<2)//stop animation
-			{
-				clearInterval(circle._timerAnimLoc);
-				circle.setRadius(circle.options.radius);//reset radius
-				if(typeof afterAnimCall == 'function')
-					afterAnimCall();
-			}
-		}, tInt);
-	},
-	
-	_findLocation: function(text) {	//get location from table _recordsCache and pan to map!
-	
-		if( this._recordsCache.hasOwnProperty(text) )
-		{
-			var newCenter = this._recordsCache[text];//search in table key,value
-			
-			if(this.options.zoom)
-				this._map.setView(newCenter, this.options.zoom);
-			else
-				this._map.panTo(newCenter);
-
-			this._markerLoc.setLatLng(newCenter);  //show circle/marker in location found
-			this._markerLoc.setTitle(text);
-			this._markerLoc.show();
-			if(this.options.animateLocation)
-				this._markerLoc.animate();
-			
-			//FIXME autoCollapse option hide this._markerLoc before that visualized!!
-			if(this.options.autoCollapse)
-				this.collapse();			
-
-			return newCenter;
-		}
-//		else
-//			this._markerLoc.hide();//remove this._circleLoc, this._markerLoc from map
-//maybe needless
-		
-		return false;
+		this._markerLoc.setLatLng(latlng);  //show circle/marker in location found
+		this._markerLoc.setTitle(title);
+		this._markerLoc.show();
+		if(this.options.animateLocation)
+			this._markerLoc.animate();
+		//this.fire("locationfound");
+		//FIXME autoCollapse option hide this._markerLoc before that visualized!!
+		if(this.options.autoCollapse)
+			this.collapse();
 	}
 });
+
+}).call(this);
+