vendor: 31,016 bytes, lines 1-1025
1/* 2 Name Data passed Description 3 4 Managed Events: 5 search:locationfound {latlng, title, layer} fired after moved and show markerLocation 6 search:expanded {} fired after control was expanded 7 search:collapsed {} fired after control was collapsed 8 search:cancel {} fired after cancel button clicked 9 10 Public methods: 11 setLayer() L.LayerGroup() set layer search at runtime 12 showAlert() 'Text message' show alert message 13 searchText() 'Text searched' search text by external code 14*/ 15 16//TODO implement can do research on multiple sources layers and remote 17//TODO history: false, //show latest searches in tooltip 18//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location 19//FIXME option condition problem {autoCollapse: false } 20// 21//TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 22// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip 23// 24//TODO change structure of _recordsCache 25// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...} 26// in this mode every record can have a free structure of attributes, only 'loc' is required 27//TODO important optimization!!! always append data in this._recordsCache 28// now _recordsCache content is emptied and replaced with new data founded 29// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch! 30// 31//TODO here insert function search inputText FIRST in _recordsCache keys and if not find results.. 32// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip 33// 34//TODO change structure of _recordsCache 35// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...} 36// in this way every record can have a free structure of attributes, only 'loc' is required 37 38(function (factory) { 39 if(typeof define === 'function' && define.amd) { 40 //AMD 41 define(['leaflet'], factory); 42 } else if(typeof module !== 'undefined') { 43 // Node/CommonJS 44 module.exports = factory(require('leaflet')); 45 } else { 46 // Browser globals 47 if(typeof window.L === 'undefined') 48 throw 'Leaflet must be loaded first'; 49 factory(window.L); 50 } 51})(function (L) { 52 53 54L.Control.Search = L.Control.extend({ 55 56includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events, 57 58options: { 59 url: '', //url for search by ajax request, ex: "search.php?q={s}". Can be function to returns string for dynamic parameter setting 60 layer: null, //layer where search markers(is a L.LayerGroup) 61 sourceData: null, //function to fill _recordsCache, passed searching text by first param and callback in second 62 //TODO implements uniq option 'sourceData' to recognizes source type: url,array,callback or layer 63 jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback" 64 propertyLoc: 'loc', //field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title' 65 propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer, 66 formatData: null, //callback for reformat all data from source to indexed data object 67 filterData: null, //callback for filtering data from text searched, params: textSearch, allRecords 68 moveToLocation: null, //callback run on location found, params: latlng, title, map 69 buildTip: null, //function to return row tip html node(or html string), receive text tooltip in first param 70 container: '', //container id to insert Search Control 71 zoom: null, //default zoom level for move to location 72 minLength: 1, //minimal text length for autocomplete 73 initial: true, //search elements only by initial text 74 casesensitive: false, //search elements in case sensitive text 75 autoType: true, //complete input with first suggested result and select this filled-in text. 76 delayType: 400, //delay while typing for show tooltip 77 tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit, 0 for no results 78 tipAutoSubmit: true, //auto map panTo when click on tooltip 79 firstTipSubmit: false, //auto select first result con enter click 80 autoResize: true, //autoresize on input change 81 collapsed: true, //collapse search control at startup 82 autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit) 83 autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur 84 textErr: 'Location not found', //error message 85 textCancel: 'Cancel', //title in cancel button 86 textPlaceholder: 'Search...', //placeholder value 87 hideMarkerOnCollapse: false, //remove circle and marker on search control collapsed 88 position: 'topleft', 89 marker: { //custom L.Marker or false for hide 90 icon: false, //custom L.Icon for maker location or false for hide 91 animate: true, //animate a circle over location found 92 circle: { //draw a circle in location found 93 radius: 10, 94 weight: 3, 95 color: '#e03', 96 stroke: true, 97 fill: false 98 } 99 } 100}, 101 102_getPath: function(obj, prop) { 103 var parts = prop.split('.'), 104 last = parts.pop(), 105 len = parts.length, 106 cur = parts[0], 107 i = 1; 108 109 if(len > 0) 110 while((obj = obj[cur]) && i < len) 111 cur = parts[i++]; 112 113 if(obj) 114 return obj[last]; 115}, 116 117_isObject: function(obj) { 118 return Object.prototype.toString.call(obj) === "[object Object]"; 119}, 120 121initialize: function(options) { 122 L.Util.setOptions(this, options || {}); 123 this._inputMinSize = this.options.textPlaceholder ? this.options.textPlaceholder.length : 10; 124 this._layer = this.options.layer || new L.LayerGroup(); 125 this._filterData = this.options.filterData || this._defaultFilterData; 126 this._formatData = this.options.formatData || this._defaultFormatData; 127 this._moveToLocation = this.options.moveToLocation || this._defaultMoveToLocation; 128 this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown 129 this._countertips = 0; //number of tips items 130 this._recordsCache = {}; //key,value table! to store locations! format: key,latlng 131 this._curReq = null; 132}, 133 134onAdd: function (map) { 135 this._map = map; 136 this._container = L.DomUtil.create('div', 'leaflet-control-search'); 137 this._input = this._createInput(this.options.textPlaceholder, 'search-input'); 138 this._tooltip = this._createTooltip('search-tooltip'); 139 this._cancel = this._createCancel(this.options.textCancel, 'search-cancel'); 140 this._button = this._createButton(this.options.textPlaceholder, 'search-button'); 141 this._alert = this._createAlert('search-alert'); 142 143 if(this.options.collapsed===false) 144 this.expand(this.options.collapsed); 145 146 if(this.options.marker) { 147 148 if(this.options.marker instanceof L.Marker || this.options.marker instanceof L.CircleMarker) 149 this._markerSearch = this.options.marker; 150 151 else if(this._isObject(this.options.marker)) 152 this._markerSearch = new L.Control.Search.Marker([0,0], this.options.marker); 153 154 this._markerSearch._isMarkerSearch = true; 155 } 156 157 this.setLayer( this._layer ); 158 159 map.on({ 160 // 'layeradd': this._onLayerAddRemove, 161 // 'layerremove': this._onLayerAddRemove 162 'resize': this._handleAutoresize 163 }, this); 164 return this._container; 165}, 166addTo: function (map) { 167 168 if(this.options.container) { 169 this._container = this.onAdd(map); 170 this._wrapper = L.DomUtil.get(this.options.container); 171 this._wrapper.style.position = 'relative'; 172 this._wrapper.appendChild(this._container); 173 } 174 else 175 L.Control.prototype.addTo.call(this, map); 176 177 return this; 178}, 179 180onRemove: function(map) { 181 this._recordsCache = {}; 182 // map.off({ 183 // 'layeradd': this._onLayerAddRemove, 184 // 'layerremove': this._onLayerAddRemove 185 // }, this); 186 map.off({ 187 // 'layeradd': this._onLayerAddRemove, 188 // 'layerremove': this._onLayerAddRemove 189 'resize': this._handleAutoresize 190 }, this); 191}, 192 193// _onLayerAddRemove: function(e) { 194// //without this, run setLayer also for each Markers!! to optimize! 195// if(e.layer instanceof L.LayerGroup) 196// if( L.stamp(e.layer) != L.stamp(this._layer) ) 197// this.setLayer(e.layer); 198// }, 199 200setLayer: function(layer) { //set search layer at runtime 201 //this.options.layer = layer; //setting this, run only this._recordsFromLayer() 202 this._layer = layer; 203 this._layer.addTo(this._map); 204 return this; 205}, 206 207showAlert: function(text) { 208 var self = this; 209 text = text || this.options.textErr; 210 this._alert.style.display = 'block'; 211 this._alert.innerHTML = text; 212 clearTimeout(this.timerAlert); 213 214 this.timerAlert = setTimeout(function() { 215 self.hideAlert(); 216 },this.options.autoCollapseTime); 217 return this; 218}, 219 220hideAlert: function() { 221 this._alert.style.display = 'none'; 222 return this; 223}, 224 225cancel: function() { 226 this._input.value = ''; 227 this._handleKeypress({ keyCode: 8 });//simulate backspace keypress 228 this._input.size = this._inputMinSize; 229 this._input.focus(); 230 this._cancel.style.display = 'none'; 231 this._hideTooltip(); 232 this.fire('search:cancel'); 233 return this; 234}, 235 236expand: function(toggle) { 237 toggle = typeof toggle === 'boolean' ? toggle : true; 238 this._input.style.display = 'block'; 239 L.DomUtil.addClass(this._container, 'search-exp'); 240 if ( toggle !== false ) { 241 this._input.focus(); 242 this._map.on('dragstart click', this.collapse, this); 243 } 244 this.fire('search:expanded'); 245 return this; 246}, 247 248collapse: function() { 249 this._hideTooltip(); 250 this.cancel(); 251 this._alert.style.display = 'none'; 252 this._input.blur(); 253 if(this.options.collapsed) 254 { 255 this._input.style.display = 'none'; 256 this._cancel.style.display = 'none'; 257 L.DomUtil.removeClass(this._container, 'search-exp'); 258 if (this.options.hideMarkerOnCollapse) { 259 this._map.removeLayer(this._markerSearch); 260 } 261 this._map.off('dragstart click', this.collapse, this); 262 } 263 this.fire('search:collapsed'); 264 return this; 265}, 266 267collapseDelayed: function() { //collapse after delay, used on_input blur 268 var self = this; 269 if (!this.options.autoCollapse) return this; 270 clearTimeout(this.timerCollapse); 271 this.timerCollapse = setTimeout(function() { 272 self.collapse(); 273 }, this.options.autoCollapseTime); 274 return this; 275}, 276 277collapseDelayedStop: function() { 278 clearTimeout(this.timerCollapse); 279 return this; 280}, 281 282////start DOM creations 283_createAlert: function(className) { 284 var alert = L.DomUtil.create('div', className, this._container); 285 alert.style.display = 'none'; 286 287 L.DomEvent 288 .on(alert, 'click', L.DomEvent.stop, this) 289 .on(alert, 'click', this.hideAlert, this); 290 291 return alert; 292}, 293 294_createInput: function (text, className) { 295 var self = this; 296 var label = L.DomUtil.create('label', className, this._container); 297 var input = L.DomUtil.create('input', className, this._container); 298 input.type = 'text'; 299 input.size = this._inputMinSize; 300 input.value = ''; 301 input.autocomplete = 'off'; 302 input.autocorrect = 'off'; 303 input.autocapitalize = 'off'; 304 input.placeholder = text; 305 input.style.display = 'none'; 306 input.role = 'search'; 307 input.id = input.role + input.type + input.size; 308 309 label.htmlFor = input.id; 310 label.style.display = 'none'; 311 label.value = text; 312 313 L.DomEvent 314 .disableClickPropagation(input) 315 .on(input, 'keyup', this._handleKeypress, this) 316 .on(input, 'paste', function(e) { 317 setTimeout(function(e) { 318 self._handleKeypress(e); 319 },10,e); 320 }, this) 321 .on(input, 'blur', this.collapseDelayed, this) 322 .on(input, 'focus', this.collapseDelayedStop, this); 323 324 return input; 325}, 326 327_createCancel: function (title, className) { 328 var cancel = L.DomUtil.create('a', className, this._container); 329 cancel.href = '#'; 330 cancel.title = title; 331 cancel.style.display = 'none'; 332 cancel.innerHTML = "<span>⊗</span>";//imageless(see css) 333 334 L.DomEvent 335 .on(cancel, 'click', L.DomEvent.stop, this) 336 .on(cancel, 'click', this.cancel, this); 337 338 return cancel; 339}, 340 341_createButton: function (title, className) { 342 var button = L.DomUtil.create('a', className, this._container); 343 button.href = '#'; 344 button.title = title; 345 346 L.DomEvent 347 .on(button, 'click', L.DomEvent.stop, this) 348 .on(button, 'click', this._handleSubmit, this) 349 .on(button, 'focus', this.collapseDelayedStop, this) 350 .on(button, 'blur', this.collapseDelayed, this); 351 352 return button; 353}, 354 355_createTooltip: function(className) { 356 var self = this; 357 var tool = L.DomUtil.create('ul', className, this._container); 358 tool.style.display = 'none'; 359 L.DomEvent 360 .disableClickPropagation(tool) 361 .on(tool, 'blur', this.collapseDelayed, this) 362 .on(tool, 'mousewheel', function(e) { 363 self.collapseDelayedStop(); 364 L.DomEvent.stopPropagation(e);//disable zoom map 365 }, this) 366 .on(tool, 'mouseover', function(e) { 367 self.collapseDelayedStop(); 368 }, this); 369 return tool; 370}, 371 372_createTip: function(text, val) {//val is object in recordCache, usually is Latlng 373 var tip; 374 375 if(this.options.buildTip) 376 { 377 tip = this.options.buildTip.call(this, text, val); //custom tip node or html string 378 if(typeof tip === 'string') 379 { 380 var tmpNode = L.DomUtil.create('div'); 381 tmpNode.innerHTML = tip; 382 tip = tmpNode.firstChild; 383 } 384 } 385 else 386 { 387 tip = L.DomUtil.create('li', ''); 388 tip.innerHTML = text; 389 } 390 391 L.DomUtil.addClass(tip, 'search-tip'); 392 tip._text = text; //value replaced in this._input and used by _autoType 393 394 if(this.options.tipAutoSubmit) 395 L.DomEvent 396 .disableClickPropagation(tip) 397 .on(tip, 'click', L.DomEvent.stop, this) 398 .on(tip, 'click', function(e) { 399 this._input.value = text; 400 this._handleAutoresize(); 401 this._input.focus(); 402 this._hideTooltip(); 403 this._handleSubmit(); 404 }, this); 405 406 return tip; 407}, 408 409//////end DOM creations 410 411_getUrl: function(text) { 412 return (typeof this.options.url === 'function') ? this.options.url(text) : this.options.url; 413}, 414 415_defaultFilterData: function(text, records) { 416 417 var init, icase, regSearch, frecords = {}; 418 419 text = text.replace(/[.*+?^${}()|[\]\\]/g, ''); 420 //sanitize remove all special characters 421 422 if(text==='') { 423 return []; 424 } 425 426 init = this.options.initial ? '^' : ''; 427 icase = !this.options.casesensitive ? 'i' : undefined; 428 429 regSearch = new RegExp(init + text, icase); 430 431 for (let key in records) { 432 if( regSearch.test(key) ) { 433 frecords[key]= records[key]; 434 } 435 } 436 437 return frecords; 438}, 439 440showTooltip: function(records) { 441 442 this._countertips = 0; 443 this._tooltip.innerHTML = ''; 444 this._tooltip.currentSelection = -1; //inizialized for _handleArrowSelect() 445 446 if(this.options.tooltipLimit) 447 { 448 for (let key in records) //fill tooltip 449 { 450 if(this._countertips === this.options.tooltipLimit) { 451 break; 452 } 453 454 this._countertips++; 455 456 this._tooltip.appendChild( this._createTip(key, records[key]) ); 457 } 458 } 459 460 if(this._countertips > 0) 461 { 462 this._tooltip.style.display = 'block'; 463 464 if(this._autoTypeTmp) { 465 this._autoType(); 466 } 467 468 this._autoTypeTmp = this.options.autoType;//reset default value 469 } 470 else { 471 this._hideTooltip(); 472 } 473 474 this._tooltip.scrollTop = 0; 475 476 return this._countertips; 477}, 478 479_hideTooltip: function() { 480 this._tooltip.style.display = 'none'; 481 this._tooltip.innerHTML = ''; 482 return 0; 483}, 484 485_defaultFormatData: function(json) { //default callback for format data to indexed data 486 var self = this, 487 propName = this.options.propertyName, 488 propLoc = this.options.propertyLoc, 489 jsonret = {}; 490 491 if( L.Util.isArray(propLoc) ) { 492 for (let i in json) { 493 jsonret[ self._getPath(json[i],propName) ]= L.latLng( self._getPath(json[i], propLoc[0]), self._getPath(json[i], propLoc[1]) ); 494 } 495 } 496 else { 497 for (let i in json) { 498 jsonret[ self._getPath(json[i],propName) ]= L.latLng( self._getPath(json[i],propLoc) ); 499 } 500 } 501 //TODO throw new Error("propertyName '"+propName+"' not found in JSON data"); 502 return jsonret; 503}, 504 505_recordsFromJsonp: function(text, callAfter) { //extract searched records from remote jsonp service 506 L.Control.Search.callJsonp = callAfter; 507 var script = L.DomUtil.create('script','leaflet-search-jsonp', document.getElementsByTagName('body')[0] ), 508 url = L.Util.template(this._getUrl(text)+'&'+this.options.jsonpParam+'=L.Control.Search.callJsonp', {s: text}); //parsing url 509 //rnd = '&_='+Math.floor(Math.random()*10000); 510 //TODO add rnd param or randomize callback name! in recordsFromJsonp 511 script.type = 'text/javascript'; 512 script.src = url; 513 return { abort: function() { script.parentNode.removeChild(script); } }; 514}, 515 516_recordsFromAjax: function(text, callAfter) { //Ajax request 517 if (window.XMLHttpRequest === undefined) { 518 window.XMLHttpRequest = function() { 519 try { return new ActiveXObject("Microsoft.XMLHTTP.6.0"); } 520 catch (e1) { 521 try { return new ActiveXObject("Microsoft.XMLHTTP.3.0"); } 522 catch (e2) { throw new Error("XMLHttpRequest is not supported"); } 523 } 524 }; 525 } 526 var IE8or9 = ( L.Browser.ie && !window.atob && document.querySelector ), 527 request = IE8or9 ? new XDomainRequest() : new XMLHttpRequest(), 528 url = L.Util.template(this._getUrl(text), {s: text}); 529 530 //rnd = '&_='+Math.floor(Math.random()*10000); 531 //TODO add rnd param or randomize callback name! in recordsFromAjax 532 533 request.open("GET", url); 534 535 536 request.onload = function() { 537 callAfter( JSON.parse(request.responseText) ); 538 }; 539 request.onreadystatechange = function() { 540 if(request.readyState === 4 && request.status === 200) { 541 this.onload(); 542 } 543 }; 544 545 request.send(); 546 return request; 547}, 548 549_searchInLayer: function(layer, retRecords, propName) { 550 var self = this, loc; 551 552 if(layer instanceof L.Control.Search.Marker) return; 553 554 if(layer instanceof L.Marker || layer instanceof L.CircleMarker) 555 { 556 if(self._getPath(layer.options,propName)) 557 { 558 loc = layer.getLatLng(); 559 loc.layer = layer; 560 retRecords[ self._getPath(layer.options,propName) ] = loc; 561 } 562 else if(self._getPath(layer.feature.properties,propName)) 563 { 564 loc = layer.getLatLng(); 565 loc.layer = layer; 566 retRecords[ self._getPath(layer.feature.properties,propName) ] = loc; 567 } 568 else { 569 //throw new Error("propertyName '"+propName+"' not found in marker"); 570 console.warn("propertyName '"+propName+"' not found in marker"); 571 } 572 } 573 else if(layer instanceof L.Path || layer instanceof L.Polyline || layer instanceof L.Polygon) 574 { 575 if(self._getPath(layer.options,propName)) 576 { 577 loc = layer.getBounds().getCenter(); 578 loc.layer = layer; 579 retRecords[ self._getPath(layer.options,propName) ] = loc; 580 } 581 else if(self._getPath(layer.feature.properties,propName)) 582 { 583 loc = layer.getBounds().getCenter(); 584 loc.layer = layer; 585 retRecords[ self._getPath(layer.feature.properties,propName) ] = loc; 586 } 587 else { 588 //throw new Error("propertyName '"+propName+"' not found in shape"); 589 console.warn("propertyName '"+propName+"' not found in shape"); 590 } 591 } 592 else if(layer.hasOwnProperty('feature'))//GeoJSON 593 { 594 if(layer.feature.properties.hasOwnProperty(propName)) 595 { 596 if(layer.getLatLng && typeof layer.getLatLng === 'function') { 597 loc = layer.getLatLng(); 598 loc.layer = layer; 599 retRecords[ layer.feature.properties[propName] ] = loc; 600 } else if(layer.getBounds && typeof layer.getBounds === 'function') { 601 loc = layer.getBounds().getCenter(); 602 loc.layer = layer; 603 retRecords[ layer.feature.properties[propName] ] = loc; 604 } else { 605 console.warn("Unknown type of Layer"); 606 } 607 } 608 else { 609 //throw new Error("propertyName '"+propName+"' not found in feature"); 610 console.warn("propertyName '"+propName+"' not found in feature"); 611 } 612 } 613 else if(layer instanceof L.LayerGroup) 614 { 615 layer.eachLayer(function (layer) { 616 self._searchInLayer(layer, retRecords, propName); 617 }); 618 } 619}, 620 621_recordsFromLayer: function() { //return table: key,value from layer 622 var self = this, 623 retRecords = {}, 624 propName = this.options.propertyName; 625 626 this._layer.eachLayer(function (layer) { 627 self._searchInLayer(layer, retRecords, propName); 628 }); 629 630 return retRecords; 631}, 632 633_autoType: function() { 634 635 //TODO implements autype without selection(useful for mobile device) 636 637 var start = this._input.value.length, 638 firstRecord = this._tooltip.firstChild ? this._tooltip.firstChild._text : '', 639 end = firstRecord.length; 640 641 if (firstRecord.indexOf(this._input.value) === 0) { // If prefix match 642 this._input.value = firstRecord; 643 this._handleAutoresize(); 644 645 if (this._input.createTextRange) { 646 var selRange = this._input.createTextRange(); 647 selRange.collapse(true); 648 selRange.moveStart('character', start); 649 selRange.moveEnd('character', end); 650 selRange.select(); 651 } 652 else if(this._input.setSelectionRange) { 653 this._input.setSelectionRange(start, end); 654 } 655 else if(this._input.selectionStart) { 656 this._input.selectionStart = start; 657 this._input.selectionEnd = end; 658 } 659 } 660}, 661 662_hideAutoType: function() { // deselect text: 663 664 var sel; 665 if ((sel = this._input.selection) && sel.empty) { 666 sel.empty(); 667 } 668 else if (this._input.createTextRange) { 669 sel = this._input.createTextRange(); 670 sel.collapse(true); 671 var end = this._input.value.length; 672 sel.moveStart('character', end); 673 sel.moveEnd('character', end); 674 sel.select(); 675 } 676 else { 677 if (this._input.getSelection) { 678 this._input.getSelection().removeAllRanges(); 679 } 680 this._input.selectionStart = this._input.selectionEnd; 681 } 682}, 683 684_handleKeypress: function (e) { //run _input keyup event 685 var self = this; 686 687 switch(e.keyCode) 688 { 689 case 27://Esc 690 this.collapse(); 691 break; 692 case 13://Enter 693 if(this._countertips == 1 || (this.options.firstTipSubmit && this._countertips > 0)) { 694 if(this._tooltip.currentSelection == -1) { 695 this._handleArrowSelect(1); 696 } 697 } 698 this._handleSubmit(); //do search 699 break; 700 case 38://Up 701 this._handleArrowSelect(-1); 702 break; 703 case 40://Down 704 this._handleArrowSelect(1); 705 break; 706 case 8://Backspace 707 case 45://Insert 708 case 46://Delete 709 this._autoTypeTmp = false;//disable temporarily autoType 710 break; 711 case 37://Left 712 case 39://Right 713 case 16://Shift 714 case 17://Ctrl 715 case 35://End 716 case 36://Home 717 break; 718 default://All keys 719 if(this._input.value.length) 720 this._cancel.style.display = 'block'; 721 else 722 this._cancel.style.display = 'none'; 723 724 if(this._input.value.length >= this.options.minLength) 725 { 726 clearTimeout(this.timerKeypress); //cancel last search request while type in 727 this.timerKeypress = setTimeout(function() { //delay before request, for limit jsonp/ajax request 728 729 self._fillRecordsCache(); 730 731 }, this.options.delayType); 732 } 733 else 734 this._hideTooltip(); 735 } 736 737 this._handleAutoresize(); 738}, 739 740searchText: function(text) { 741 var code = text.charCodeAt(text.length); 742 743 this._input.value = text; 744 745 this._input.style.display = 'block'; 746 L.DomUtil.addClass(this._container, 'search-exp'); 747 748 this._autoTypeTmp = false; 749 750 this._handleKeypress({keyCode: code}); 751}, 752 753_fillRecordsCache: function() { 754 755 var self = this, 756 inputText = this._input.value, records; 757 758 if(this._curReq && this._curReq.abort) 759 this._curReq.abort(); 760 //abort previous requests 761 762 L.DomUtil.addClass(this._container, 'search-load'); 763 764 if(this.options.layer) 765 { 766 //TODO _recordsFromLayer must return array of objects, formatted from _formatData 767 this._recordsCache = this._recordsFromLayer(); 768 769 records = this._filterData( this._input.value, this._recordsCache ); 770 771 this.showTooltip( records ); 772 773 L.DomUtil.removeClass(this._container, 'search-load'); 774 } 775 else 776 { 777 if(this.options.sourceData) 778 this._retrieveData = this.options.sourceData; 779 780 else if(this.options.url) //jsonp or ajax 781 this._retrieveData = this.options.jsonpParam ? this._recordsFromJsonp : this._recordsFromAjax; 782 783 this._curReq = this._retrieveData.call(this, inputText, function(data) { 784 785 self._recordsCache = self._formatData.call(self, data); 786 787 //TODO refact! 788 if(self.options.sourceData) 789 records = self._filterData( self._input.value, self._recordsCache ); 790 else 791 records = self._recordsCache; 792 793 self.showTooltip( records ); 794 795 L.DomUtil.removeClass(self._container, 'search-load'); 796 }); 797 } 798}, 799 800_handleAutoresize: function() { 801 var maxWidth; 802 803 if (this._input.style.maxWidth !== this._map._container.offsetWidth) { 804 maxWidth = this._map._container.clientWidth; 805 806 // other side margin + padding + width border + width search-button + width search-cancel 807 maxWidth -= 10 + 20 + 1 + 30 + 22; 808 809 this._input.style.maxWidth = maxWidth.toString() + 'px'; 810 } 811 812 if (this.options.autoResize && (this._container.offsetWidth + 20 < this._map._container.offsetWidth)) { 813 this._input.size = this._input.value.length < this._inputMinSize ? this._inputMinSize : this._input.value.length; 814 } 815}, 816 817_handleArrowSelect: function(velocity) { 818 819 var searchTips = this._tooltip.hasChildNodes() ? this._tooltip.childNodes : []; 820 821 for (let i=0; i<searchTips.length; i++) { 822 L.DomUtil.removeClass(searchTips[i], 'search-tip-select'); 823 } 824 825 if ((velocity == 1 ) && (this._tooltip.currentSelection >= (searchTips.length - 1))) {// If at end of list. 826 L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select'); 827 } 828 else if ((velocity == -1 ) && (this._tooltip.currentSelection <= 0)) { // Going back up to the search box. 829 this._tooltip.currentSelection = -1; 830 } 831 else if (this._tooltip.style.display != 'none') { 832 this._tooltip.currentSelection += velocity; 833 834 L.DomUtil.addClass(searchTips[this._tooltip.currentSelection], 'search-tip-select'); 835 836 this._input.value = searchTips[this._tooltip.currentSelection]._text; 837 838 // scroll: 839 var tipOffsetTop = searchTips[this._tooltip.currentSelection].offsetTop; 840 841 if (tipOffsetTop + searchTips[this._tooltip.currentSelection].clientHeight >= this._tooltip.scrollTop + this._tooltip.clientHeight) { 842 this._tooltip.scrollTop = tipOffsetTop - this._tooltip.clientHeight + searchTips[this._tooltip.currentSelection].clientHeight; 843 } 844 else if (tipOffsetTop <= this._tooltip.scrollTop) { 845 this._tooltip.scrollTop = tipOffsetTop; 846 } 847 } 848}, 849 850_handleSubmit: function() { //button and tooltip click and enter submit 851 852 this._hideAutoType(); 853 854 this.hideAlert(); 855 this._hideTooltip(); 856 857 if(this._input.style.display == 'none') //on first click show _input only 858 this.expand(); 859 else 860 { 861 if(this._input.value === '') //hide _input only 862 this.collapse(); 863 else 864 { 865 var loc = this._getLocation(this._input.value); 866 867 if(!loc) { 868 this.showAlert(); 869 } 870 else 871 { 872 this.showLocation(loc, this._input.value); 873 this.fire('search:locationfound', { 874 latlng: loc, 875 text: this._input.value, 876 layer: loc.layer ? loc.layer : null 877 }); 878 } 879 } 880 } 881}, 882 883_getLocation: function(key) { //extract latlng from _recordsCache 884 885 if( this._recordsCache.hasOwnProperty(key) ) 886 return this._recordsCache[key];//then after use .loc attribute 887 else 888 return false; 889}, 890 891_defaultMoveToLocation: function(latlng, title, map) { 892 if(this.options.zoom) 893 this._map.setView(latlng, this.options.zoom); 894 else 895 this._map.panTo(latlng); 896}, 897 898showLocation: function(latlng, title) { //set location on map from _recordsCache 899 var self = this; 900 901 self._map.once('moveend zoomend', function(e) { 902 903 if(self._markerSearch) { 904 self._markerSearch.addTo(self._map).setLatLng(latlng); 905 } 906 907 }); 908 909 self._moveToLocation(latlng, title, self._map); 910 //FIXME autoCollapse option hide self._markerSearch before visualized!! 911 if(self.options.autoCollapse) 912 self.collapse(); 913 914 return self; 915} 916}); 917 918L.Control.Search.Marker = L.Marker.extend({ 919 920includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events, 921 922options: { 923 icon: new L.Icon.Default(), 924 animate: true, 925 circle: { 926 radius: 10, 927 weight: 3, 928 color: '#e03', 929 stroke: true, 930 fill: false 931 } 932}, 933 934initialize: function (latlng, options) { 935 L.setOptions(this, options); 936 937 if(options.icon === true) 938 options.icon = new L.Icon.Default(); 939 940 L.Marker.prototype.initialize.call(this, latlng, options); 941 942 if( L.Control.Search.prototype._isObject(this.options.circle) ) 943 this._circleLoc = new L.CircleMarker(latlng, this.options.circle); 944}, 945 946onAdd: function (map) { 947 L.Marker.prototype.onAdd.call(this, map); 948 if(this._circleLoc) { 949 map.addLayer(this._circleLoc); 950 if(this.options.animate) 951 this.animate(); 952 } 953}, 954 955onRemove: function (map) { 956 L.Marker.prototype.onRemove.call(this, map); 957 if(this._circleLoc) 958 map.removeLayer(this._circleLoc); 959}, 960 961setLatLng: function (latlng) { 962 L.Marker.prototype.setLatLng.call(this, latlng); 963 if(this._circleLoc) 964 this._circleLoc.setLatLng(latlng); 965 return this; 966}, 967 968_initIcon: function () { 969 if(this.options.icon) 970 L.Marker.prototype._initIcon.call(this); 971}, 972 973_removeIcon: function () { 974 if(this.options.icon) 975 L.Marker.prototype._removeIcon.call(this); 976}, 977 978animate: function() { 979//TODO refact animate() more smooth! like this: http://goo.gl/DDlRs 980 if(this._circleLoc) 981 { 982 var circle = this._circleLoc, 983 tInt = 200, //time interval 984 ss = 5, //frames 985 mr = parseInt(circle._radius/ss), 986 oldrad = this.options.circle.radius, 987 newrad = circle._radius * 2, 988 acc = 0; 989 990 circle._timerAnimLoc = setInterval(function() { 991 acc += 0.5; 992 mr += acc; //adding acceleration 993 newrad -= mr; 994 995 circle.setRadius(newrad); 996 997 if(newrad<oldrad) 998 { 999 clearInterval(circle._timerAnimLoc); 1000 circle.setRadius(oldrad);//reset radius 1001 //if(typeof afterAnimCall == 'function') 1002 //afterAnimCall(); 1003 //TODO use create event 'animateEnd' in L.Control.Search.Marker 1004 } 1005 }, tInt); 1006 } 1007 1008 return this; 1009} 1010}); 1011 1012L.Map.addInitHook(function () { 1013 if (this.options.searchControl) { 1014 this.searchControl = L.control.search(this.options.searchControl); 1015 this.addControl(this.searchControl); 1016 } 1017}); 1018 1019L.control.search = function (options) { 1020 return new L.Control.Search(options); 1021}; 1022 1023return L.Control.Search; 1024 1025});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.