I have written a basic function to allow me to display a popup from a link outside the map. The functionality to open the popup is working fine, but I can't then close it.
Demo link: http://www.catchingtherain.com/bikestats/stations.php - click on links in left-hand tabbed panels.
Here's a bit more detail ...
A typical map has about 300 features on a vector layer 'stations' loaded from kml. These are activated onload using
select = new OpenLayers.Control.SelectFeature(stations);
stations.events.on({
"featureselected": onFeatureSelect,
"featureunselected": onFeatureUnselect
});
map.addLayer(stations);
map.addControl(select);
select.activate();
which works fine - I can open and close popups.
With my off-map links I am calling onclick="showMyPopup([x]) with [x] being an ID attribute loaded in from the kml. The showMyPopup function is
function showMyPopup(myID){
for(var a = 0; a < stations.features.length; a++){ //loop through all the features
var feature = stations.features[a];
if (feature.attributes.ID.value == myID) { //until it finds the one with the matching ID attribute
var content = "<h4>" + feature.attributes.name + "</h4>" + feature.attributes.description;
popup = new OpenLayers.Popup.FramedCloud("chicken",
feature.geometry.getBounds().getCenterLonLat(),
new OpenLayers.Size(200,200),
content,
null, true, onPopupClose);
feature.popup = popup;
map.addPopup(popup);
}
}
}
This opens the correct popup from the stations layer as expected, and I can see the popup using the DOM inspector on the stations layer just as it would appear if loaded by clicking on the map feature, but there's then seemingly no way of closing it. The original features on the stations layer are working fine though (opening and closing).
Any help would be much appreciated (maybe there's a simpler way of tackling this?)
Thanks, James
PS and just in case, here's the onFeatureUnselect function ...
function onFeatureUnselect(event) {
var feature = event.feature;
if(feature.popup) {
map.removePopup(feature.popup);
feature.popup.destroy();
delete feature.popup;
}
}
Your on onPopupClose() function is:
function onPopupClose(evt) {
select.unselectAll();
}
When you select feature from map and click on popup's Close icon, then feature will be unselected, but popup is not closed yet. Then, onFeatureUnselect event is triggered, and popup is actually closed.
When you create popup by showMyPopup() function, you are not selecting it. onPopupClose() is called, but it doesn't close popup. onFeatureUnselect is not triggered.
I suggest to select feature in showMyPopup() function. featureselected event will be fired and popup is created by onFeatureSelect(), and user can close popup both with popup's Close icon and unselecting feature on map.
But alas, there's a possible bug (or unexpected behaviour) in OL, when you select feature with code and try to unselect it with clickout. It's described here: http://lists.osgeo.org/pipermail/openlayers-users/2012-September/026349.html One possible fix is to set SelectControl.handlers.feature.lastFeature manually.
function showMyPopup(myID){
for(var a = 0; a < stations.features.length; a++){ //loop through all the features
var feature = stations.features[a];
if (feature.attributes.ID.value == myID) { //until it finds the one with the matching ID attribute
// select is your SelectFeature control
select.select(feature);
// Fix for unselect bug
select.handlers.feature.lastFeature = feature;
break;
}
}
}
I take a look in the OpenLayers sources and there is in Popup.js something like that ...
...
var closePopup = callback || function(e) {
this.hide();
OpenLayers.Event.stop(e);
};
OpenLayers.Event.observe(this.closeDiv, "touchend",
OpenLayers.Function.bindAsEventListener(closePopup, this));
OpenLayers.Event.observe(this.closeDiv, "click",
OpenLayers.Function.bindAsEventListener(closePopup, this));
...
It seems to me if you add your own closePopup function you need to call the hide function in your code.
Related
I have a really annoying issue with javascript in my Django project. Currently building a webbapp which reads data from sensors placed in manholes for water-temperature measurements. We display these sensors as markers on a Leafletmap with the pipe-system between each manhole.
I'm currently storing the sensor-id as a hidden variable in each manhole and then grabbing these to build a D3 graph displaying the temperature data for the specific manhole that has been clicked.
onEachFeature: (feature, layer) => {
for (let i = 0; i < place.length; i++) {
if (place[i].fields.pnamn === feature.properties.pnamn) {
sensorid = place[i].fields.eui;
}
}
var popupText = "<strong>" + feature.properties.pnamn + "<p id='popupText' style='display:none'>" + sensorid + "</p>" + "</strong>";
layer.bindPopup(popupText);
},
[......] }).on('click', onClick).on('popupclose', startZoomer).addTo(map);
The id in question is the sensorid in the p-element. It works as it should, except for the extremely annoying fact that you can just click on a new manhole to update the graph without clicking twice on the new one or by clicking anywhere on the map.
I capture the sensorid in the function below and this is where I believe the problem is hiding. Can't really wrap my head around why this is happening and would appreciate any help at this point in time!
function onClick() {
let id = document.getElementById("popupText").innerText;
urlen = urlen.replace(/([A-Z])\w+/, id);
console.log(id);
console.log(urlen);
var x = document.getElementById("chart-area");
if (x.style.display === "none") {
x.style.display = "block";
} else {
x.style.display = "block";
}
map.scrollWheelZoom.disable();
update();
}
Where the building of the new id for updating the graph is happening is the first 4 rows under the function initialization. The rest is for locking the map for mousewheel scroll when a popup is open so that user can scroll between the graph and map as they are stacked on top of eachother.
It's as I said extremely annoying for me and unacceptable when the system's put to use to have to click twice, and if you don't know this happens it can skew your view as it does update if you just click between manholes but you get the manhole you clicked before the current.
Please help me.
When the click event happens, most probably your new popup is not opened yet. This would explain why you find no element with matching id, or get the previous one.
You might have more luck using the "popupopen" event instead.
But in the first place, relying on scraping your sensor id from the popup text is a strange design, when you have control over the page code.
A more appropriate design would directly associate the sensor id with the Marker or its associated Feature, so that you can retrieve it easily on a Marker click event, instead of having to go through DOM querying.
For example in your onEachFeature:
feature.properties.sensorid = sensorid
Then in your onClick listener:
function onClick(event) {
// the clicked layer is event.layer if the listener is on a Feature Group,
// but is event.target if the listener is directly on that layer.
const sensorid = event.layer.feature.properties.sensorid;
}
I'm using jQuery Mobile to create a popup near an info image button (see picture below). The popup I'm creating has data-dismissable set as false. In red it has been highlighted the container created by jQuery Mobile.
If the user taps once on i icon, it works perfectly. The popup opens and popupafteropen event is called. To dismiss it, the user can tap (click) on the i or outside the red area (thanks to data-dismissable value). On the contrary, if the users performs a double tap (here I mean that the delay bewteen two taps is very short), the popup opens, popupafteropen is called but then also close is called (due to the second tap and data-dismissable value). The overall result is that the popup is not shown.
Are there any solutions to adopt? My goal is to prevent the second tap in order to display the popup correctly.
Thanks.
Kazekage Gaara has a good idea so look first at it.
Other one would require you to bind a doubletap event to popup opening icon and prevent default action, unfortunately jQuery Mobile don't have support for doubletap so you can use this plugin:
(function($) {
$.fn.doubleTap = function(doubleTapCallback) {
return this.each(function(){
var elm = this;
var lastTap = 0;
$(elm).bind('vmousedown', function (e) {
var now = (new Date()).valueOf();
var diff = (now - lastTap);
lastTap = now ;
if (diff < 250) {
if($.isFunction( doubleTapCallback ))
{
doubleTapCallback.call(elm);
}
}
});
});
}
})(jQuery);
and bind it like this:
$(".icon").doubleTap(function(event){
event.preventDefault();
});
There used to be much easier solution for this, jQUery Mobile used to have mobileinit configuration parameter that allowed you to set how long tap event can last.
Or you can monitor interval between taps and prevent allow actions, like this:
var lastTapTime;
function isJqmGhostClick(event) {
var currTapTime = new Date().getTime();
if(lastTapTime == null || currTapTime > (lastTapTime + 800)) {
lastTapTime = currTapTime;
return false;
}
else {
return true;
}
}
You can handle the event and ignore it if the popup is already open. Something like :
if ($.mobile.activePage.find("#popupID").is(":visible") {
// Do something here if the popup is open
}
I am using leaflet.js to display markers on a OSM map.
The problem is, that the first time a marker is clicked, the popup opens normally, but on a second click on the same marker the popup does not open anymore.
PS: Anywhere else in the code I close popups (with the closePopup() function). In the block below i even commented out the explicit closing of other popups once a marker is clicked.
PPS: My application runs on Ruby on Rails (ruby-1.9.3, Rails 3.2.16), and uses leaflet-rails (0.7.2)
bindListeners = function(marker){
marker.on('click', function(evt) {
//resize all markers' icons to default size
for (i=0;i<markersOfTheMap.length;i++) {
resizeMarkerIcon(markersOfTheMap[i], false);
}
//map.closePopup();
var infoBoxContent = buildInfoboxHtml(marker);
marker.bindPopup(infoBoxContent, {className: 'click-popup'}, {closeOnClick: false});
resizeMarkerIcon(marker, true);
marker.openPopup();
var popup = marker.getPopup(); // returns marker._popup
popup._isOpen = true;
console.log("is popup open? " +popup._isOpen); // true
popupsTestArray.push(popup);
console.log(popupsTestArray); // popup_isOpen is false...
});
I also faced the same issue. I am pasting here my piece of code. Hope It will help you out to sort out your problem.
marker.on('click', function (e) {
if (e.target._popup == undefined) { // same as e.target.getPopup()
$.getJSON(url, { entityObject: e.target.options.alt }, function (infoBoxContent ) { //e.target.options.alt contains entity Id from which we will get Infobox window content.
e.target.bindPopup(infoBoxContent).openPopup();
});
}
else {
marker.openPopup();
}
});
In If condition we fetch data from server side and bind it to marker popup.
And in Else condition there after same marker is clicked again we will show content from client side.
This code works fine me. If you have any queries then we can discuss here.
I'm using the YUI TabView widget for adding and removing tabs like described in yuilibrary-tabview-add-remove.
I've noticed a "bug" or maybe just a missing functionality: When you close all tabs and then add a new tab, the "add tab" button will get stuck on the left side of the tab bar, and all new tabs will be sorted on the right side. If you don't close all tabs, the button will always stay on the right side no matter what.
Now, I've added a workaround: When adding a new tab, the no-tabs state will be detected and the DOM li-item will be sorted with the jQuery after() method. Finally, the newly added tab will be selected:
onAddClick : function(e) {
e.stopPropagation();
var tabview = this.get('host'), input = this.getTabInput();
tabview.add(input, input.index);
// When previously no tabs present, move 'add button' to end after adding a new tab
if ( tabview.size() == 1) {
var addTabButton = $('#addTabButton');
addTabButton.next().after(addTabButton);
tabview.selectChild(0);
};
}
However, I'm not happy with this solution. Might there be a more elegant way to solve this issue?
Your solution is definitely valid. I'd just write it using YUI because loading YUI and jQuery is really expensive in kweight and maintenance cost (you and your coworkers need to master two libraries).
One clean option is to create a node in the initializer and keep a reference to it so that you can move it around later:
initializer: function (config) {
var tabview = this.get('host');
// create the node before rendering and keep a reference to it
this._addNode = Y.Node.create(this.ADD_TEMPLATE);
tabview.after('render', this.afterRender, this);
tabview.get('contentBox')
.delegate('click', this.onAddClick, '.yui3-tab-add', this);
},
_appendAddNode: function () {
var tabview = this.get('host');
tabview.get('contentBox').one('> ul').append(this._addNode);
},
afterRender: function (e) {
this._appendAddNode();
},
onAddClick: function (e) {
e.stopPropagation();
var tabview = this.get('host'), input = this.getTabInput();
tabview.add(input, input.index);
// When previously no tabs present, move 'add button' to end after adding a new tab
if ( tabview.size() == 1) {
// _addNode will already be present, but by using append() it'll be moved to the
// last place in the list
this._appendAddNode();
};
}
Here's a working version: http://jsbin.com/iLiM/2/
I'm re-parsing the KML that's already been loaded onto the map similar to the example here:
http://openlayers.org/dev/examples/sundials.html and turning it into a clickable list that will center the map on the point clicked, and display the popup window for it.
This was really easy to do in Google Maps, but I can't find any similar Openlayers examples. Is there any easier way to do this? Something built-in that I'm missing?
HTML:
<ul id="locationTable">
</ul>
JS:
htmlRows = "";
for(var feat in features) {
// Build details table
featId = features[feat].id; // determine the feature ID
title = jQuery(f).filter('[name=TITLE]').text();
htmlRow = "<li>"+title+"</li>";
htmlRows = htmlRows + htmlRow;
}
jQuery('#locationTable').append(htmlRows);
And then for the selectFeature function:
function selectFeature(fid) {
for(var i = 0; i<kml.features.length;++i) {
if (kml.features[i].id == fid)
{
selected = new OpenLayers.Control.SelectFeature(kml.features[i]);
selected.clickFeature(); // make call to simulate Click event of feature
break;
}
}
}
I think you should remove the "selected.clickFeature" call, and instead create an event listener for the "featureselected" event in your feature layer:
OpenLayers.Layer.Vector
If you display the popup in that event, you will only have to find it and select it with your existing code, and remove the line
selected.clickFeature();
Sidenote: Can your feature server deliver data in other formats? WFS for instance? Parsing KML data shouldn't be needed.