Your IP : 216.73.216.84


Current Path : /home/helpink/www/components/com_jbusinessdirectory/assets/libraries/leaflet/
Upload File :
Current File : /home/helpink/www/components/com_jbusinessdirectory/assets/libraries/leaflet/leaflet-search.js

/*
	Name					Data passed			   Description

	Managed Events:
	 search:locationfound	{latlng, title, layer} fired after moved and show markerLocation
	 search:expanded		{}					   fired after control was expanded
	 search:collapsed		{}					   fired after control was collapsed
 	 search:cancel			{}					   fired after cancel button clicked

	Public methods:
	 setLayer()				L.LayerGroup()         set layer search at runtime
	 showAlert()            'Text message'         show alert message
	 searchText()			'Text searched'        search text by external code
*/

// implement can do research on multiple sources layers and remote
// history: false,		//show latest searches in tooltip
// option condition problem {autoCollapse: true, markerLocation: true} not show location
// option condition problem {autoCollapse: false }
//
// here insert function  search inputText FIRST in _recordsCache keys and if not find results..
//  run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
//
// 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
// 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!
//
// here insert function  search inputText FIRST in _recordsCache keys and if not find results..
//  run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
//
// change structure of _recordsCache
//	like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
//	in this way every record can have a free structure of attributes, only 'loc' is required

