343 lines
11 KiB
JavaScript
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 = '×';
|
|
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);
|
|
};
|