从下拉列表中搜索带有 Leaflet.Control.Search 的标记
Searching markers with Leaflet.Control.Search from drop down list
我使用 Leaflet.Control.Search 通过 GeoJSON 功能搜索标记,它工作正常。现在我必须输入第一个字母才能找到标记,但我想从包含所有标记的下拉列表中选择它们。有办法吗?
如果您想要一个包含所有标记的下拉列表,最好创建一个自定义控件,而不是尝试修改 Leaflet.Control.Search。使用包含所有标记的 select
元素创建控件有点复杂,但肯定比调整其他人已完成项目的代码简单得多。
首先创建一个空控件:
var selector = L.control({
position: 'topright'
});
要将内容放入控件中,您可以使用控件的 .onAdd
方法。使用 L.DomUtil.create
为控件创建一个容器 div,在此上下文中,它将自动分配 class leaflet-control
,允许 div 中的任何内容显示在地图和行为就像控件应该表现的那样。然后在 div 中创建一个 select
元素。如果你愿意,给它一个默认选项。最重要的是,给它一个id
,方便以后参考:
selector.onAdd = function(map) {
var div = L.DomUtil.create('div', 'mySelector');
div.innerHTML = '<select id="marker_select"><option value="init">(select item)</option></select>';
return div;
};
现在控件知道添加到地图时要做什么,继续添加它:
selector.addTo(map);
要将所有标记作为选项添加到 selector 中,您可以使用 .eachLayer
method,它遍历组中的所有标记并为每个标记调用一个函数。对于每一层,创建一个 option
元素并将其附加到 select
元素,使用上面指定的 id
。假设您已经创建了一个名为 markerLayer
的 GeoJSON 层,它有一个名为 STATION
的 属性,您希望将其用作选项文本,它看起来像这样:
markerLayer.eachLayer(function(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.STATION;
optionElement.value = layer._leaflet_id;
L.DomUtil.get("marker_select").appendChild(optionElement);
});
在这里,我们依赖于这样一个事实,即每个层在创建时都会分配一个唯一的内部 ID 号 _leaflet_id
。 We set each option's value
attribute to the corresponding layer's _leaflet_id
, so that when the option is selected, we have a way to access the marker.
最后,要让控件在您 select 选项之一时实际执行某些操作,请使用 select 或元素的 id
添加一些事件侦听器:
var marker_select = L.DomUtil.get("marker_select");
L.DomEvent.addListener(marker_select, 'click', function(e) {
L.DomEvent.stopPropagation(e);
});
L.DomEvent.addListener(marker_select, 'change', changeHandler);
使用 stopPropagation
方法的 click
侦听器是为了防止点击 select 或者在与地图窗格重叠时传播到地图,这可能会立即取消select 您要突出显示的图层。 change
监听器将 运行 一个处理函数,你可以设置它来做任何你想做的事情。在这里,我将其设置为在相应选项为 selected:
时打开标记的弹出窗口
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
markerLayer.getLayer(e.target.value).openPopup();
}
}
就是这样!这是一个 fiddle 所有这些一起工作的例子:
http://jsfiddle.net/nathansnider/ev3kojon/
编辑:
如果您使用的是 MarkerCluster 插件,您可以使用 .zoomToShowLayer
method:
修改更改处理函数以使用集群标记
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
var selected = markerLayer.getLayer(e.target.value);
markerClusterLayer.zoomToShowLayer(selected, function() {
selected.openPopup();
})
}
}
示例:
http://jsfiddle.net/nathansnider/oqk6u0sL/
(我还更新了原始代码和示例以使用 .getLayer
方法而不是 ._layers[e.target.value]
,因为这是一种更清晰的基于其 id 访问标记的方法)
有一种方法可以稍微修改 Leaflet-search 插件,以便在用户单击放大镜按钮时(即当用户展开搜索控件时)显示完整的标记列表。
好像搜索是针对 0 个键入的字母触发的。
不幸的是,在不修改插件代码的情况下使用选项 minLength: 0
不会在不输入的情况下触发搜索。
L.Control.Search.mergeOptions({
minLength: 0 // Show full list when no text is typed.
});
L.Control.Search.include({
_handleKeypress: function(e) {
switch (e.keyCode) {
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if (this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38: //Up
this._handleArrowSelect(-1);
break;
case 40: //Down
this._handleArrowSelect(1);
break;
case 37: //Left
case 39: //Right
case 16: //Shift
case 17: //Ctrl
//case 32://Space
break;
case 8: //backspace
case 46: //delete
this._autoTypeTmp = false;
if (this._collapsing) { // break only if collapsing.
break;
}
default: //All keys
this._doSearch(); // see below
}
},
// externalized actual search process so that we can trigger it after control expansion.
_doSearch: function() {
if (this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if (this._input.value.length >= this.options.minLength) {
var that = this;
clearTimeout(this.timerKeypress);
this.timerKeypress = setTimeout(function() {
that._fillRecordsCache();
}, this.options.delayType);
} else
this._hideTooltip();
},
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');
this._doSearch(); // Added to trigger a search when expanding the control.
return this;
},
collapse: function() {
this._hideTooltip();
this._collapsing = true; // added to prevent showing tooltip when collapsing
this.cancel();
this._collapsing = false; // added to prevent showing tooltip when collapsing
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._markerLoc.hide();
}
this._map.off('dragstart click', this.collapse, this);
}
this.fire('search_collapsed');
return this;
}
});
在实例化 L.Control.Search
之前将此代码包含在您的 JavaScript 中。
演示:http://jsfiddle.net/ve2huzxw/190/
此解决方案的一大缺点是标记列表是在地图容器内构建的。因此,如果它太大,它将在底部被裁剪,而真正的 select(下拉)输入将 "overflow" 超出容器,如 nathansnider 的解决方案。
我使用 Leaflet.Control.Search 通过 GeoJSON 功能搜索标记,它工作正常。现在我必须输入第一个字母才能找到标记,但我想从包含所有标记的下拉列表中选择它们。有办法吗?
如果您想要一个包含所有标记的下拉列表,最好创建一个自定义控件,而不是尝试修改 Leaflet.Control.Search。使用包含所有标记的 select
元素创建控件有点复杂,但肯定比调整其他人已完成项目的代码简单得多。
首先创建一个空控件:
var selector = L.control({
position: 'topright'
});
要将内容放入控件中,您可以使用控件的 .onAdd
方法。使用 L.DomUtil.create
为控件创建一个容器 div,在此上下文中,它将自动分配 class leaflet-control
,允许 div 中的任何内容显示在地图和行为就像控件应该表现的那样。然后在 div 中创建一个 select
元素。如果你愿意,给它一个默认选项。最重要的是,给它一个id
,方便以后参考:
selector.onAdd = function(map) {
var div = L.DomUtil.create('div', 'mySelector');
div.innerHTML = '<select id="marker_select"><option value="init">(select item)</option></select>';
return div;
};
现在控件知道添加到地图时要做什么,继续添加它:
selector.addTo(map);
要将所有标记作为选项添加到 selector 中,您可以使用 .eachLayer
method,它遍历组中的所有标记并为每个标记调用一个函数。对于每一层,创建一个 option
元素并将其附加到 select
元素,使用上面指定的 id
。假设您已经创建了一个名为 markerLayer
的 GeoJSON 层,它有一个名为 STATION
的 属性,您希望将其用作选项文本,它看起来像这样:
markerLayer.eachLayer(function(layer) {
var optionElement = document.createElement("option");
optionElement.innerHTML = layer.feature.properties.STATION;
optionElement.value = layer._leaflet_id;
L.DomUtil.get("marker_select").appendChild(optionElement);
});
在这里,我们依赖于这样一个事实,即每个层在创建时都会分配一个唯一的内部 ID 号 _leaflet_id
。 We set each option's value
attribute to the corresponding layer's _leaflet_id
, so that when the option is selected, we have a way to access the marker.
最后,要让控件在您 select 选项之一时实际执行某些操作,请使用 select 或元素的 id
添加一些事件侦听器:
var marker_select = L.DomUtil.get("marker_select");
L.DomEvent.addListener(marker_select, 'click', function(e) {
L.DomEvent.stopPropagation(e);
});
L.DomEvent.addListener(marker_select, 'change', changeHandler);
使用 stopPropagation
方法的 click
侦听器是为了防止点击 select 或者在与地图窗格重叠时传播到地图,这可能会立即取消select 您要突出显示的图层。 change
监听器将 运行 一个处理函数,你可以设置它来做任何你想做的事情。在这里,我将其设置为在相应选项为 selected:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
markerLayer.getLayer(e.target.value).openPopup();
}
}
就是这样!这是一个 fiddle 所有这些一起工作的例子:
http://jsfiddle.net/nathansnider/ev3kojon/
编辑:
如果您使用的是 MarkerCluster 插件,您可以使用 .zoomToShowLayer
method:
function changeHandler(e) {
if (e.target.value == "init") {
map.closePopup();
} else {
var selected = markerLayer.getLayer(e.target.value);
markerClusterLayer.zoomToShowLayer(selected, function() {
selected.openPopup();
})
}
}
示例:
http://jsfiddle.net/nathansnider/oqk6u0sL/
(我还更新了原始代码和示例以使用 .getLayer
方法而不是 ._layers[e.target.value]
,因为这是一种更清晰的基于其 id 访问标记的方法)
有一种方法可以稍微修改 Leaflet-search 插件,以便在用户单击放大镜按钮时(即当用户展开搜索控件时)显示完整的标记列表。
好像搜索是针对 0 个键入的字母触发的。
不幸的是,在不修改插件代码的情况下使用选项 minLength: 0
不会在不输入的情况下触发搜索。
L.Control.Search.mergeOptions({
minLength: 0 // Show full list when no text is typed.
});
L.Control.Search.include({
_handleKeypress: function(e) {
switch (e.keyCode) {
case 27: //Esc
this.collapse();
break;
case 13: //Enter
if (this._countertips == 1)
this._handleArrowSelect(1);
this._handleSubmit(); //do search
break;
case 38: //Up
this._handleArrowSelect(-1);
break;
case 40: //Down
this._handleArrowSelect(1);
break;
case 37: //Left
case 39: //Right
case 16: //Shift
case 17: //Ctrl
//case 32://Space
break;
case 8: //backspace
case 46: //delete
this._autoTypeTmp = false;
if (this._collapsing) { // break only if collapsing.
break;
}
default: //All keys
this._doSearch(); // see below
}
},
// externalized actual search process so that we can trigger it after control expansion.
_doSearch: function() {
if (this._input.value.length)
this._cancel.style.display = 'block';
else
this._cancel.style.display = 'none';
if (this._input.value.length >= this.options.minLength) {
var that = this;
clearTimeout(this.timerKeypress);
this.timerKeypress = setTimeout(function() {
that._fillRecordsCache();
}, this.options.delayType);
} else
this._hideTooltip();
},
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');
this._doSearch(); // Added to trigger a search when expanding the control.
return this;
},
collapse: function() {
this._hideTooltip();
this._collapsing = true; // added to prevent showing tooltip when collapsing
this.cancel();
this._collapsing = false; // added to prevent showing tooltip when collapsing
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._markerLoc.hide();
}
this._map.off('dragstart click', this.collapse, this);
}
this.fire('search_collapsed');
return this;
}
});
在实例化 L.Control.Search
之前将此代码包含在您的 JavaScript 中。
演示:http://jsfiddle.net/ve2huzxw/190/
此解决方案的一大缺点是标记列表是在地图容器内构建的。因此,如果它太大,它将在底部被裁剪,而真正的 select(下拉)输入将 "overflow" 超出容器,如 nathansnider 的解决方案。