(function (factory) {
    if(typeof define === 'function' && define.amd) {
        //AMD
        define(['leaflet'], factory);
    } else if(typeof module !== 'undefined') {
        // Node/CommonJS
        module.exports = factory(require('leaflet'));
    } else {
        // Browser globals
        if(typeof window.L === 'undefined')
            throw 'Leaflet must be loaded first';
        factory(window.L);
    }
})(function (L) {


    L.Control.Search = L.Control.extend({

        includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events,

        options: {
            url: '',						//url for search by ajax request, ex: "search.php?q={s}". Can be function to returns string for dynamic parameter setting
            layer: null,					//layer where search markers(is a L.LayerGroup)
            sourceData: null,				//function to fill _recordsCache, passed searching text by first param and callback in second
            // implements uniq option 'sourceData' to recognizes source type: url,array,callback or layer
            jsonpParam: null,				//jsonp param name for search by jsonp service, ex: "callback"
            propertyLoc: 'loc',				//field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title'
            propertyName: 'title',			//property in marker.options(or feature.properties for vector layer) trough filter elements in layer,
            formatData: null,				//callback for reformat all data from source to indexed data object
            filterData: null,				//callback for filtering data from text searched, params: textSearch, allRecords
            moveToLocation: null,			//callback run on location found, params: latlng, title, map
            buildTip: null,					//function to return row tip html node(or html string), receive text tooltip in first param
            container: '',					//container id to insert Search Control
            zoom: null,						//default zoom level for move to location
            minLength: 1,					//minimal text length for autocomplete
            initial: true,					//search elements only by initial text
            casesensitive: false,			//search elements in case sensitive 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, 0 for no results
            tipAutoSubmit: true,			//auto map panTo when click on tooltip
            firstTipSubmit: false,			//auto select first result con enter click
            autoResize: true,				//autoresize on input change
            collapsed: true,				//collapse search control at startup
            autoCollapse: false,			//collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
            autoCollapseTime: 1200,			//delay for autoclosing alert and collapse after blur
            textErr: 'Location not found',	//error message
            textCancel: 'Cancel',		    //title in cancel button
            textPlaceholder: 'Search...',   //placeholder value
            hideMarkerOnCollapse: false,    //remove circle and marker on search control collapsed
            position: 'topleft',
            marker: {						//custom L.Marker or false for hide
                icon: false,				//custom L.Icon for maker location or false for hide
                animate: true,				//animate a circle over location found
                circle: {					//draw a circle in location found
                    radius: 10,
                    weight: 3,
                    color: '#e03',
                    stroke: true,
                    fill: false
                }
            }
        },

        _getPath: function(obj, prop) {
            var parts = prop.split('.'),
                last = parts.pop(),
                len = parts.length,
                cur = parts[0],
                i = 1;

            if(len > 0)
                while((obj = obj[cur]) && i < len)
                    cur = parts[i++];

            if(obj)
                return obj[last];
        },

        _isObject: function(obj) {
            return Object.prototype.toString.call(obj) === "[object Object]";
        },

        initialize: function(options) {
            L.Util.setOptions(this, options || {});
            this._inputMinSize = this.options.textPlaceholder ? this.options.textPlaceholder.length : 10;
            this._layer = this.options.layer || new L.LayerGroup();
            this._filterData = this.options.filterData || this._defaultFilterData;
            this._formatData = this.options.formatData || this._defaultFormatData;
            this._moveToLocation = this.options.moveToLocation || this._defaultMoveToLocation;
            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! to store locations! format: key,latlng
            this._curReq = null;
        },

        onAdd: function (map) {
            this._map = map;
            this._container = L.DomUtil.create('div', 'leaflet-control-search');
            this._input = this._createInput(this.options.textPlaceholder, 'search-input');
            this._tooltip = this._createTooltip('search-tooltip');
            this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
            this._button = this._createButton(this.options.textPlaceholder, 'search-button');
            this._alert = this._createAlert('search-alert');

            if(this.options.collapsed===false)
                this.expand(this.options.collapsed);

            if(this.options.marker) {

                if(this.options.marker instanceof L.Marker || this.options.marker instanceof L.CircleMarker)
                    this._markerSearch = this.options.marker;

                else if(this._isObject(this.options.marker))
                    this._markerSearch = new L.Control.Search.Marker([0,0], this.options.marker);

                this._markerSearch._isMarkerSearch = true;
            }

            this.setLayer( this._layer );

            map.on({
                // 		'layeradd': this._onLayerAddRemove,
                // 		'layerremove': this._onLayerAddRemove
                'resize': this._handleAutoresize
            }, this);
            return this._container;
        },
        addTo: function (map) {

            if(this.options.container) {
                this._container = this.onAdd(map);
                this._wrapper = L.DomUtil.get(this.options.container);
                this._wrapper.style.position = 'relative';
                this._wrapper.appendChild(this._container);
            }
            else
                L.Control.prototype.addTo.call(this, map);

            return this;
        },

        onRemove: function(map) {
            this._recordsCache = {};
            // map.off({
            // 		'layeradd': this._onLayerAddRemove,
            // 		'layerremove': this._onLayerAddRemove
            // 	}, this);
        },

        // _onLayerAddRemove: function(e) {
        // 	//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);
            return this;
        },

        showAlert: function(text) {
            var self = this;
            text = text || this.options.textErr;
            this._alert.style.display = 'block';
            this._alert.innerHTML = text;
            clearTimeout(this.timerAlert);

            this.timerAlert = setTimeout(function() {
                self.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';
            this._hideTooltip();
            this.fire('search:cancel');
            return this;
        },

        expand: function(toggle) {
            toggle = typeof toggle === 'boolean' ? toggle : true;
            this._input.style.display = 'block';
            L.DomUtil.addClass(this._container, 'search-exp');
            if ( toggle !== false ) {
                this._input.focus();
                this._map.on('dragstart click', this.collapse, this);
            }
            this.fire('search:expanded');
            return this;
        },

        collapse: function() {
            this._hideTooltip();
            this.cancel();
            this._alert.style.display = 'none';
            this._input.blur();
            if(this.options.collapsed)
            {
                this._input.style.display = 'none';
                this._cancel.style.display = 'none';
                L.DomUtil.removeClass(this._container, 'search-exp');
                if (this.options.hideMarkerOnCollapse) {
                    this._map.removeLayer(this._markerSearch);
                }
                this._map.off('dragstart click', this.collapse, this);
            }
            this.fire('search:collapsed');
            return this;
        },

        collapseDelayed: function() {	//collapse after delay, used on_input blur
            var self = this;
            if (!this.options.autoCollapse) return this;
            clearTimeout(this.timerCollapse);
            this.timerCollapse = setTimeout(function() {
                self.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 label = L.DomUtil.create('label', className, this._container);
            var input = L.DomUtil.create('input', className, this._container);
            input.type = 'text';
            input.size = this._inputMinSize;
            input.value = '';
            input.autocomplete = 'off';
            input.autocorrect = 'off';
            input.autocapitalize = 'off';
            input.placeholder = text;
            input.style.display = 'none';
            input.role = 'search';
            input.id = input.role + input.type + input.size;

            label.htmlFor = input.id;
            label.style.display = 'none';
            label.value = text;

            L.DomEvent
                .disableClickPropagation(input)
                .on(input, 'keyup', this._handleKeypress, 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 self = this;
            var tool = L.DomUtil.create('ul', className, this._container);
            tool.style.display = 'none';
            L.DomEvent
                .disableClickPropagation(tool)
                .on(tool, 'blur', this.collapseDelayed, this)
                .on(tool, 'mousewheel', function(e) {
                    self.collapseDelayedStop();
                    L.DomEvent.stopPropagation(e);//disable zoom map
                }, this)
                .on(tool, 'mouseover', function(e) {
                    self.collapseDelayedStop();
                }, this);
            return tool;
        },

        _createTip: function(text, val) {//val is object in recordCache, usually is Latlng
            var tip;

            if(this.options.buildTip)
            {
                tip = this.options.buildTip.call(this, 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('li', '');
                tip.innerHTML = text;
            }

            L.DomUtil.addClass(tip, 'search-tip');
            tip._text = text; //value replaced in this._input and used by _autoType

            if(this.options.tipAutoSubmit)
                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._handleSubmit();
                    }, this);

            return tip;
        },

        //////end DOM creations

        _getUrl: function(text) {
            return (typeof this.options.url === 'function') ? this.options.url(text) : this.options.url;
        },

        _defaultFilterData: function(text, records) {

            var I, icase, regSearch, frecords = {};

            text = text.replace(/[.*+?^${}()|[\]\\]/g, '');  //sanitize remove all special characters
            if(text==='')
                return [];

            I = this.options.initial ? '^' : '';  //search only initial text
            icase = !this.options.casesensitive ? 'i' : undefined;

            regSearch = new RegExp(I + text, icase);

            // use .filter or .map
            for(var key in records) {
                if( regSearch.test(key) )
                    frecords[key]= records[key];
            }

            return frecords;
        },

        showTooltip: function(records) {


            this._countertips = 0;
            this._tooltip.innerHTML = '';
            this._tooltip.currentSelection = -1;  //inizialized for _handleArrowSelect()

            if(this.options.tooltipLimit)
            {
                for(var key in records)//fill tooltip
                {
                    if(this._countertips === this.options.tooltipLimit)
                        break;

                    this._countertips++;

                    this._tooltip.appendChild( this._createTip(key, records[key]) );
                }
            }

            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;
        },

        _defaultFormatData: function(json) {	//default callback for format data to indexed data
            var self = this,
                propName = this.options.propertyName,
                propLoc = this.options.propertyLoc,
                i, jsonret = {};

            if( L.Util.isArray(propLoc) )
                for(i in json)
                    jsonret[ self._getPath(json[i],propName) ]= L.latLng( json[i][ propLoc[0] ], json[i][ propLoc[1] ] );
            else
                for(i in json)
                    jsonret[ self._getPath(json[i],propName) ]= L.latLng( self._getPath(json[i],propLoc) );
            // throw new Error("propertyName '"+propName+"' not found in JSON data");
            return jsonret;
        },

        _recordsFromJsonp: function(text, callAfter) {  //extract searched records from remote jsonp service
            L.Control.Search.callJsonp = callAfter;
            var script = L.DomUtil.create('script','leaflet-search-jsonp', document.getElementsByTagName('body')[0] ),
                url = L.Util.template(this._getUrl(text)+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url
            //rnd = '&_='+Math.floor(Math.random()*10000);
            // add rnd param or randomize callback name! in recordsFromJsonp
            script.type = 'text/javascript';
            script.src = url;
            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 IE8or9 = ( L.Browser.ie && !window.atob && document.querySelector ),
                request = IE8or9 ? new XDomainRequest() : new XMLHttpRequest(),
                url = L.Util.template(this._getUrl(text), {s: text});

            //rnd = '&_='+Math.floor(Math.random()*10000);
            // add rnd param or randomize callback name! in recordsFromAjax

            request.open("GET", url);


            request.onload = function() {
                callAfter( JSON.parse(request.responseText) );
            };
            request.onreadystatechange = function() {
                if(request.readyState === 4 && request.status === 200) {
                    this.onload();
                }
            };

            request.send();
            return request;
        },

        _searchInLayer: function(layer, retRecords, propName) {
            var self = this, loc;

            if(layer instanceof L.Control.Search.Marker) return;

            if(layer instanceof L.Marker || layer instanceof L.CircleMarker)
            {
                if(self._getPath(layer.options,propName))
                {
                    loc = layer.getLatLng();
                    loc.layer = layer;
                    retRecords[ self._getPath(layer.options,propName) ] = loc;
                }
                else if(self._getPath(layer.feature.properties,propName))
                {
                    loc = layer.getLatLng();
                    loc.layer = layer;
                    retRecords[ self._getPath(layer.feature.properties,propName) ] = loc;
                }
                else {
                    //throw new Error("propertyName '"+propName+"' not found in marker");
                    console.warn("propertyName '"+propName+"' not found in marker");
                }
            }
            else if(layer instanceof L.Path || layer instanceof L.Polyline || layer instanceof L.Polygon)
            {
                if(self._getPath(layer.options,propName))
                {
                    loc = layer.getBounds().getCenter();
                    loc.layer = layer;
                    retRecords[ self._getPath(layer.options,propName) ] = loc;
                }
                else if(self._getPath(layer.feature.properties,propName))
                {
                    loc = layer.getBounds().getCenter();
                    loc.layer = layer;
                    retRecords[ self._getPath(layer.feature.properties,propName) ] = loc;
                }
                else {
                    //throw new Error("propertyName '"+propName+"' not found in shape");
                    console.warn("propertyName '"+propName+"' not found in shape");
                }
            }
            else if(layer.hasOwnProperty('feature'))//GeoJSON
            {
                if(layer.feature.properties.hasOwnProperty(propName))
                {
                    if(layer.getLatLng && typeof layer.getLatLng === 'function') {
                        loc = layer.getLatLng();
                        loc.layer = layer;
                        retRecords[ layer.feature.properties[propName] ] = loc;
                    } else if(layer.getBounds && typeof layer.getBounds === 'function') {
                        loc = layer.getBounds().getCenter();
                        loc.layer = layer;
                        retRecords[ layer.feature.properties[propName] ] = loc;
                    } else {
                        console.warn("Unknown type of Layer");
                    }
                }
                else {
                    //throw new Error("propertyName '"+propName+"' not found in feature");
                    console.warn("propertyName '"+propName+"' not found in feature");
                }
            }
            else if(layer instanceof L.LayerGroup)
            {
                layer.eachLayer(function (layer) {
                    self._searchInLayer(layer, retRecords, propName);
                });
            }
        },

        _recordsFromLayer: function() {	//return table: key,value from layer
            var self = this,
                retRecords = {},
                propName = this.options.propertyName;

            this._layer.eachLayer(function (layer) {
                self._searchInLayer(layer, retRecords, propName);
            });

            return retRecords;
        },

        _autoType: function() {

            // implements autype without selection(useful for mobile device)

            var start = this._input.value.length,
                firstRecord = this._tooltip.firstChild ? 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
            var self = this;

            switch(e.keyCode)
            {
                case 27://Esc
                    this.collapse();
                    break;
                case 13://Enter
                    if(this._countertips == 1 || (this.options.firstTipSubmit && this._countertips > 0))
                        if(this._tooltip.currentSelection == -1)
                            this._handleArrowSelect(1);
                    this._handleSubmit();	//do search
                    break;
                case 38://Up
                    this._handleArrowSelect(-1);
                    break;
                case 40://Down
                    this._handleArrowSelect(1);
                    break;
                case  8://Backspace
                case 45://Insert
                case 46://Delete
                    this._autoTypeTmp = false;//disable temporarily autoType
                    break;
                case 37://Left
                case 39://Right
                case 16://Shift
                case 17://Ctrl
                case 35://End
                case 36://Home
                    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)
                    {
                        clearTimeout(this.timerKeypress);	//cancel last search request while type in
                        this.timerKeypress = setTimeout(function() {	//delay before request, for limit jsonp/ajax request

                            self._fillRecordsCache();

                        }, this.options.delayType);
                    }
                    else
                        this._hideTooltip();
            }

            this._handleAutoresize();
        },

        searchText: function(text) {
            var code = text.charCodeAt(text.length);

            this._input.value = text;

            this._input.style.display = 'block';
            L.DomUtil.addClass(this._container, 'search-exp');

            this._autoTypeTmp = false;

            this._handleKeypress({keyCode: code});
        },

        _fillRecordsCache: function() {

            var self = this,
                inputText = this._input.value, records;

            if(this._curReq && this._curReq.abort)
                this._curReq.abort();
            //abort previous requests

            L.DomUtil.addClass(this._container, 'search-load');

            if(this.options.layer)
            {
                // _recordsFromLayer must return array of objects, formatted from _formatData
                this._recordsCache = this._recordsFromLayer();

                records = this._filterData( this._input.value, this._recordsCache );

                this.showTooltip( records );

                L.DomUtil.removeClass(this._container, 'search-load');
            }
            else
            {
                if(this.options.sourceData)
                    this._retrieveData = this.options.sourceData;

                else if(this.options.url)	//jsonp or ajax
                    this._retrieveData = this.options.jsonpParam ? this._recordsFromJsonp : this._recordsFromAjax;

                this._curReq = this._retrieveData.call(this, inputText, function(data) {

                    self._recordsCache = self._formatData.call(self, data);

                    // refact!
                    if(self.options.sourceData)
                        records = self._filterData( self._input.value, self._recordsCache );
                    else
                        records = self._recordsCache;

                    self.showTooltip( records );

                    L.DomUtil.removeClass(self._container, 'search-load');
                });
            }
        },

        _handleAutoresize: function() {	//autoresize this._input
            // 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') {
                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
                        });
                    }
                }
            }
        },

        _getLocation: function(key) {	//extract latlng from _recordsCache

            if( this._recordsCache.hasOwnProperty(key) )
                return this._recordsCache[key];//then after use .loc attribute
            else
                return false;
        },

        _defaultMoveToLocation: function(latlng, title, map) {
            if(this.options.zoom)
                this._map.setView(latlng, this.options.zoom);
            else
                this._map.panTo(latlng);
        },

        showLocation: function(latlng, title) {	//set location on map from _recordsCache
            var self = this;

            self._map.once('moveend zoomend', function(e) {

                if(self._markerSearch) {
                    self._markerSearch.addTo(self._map).setLatLng(latlng);
                }

            });

            self._moveToLocation(latlng, title, self._map);
            //FIXME autoCollapse option hide self._markerSearch before visualized!!
            if(self.options.autoCollapse)
                self.collapse();

            return self;
        }
    });

    L.Control.Search.Marker = L.Marker.extend({

        includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events,

        options: {
            icon: new L.Icon.Default(),
            animate: true,
            circle: {
                radius: 10,
                weight: 3,
                color: '#e03',
                stroke: true,
                fill: false
            }
        },

        initialize: function (latlng, options) {
            L.setOptions(this, options);

            if(options.icon === true)
                options.icon = new L.Icon.Default();

            L.Marker.prototype.initialize.call(this, latlng, options);

            if( L.Control.Search.prototype._isObject(this.options.circle) )
                this._circleLoc = new L.CircleMarker(latlng, this.options.circle);
        },

        onAdd: function (map) {
            L.Marker.prototype.onAdd.call(this, map);
            if(this._circleLoc) {
                map.addLayer(this._circleLoc);
                if(this.options.animate)
                    this.animate();
            }
        },

        onRemove: function (map) {
            L.Marker.prototype.onRemove.call(this, map);
            if(this._circleLoc)
                map.removeLayer(this._circleLoc);
        },

        setLatLng: function (latlng) {
            L.Marker.prototype.setLatLng.call(this, latlng);
            if(this._circleLoc)
                this._circleLoc.setLatLng(latlng);
            return this;
        },

        _initIcon: function () {
            if(this.options.icon)
                L.Marker.prototype._initIcon.call(this);
        },

        _removeIcon: function () {
            if(this.options.icon)
                L.Marker.prototype._removeIcon.call(this);
        },

        animate: function() {
            // refact animate() more smooth! like this: http://goo.gl/DDlRs
            if(this._circleLoc)
            {
                var circle = this._circleLoc,
                    tInt = 200,	//time interval
                    ss = 5,	//frames
                    mr = parseInt(circle._radius/ss),
                    oldrad = this.options.circle.radius,
                    newrad = circle._radius * 2,
                    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();
                        // use create event 'animateEnd' in L.Control.Search.Marker
                    }
                }, 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);
    };

    return L.Control.Search;

});