Files
bqkc/assets/lib/leaflet/leaflet.fusesearch.js
Nasir Anthony Montalvo 526096840e Initial commit
2025-11-13 14:48:58 -06:00

343 lines
11 KiB
JavaScript

// From http://www.tutorialspoint.com/javascript/array_map.htm
if (!Array.prototype.map)
{
Array.prototype.map = function(fun /*, thisp*/)
{
var len = this.length;
if (typeof fun !== "function")
throw new TypeError();
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++)
{
if (i in this)
res[i] = fun.call(thisp, this[i], i, this);
}
return res;
};
}
L.Control.FuseSearch = L.Control.extend({
includes: L.Evented.prototype,
options: {
position: 'topright',
title: 'Search',
panelTitle: '',
placeholder: 'Search',
caseSensitive: false,
threshold: 0.5,
maxResultLength: null,
showResultFct: null,
showInvisibleFeatures: true
},
initialize: function(options) {
L.setOptions(this, options);
this._panelOnLeftSide = (this.options.position.indexOf("left") !== -1);
},
onAdd: function(map) {
var ctrl = this._createControl();
this._createPanel(map);
this._setEventListeners();
map.invalidateSize();
return ctrl;
},
onRemove: function(map) {
this.hidePanel(map);
this._clearEventListeners();
this._clearPanel(map);
this._clearControl();
return this;
},
_createControl: function() {
var className = 'leaflet-fusesearch-control',
container = L.DomUtil.create('div', className);
// Control to open the search panel
var butt = this._openButton = L.DomUtil.create('a', 'button', container);
butt.href = '#';
butt.title = this.options.title;
var stop = L.DomEvent.stopPropagation;
L.DomEvent.on(butt, 'click', stop)
.on(butt, 'mousedown', stop)
.on(butt, 'touchstart', stop)
.on(butt, 'mousewheel', stop)
.on(butt, 'MozMousePixelScroll', stop);
L.DomEvent.on(butt, 'click', L.DomEvent.preventDefault);
L.DomEvent.on(butt, 'click', this.showPanel, this);
return container;
},
_clearControl: function() {
// Unregister events to prevent memory leak
var butt = this._openButton;
var stop = L.DomEvent.stopPropagation;
L.DomEvent.off(butt, 'click', stop)
.off(butt, 'mousedown', stop)
.off(butt, 'touchstart', stop)
.off(butt, 'mousewheel', stop)
.off(butt, 'MozMousePixelScroll', stop);
L.DomEvent.off(butt, 'click', L.DomEvent.preventDefault);
L.DomEvent.off(butt, 'click', this.showPanel);
},
_createPanel: function(map) {
var _this = this;
// Create the search panel
var mapContainer = map.getContainer();
var className = 'leaflet-fusesearch-panel',
pane = this._panel = L.DomUtil.create('div', className, mapContainer);
// Make sure we don't drag the map when we interact with the content
var stop = L.DomEvent.stopPropagation;
L.DomEvent.on(pane, 'click', stop)
.on(pane, 'dblclick', stop)
.on(pane, 'mousedown', stop)
.on(pane, 'touchstart', stop)
.on(pane, 'mousewheel', stop)
.on(pane, 'MozMousePixelScroll', stop);
// place the pane on the same side as the control
if (this._panelOnLeftSide) {
L.DomUtil.addClass(pane, 'left');
} else {
L.DomUtil.addClass(pane, 'right');
}
// Intermediate container to get the box sizing right
var container = L.DomUtil.create('div', 'content', pane);
var header = L.DomUtil.create('div', 'header', container);
if (this.options.panelTitle) {
var title = L.DomUtil.create('p', 'panel-title', header);
title.innerHTML = this.options.panelTitle;
}
// Search image and input field
L.DomUtil.create('img', 'search-image', header);
this._input = L.DomUtil.create('input', 'search-input', header);
this._input.maxLength = 30;
this._input.placeholder = this.options.placeholder;
this._input.onkeyup = function(evt) {
var searchString = evt.currentTarget.value;
_this.searchFeatures(searchString);
};
// Close button
var close = this._closeButton = L.DomUtil.create('a', 'close', header);
close.innerHTML = '&times;';
L.DomEvent.on(close, 'click', this.hidePanel, this);
// Where the result will be listed
this._resultList = L.DomUtil.create('div', 'result-list', container);
return pane;
},
_clearPanel: function(map) {
// Unregister event handlers
var stop = L.DomEvent.stopPropagation;
L.DomEvent.off(this._panel, 'click', stop)
.off(this._panel, 'dblclick', stop)
.off(this._panel, 'mousedown', stop)
.off(this._panel, 'touchstart', stop)
.off(this._panel, 'mousewheel', stop)
.off(this._panel, 'MozMousePixelScroll', stop);
L.DomEvent.off(this._closeButton, 'click', this.hidePanel);
var mapContainer = map.getContainer();
mapContainer.removeChild(this._panel);
this._panel = null;
},
_setEventListeners : function() {
var that = this;
var input = this._input;
this._map.addEventListener('overlayadd', function() {
that.searchFeatures(input.value);
});
this._map.addEventListener('overlayremove', function() {
that.searchFeatures(input.value);
});
},
_clearEventListeners: function() {
this._map.removeEventListener('overlayadd');
this._map.removeEventListener('overlayremove');
},
isPanelVisible: function () {
return L.DomUtil.hasClass(this._panel, 'visible');
},
showPanel: function () {
if (! this.isPanelVisible()) {
L.DomUtil.addClass(this._panel, 'visible');
// Preserve map centre
this._map.panBy([this.getOffset() * 0.5, 0], {duration: 0.5});
this.fire('show');
this._input.select();
// Search again as visibility of features might have changed
this.searchFeatures(this._input.value);
}
},
hidePanel: function (e) {
if (this.isPanelVisible()) {
L.DomUtil.removeClass(this._panel, 'visible');
// Move back the map centre - only if we still hold this._map
// as this might already have been cleared up by removeFrom()
if (null !== this._map) {
this._map.panBy([this.getOffset() * -0.5, 0], {duration: 0.5});
};
this.fire('hide');
if(e) {
L.DomEvent.stopPropagation(e);
}
}
},
getOffset: function() {
if (this._panelOnLeftSide) {
return - this._panel.offsetWidth;
} else {
return this._panel.offsetWidth;
}
},
indexFeatures: function(data, keys) {
var jsonFeatures = data.features || data;
this._keys = keys;
var properties = jsonFeatures.map(function(feature) {
// Keep track of the original feature
feature.properties._feature = feature;
return feature.properties;
});
var options = {
keys: keys,
caseSensitive: this.options.caseSensitive,
threshold : this.options.threshold
};
this._fuseIndex = new Fuse(properties, options);
},
searchFeatures: function(string) {
var result = this._fuseIndex.search(string);
// Empty result list
var listItems = document.querySelectorAll(".result-item");
for (var i = 0 ; i < listItems.length ; i++) {
listItems[i].remove();
}
var resultList = document.querySelector('.result-list');
var num = 0;
var max = this.options.maxResultLength;
for (var i in result) {
var props = result[i];
var feature = props._feature;
var popup = this._getFeaturePopupIfVisible(feature);
if (undefined !== popup || this.options.showInvisibleFeatures) {
this.createResultItem(props, resultList, popup);
if (undefined !== max && ++num === max)
break;
}
}
},
refresh: function() {
// Reapply the search on the indexed features - useful if features have been filtered out
if (this.isPanelVisible()) {
this.searchFeatures(this._input.value);
}
},
_getFeaturePopupIfVisible: function(feature) {
var layer = feature.layer;
if (undefined !== layer && this._map.hasLayer(layer)) {
return layer.getPopup();
}
},
createResultItem: function(props, container, popup) {
var _this = this;
var feature = props._feature;
// Create a container and open the associated popup on click
var resultItem = L.DomUtil.create('p', 'result-item', container);
if (undefined !== popup) {
L.DomUtil.addClass(resultItem, 'clickable');
resultItem.onclick = function() {
if (window.matchMedia("(max-width:480px)").matches) {
_this.hidePanel();
feature.layer.openPopup();
} else {
_this._panAndPopup(feature, popup);
}
};
}
// Fill in the container with the user-supplied function if any,
// otherwise display the feature properties used for the search.
if (null !== this.options.showResultFct) {
this.options.showResultFct(feature, resultItem);
} else {
str = '<b>' + props[this._keys[0]] + '</b>';
for (var i = 1; i < this._keys.length; i++) {
str += '<br/>' + props[this._keys[i]];
}
resultItem.innerHTML = str;
};
return resultItem;
},
_panAndPopup : function(feature, popup) {
// Temporarily adapt the map padding so that the popup is not hidden by the search pane
if (this._panelOnLeftSide) {
var oldPadding = popup.options.autoPanPaddingTopLeft;
var newPadding = new L.Point(- this.getOffset(), 10);
popup.options.autoPanPaddingTopLeft = newPadding;
feature.layer.openPopup();
popup.options.autoPanPaddingTopLeft = oldPadding;
} else {
var oldPadding = popup.options.autoPanPaddingBottomRight;
var newPadding = new L.Point(this.getOffset(), 10);
popup.options.autoPanPaddingBottomRight = newPadding;
feature.layer.openPopup();
popup.options.autoPanPaddingBottomRight = oldPadding;
}
}
});
L.control.fuseSearch = function(options) {
return new L.Control.FuseSearch(options);
};