I have been able to integrate markers to the mapbox we are using, but still wonder if we can get a click on them. If so how?
Following is my code:
<style>
/*
* Unlike other icons, you can style `L.divIcon` with CSS.
* These styles make each marker a circle with a border and centered text.
*/
.count-icon1 {
background:url(images/redpin.png);
color:#000;
font-weight:600;
text-align:center;
padding:19px 0 0 0px; font-size:180%;
}
.count-icon2 {
background:url(images/greenpin.png);
color:#000;
font-weight:600;
text-align:center;
padding:19px 0 0 0px; font-size:180%;
}
</style>
js code:
var defaultLat = 39.12367;
var defaultLon = -76.81229;
if($scope.currentLocDetails != null){
if($scope.currentLocDetails.Lat != null && $scope.currentLocDetails.Lon != null){
defaultLat = $scope.currentLocDetails.Lat;
defaultLon = $scope.currentLocDetails.Lon;
}
}
var x = 0;
if(map != null)
map.remove();
map = L.mapbox.map('map_view', 'your key here').setView([defaultLat, defaultLon], 9);
for (var i = 0; i < responseData.JobLocation.length; i++) {
var eachObj = responseData.JobLocation[i];
if(eachObj.Lat != null && eachObj.Lon != null){
x++;
// Use a little math to position markers.
// Replace this with your own code.
L.marker([
eachObj.Lat,
eachObj.Lon
], {
icon: L.divIcon({
// Specify a class name we can refer to in CSS.
className: ((currentSelectedIndex + 1) == i + 1)?'count-icon1':'count-icon2',
// Define what HTML goes in each marker.
html: i + 1,
// Set a markers width and height.
iconSize: [65, 94]
})
}).addTo(map);
}
}
I tried doing a bit R & D, but get to no where:
We need to use featureLayer, but dunno how.
For the click feature we need to follow this code, but how?
// Listen for individual marker clicks.
myLayer.on('click',function(e) {
// Force the popup closed.
e.layer.closePopup();
var feature = e.layer.feature;
var content = '<div><strong>' + feature.properties.title + '</strong>' +
'<p>' + feature.properties.description + '</p></div>';
info.innerHTML = content;
});
Any help with this is really appreciated.
Thanks
I believe there are various ways of doing this with Mapbox, unfortunately I don't have access to my project where I use it right now so I'm just going off the Mapbox documentation.
Following your example this looks the simplest - if you have added marker, for example:
var marker = L.marker([43.6475, -79.3838], {
icon: L.mapbox.marker.icon({
'marker-color': '#9c89cc'
})
})
.bindPopup('<p>Your html code here</p>')
.addTo(map);
You can pass whatever HTML you want in the bindPopup argument.
https://www.mapbox.com/mapbox.js/example/v1.0.0/clicks-in-popups/
Then it should be be pretty simple via 'addEventListener('click', function)' on that marker variable.
Or alternatively -
myLayer.on('click', function(e) {
resetColors();
e.layer.feature.properties['old-color'] = e.layer.feature.properties['marker-color'];
e.layer.feature.properties['marker-color'] = '#ff8888';
myLayer.setGeoJSON(geoJson);
});
map.on('click', resetColors);
Effectively add an event listener on the map variable - and then listen to what you've clicked on via the event argument passed to the event listener.
This may be useful: https://www.mapbox.com/mapbox.js/example/v1.0.0/change-marker-color-click/
Good luck!
Related
I applied a popup.update() code snippet provided by #ghybs that works very well to adjust the popup to fit within the frame of the map, as you can see the code here:
document.querySelector(".leaflet-popup-pane").addEventListener("load", function (event) {
var tagName = event.target.tagName,
popup = map._popup;
console.log("got load event from " + tagName);
if (tagName === "IMG" && popup) {
popup.update();
}
}, true);
The problem is that I embedded a url in the thumbnail for each popup. When I go to click on the thumbnail, the cursor indicates that a 'click' should be possible, but it does not do anything. When I right click the thumbnail, I can open the url in a new tab. So the url is there, just not working the way I want it to. A friend and I looked at the code and we determined that the javascript popup is constantly being recreated. He suggested it might be the update call, but wasn't sure. If there is a way to stop the javascript from constantly recreating the popup, then that might solve the issue.
He also noted that when he stops javascript from running, the url is only clickable on the bottom half of the thumbnail (specifically 14px high) when it is supposed to occupy the entire thumbnail (which is typically 250px).
When I've checked for console.log(popup) right after the update, it gets stuck in an infinite loop. I'm guessing this is the heart of the problem. Is there a way to stop the update after it updates the popup size? I'm hoping this would free the embedded URL so as to be clickable, but I would also like the height of the link to match the entire thumbnail.
For reference, I am extracting the points from a geojson file and applying the same method to each point, like so:
var clusters = L.markerClusterGroup({maxClusterRadius:75});
var getjson = $.getJSON("map-v2.geojson",function(data){
var bev = L.geoJson(data,{
pointToLayer: function(feature,latlng){
var marker = L.marker(latlng, { tags: feature.properties.Genres.concat(feature.properties.Creator)});
marker.bindPopup('<p align=center>' + '<strong>Title: </strong>' + feature.properties.Title + '<br/><img src="' + feature.properties.Thumbnail_URL + '"/><br/>' + '<strong>Date: </strong>' + feature.properties.Date + '<br/>' + '<strong>Creator: </strong>' + feature.properties.Creator + feature.properties.Genre, {minWidth : 250});
return marker;
}
});
clusters.addLayer(bev);
map.addLayer(clusters);
});
Welcome to SO!
Hum indeed it looks like the given workaround does create an infinite loop when you specify the popup content as HTML string containing an <img>. What happens is that when an image completes loading, the popup.update() resets the Popup content using the HTML string, hence re-creates the <img> element, which emits a new "load" event, even if now it comes from browser cache. Then the listener executes popup.update() again, etc.
Demo (open your Web Console to see the inifinite loop logging "got load event from IMG"):
var map = L.map('map').setView([48.86, 2.35], 11);
// Modify the cache busting value to force browser fetching from network.
var imgSrc = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=1';
var popupContent =
'<a href="https://a.tile.openstreetmap.org/0/0/0.png" target="_blank">' +
'<img src="' + imgSrc + '"/></a>';
L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent);
document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
var tagName = event.target.tagName,
popup = map._popup;
console.log("got load event from " + tagName);
if (tagName === "IMG" && popup) {
popup.update();
}
}, true);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
html,
body,
#map {
height: 100%;
margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>
<div id="map"></div>
In your very case, if you are sure there will be only 1 Popup open at any given time, and that it includes only a single <img>, you could simply set a flag at first "load" event on that Popup, in order to prevent the inifinite looping:
var map = L.map('map').setView([48.86, 2.35], 11);
// Modify the cache busting value to force browser fetching from network.
var imgSrc = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=2';
var popupContent =
'<a href="https://a.tile.openstreetmap.org/0/0/0.png" target="_blank">' +
'<img src="' + imgSrc + '"/></a>';
L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent);
document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
var tagName = event.target.tagName,
popup = map._popup;
console.log("got load event from " + tagName);
// Also check if flag is already set.
if (tagName === "IMG" && popup && !popup._updated) {
popup._updated = true; // Set flag to prevent looping.
popup.update();
}
}, true);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
html,
body,
#map {
height: 100%;
margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>
<div id="map"></div>
(note that SO code snippets seem to prevent <a href> links to open, so here is a Plunk to check that the link does open normally: https://next.plnkr.co/edit/ore09Yxmm6DVmJGc)
Now to generalize the solution for the case where an arbitrary number of images are contained in the HTML string, and when multiple Popups can be open simultaneously, we could imagine:
On "popupopen" and each image "load" events, check if all Popup images have a non zero naturalWidth, update otherwise.
Store the reference to the Popup on each image, so that we do not have to resort to read the map._popup (which reference the last open Popup only).
var map = L.map('map', {
closePopupOnClick: false
}).setView([48.86, 2.35], 11);
// Modify the cache busting value to force browser fetching from network.
var imgSrc1 = 'https://a.tile.openstreetmap.org/0/0/0.png?bust=3';
var imgSrc2 = 'https://a.tile.openstreetmap.org/11/1037/704.png?bust=3';
var popupContent =
'<a href="' + imgSrc1 + '" target="_blank">' +
'<img src="' + imgSrc1 + '"/></a>' +
'<a href="' + imgSrc2 + '" target="_blank">' +
'<img src="' + imgSrc2 + '"/></a>';
L.marker([48.86, 2.35]).addTo(map).bindPopup(popupContent, {
autoClose: false
}).on('click', function() {
// Open another Popup after this one.
m2.openPopup();
});
var m2 = L.marker([48.86, 2.32]).bindPopup('Second Popup', {
autoClose: false
}).addTo(map);
// Prepare the Popup when it opens.
map.on('popupopen', function(event) {
var popup = event.popup;
popup._imgAllSized = popupImgAllSized(popup);
});
document.querySelector(".leaflet-popup-pane").addEventListener("load", function(event) {
var target = event.target,
tagName = target.tagName,
popup = target._popup;
console.log("got load event from " + tagName);
// Also check the Popup "_imgAllSized" flag.
if (tagName === "IMG" && popup && !popup._imgAllSized) {
console.log('updated');
// Update the flag, in case all images have finished loading.
popup.update();
popup._imgAllSized = popupImgAllSized(popup);
}
}, true);
function popupImgAllSized(popup) {
// Get the HTMLElement holding the Popup content.
var container = popup._contentNode;
var imgs = container.querySelectorAll('img');
var imgAllSized = true;
for (var i = 0; i < imgs.length; i += 1) {
// Store reference to popup in <img>
imgs[i]._popup = popup;
// Check if the image has unknown size.
if (!imgs[i].naturalWidth) {
imgAllSized = false;
}
}
console.log(imgAllSized);
return imgAllSized;
}
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
html,
body,
#map {
height: 100%;
margin: 0;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin="" />
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet-src.js" integrity="sha512-GosS1/T5Q7ZMS2cvsPm9klzqijS+dUz8zgSLnyP1iMRu6q360mvgZ4d1DmMRP2TDEEyCT6C16aB7Vj1yhGT1LA==" crossorigin=""></script>
<div id="map"></div>
Then we could even further improve this solution by trying to update as soon as the images have their naturalWidth, instead of waiting for their "load" event, so that even while the browser is still fetching them, the Popup size and position is updated.
I am new to here maps and need to show a div on marker hover. I have been able to put markers with icons but now need to show a div with some extra information. Does HERE maps API provide this functionality?
Any doc URL or piece of code will be appreciated.
NOTE: I am using HERE maps JS API for web.
You can create a tooltip effect by adding various event listeners to the map to check if the mouse pointer is over an object.
(function (ctx) {
// ensure CSS is injected
var tooltipStyleNode = ctx.createElement('style'),
css = '#nm_tooltip{' +
' color:white;' +
' background:black;' +
' border: 1px solid grey;' +
' padding-left: 1em; ' +
' padding-right: 1em; ' +
' display: none; ' +
' min-width: 120px; ' +
'}';
tooltipStyleNode.type = 'text/css';
if (tooltipStyleNode.styleSheet) { // IE
tooltipStyleNode.styleSheet.cssText = css;
} else {
tooltipStyleNode.appendChild(ctx.createTextNode(css));
}
if (ctx.body) {
ctx.body.appendChild(tooltipStyleNode);
} else if (ctx.addEventListener) {
ctx.addEventListener('DOMContentLoaded', function () {
ctx.body.appendChild(tooltipStyleNode);
}, false);
} else {
ctx.attachEvent('DOMContentLoaded', function () {
ctx.body.appendChild(tooltipStyleNode);
});
}
})(document);
Object.defineProperty(Tooltip.prototype, 'visible', {
get: function() {
return this._visible;
},
set: function(visible) {
this._visible = visible;
this.tooltip.style.display = visible ? 'block' : 'none';
}
});
function Tooltip(map) {
var that = this;
that.map = map;
that.tooltip = document.createElement('div');
that.tooltip.id = 'nm_tooltip';
that.tooltip.style.position = 'absolute';
obj = null,
showTooltip = function () {
var point = that.map.geoToScreen(obj.getPosition()),
left = point.x - (that.tooltip.offsetWidth / 2),
top = point.y + 1; // Slight offset to avoid flicker.
that.tooltip.style.left = left + 'px';
that.tooltip.style.top = top + 'px';
that.visible = true;
that.tooltip.innerHTML = obj.title;
};
map.getElement().appendChild(that.tooltip);
map.addEventListener('pointermove', function (evt) {
obj = that.map.getObjectAt(evt.currentPointer.viewportX,
evt.currentPointer.viewportY);
if(obj && obj.title){
showTooltip();
} else {
that.visible = false;
}
});
map.addEventListener('tap', function (evt){
that.tooltip.visible = false;
});
map.addEventListener('drag', function (evt){
if (that.visible) {
showTooltip();
}
});
};
This is initialised by passing the map object as shown:
function addTooltipControlToMap(map) {
tooltip = new Tooltip(map);
}
The code as written is looking for a .title attribute to be added to the map objects - this could be updated to use .getData() if preferred. Tooltips can be initialised as shown below, taking either text or html:
function addMarkersWithTooltips(map) {
// Simple Marker with tooltip
var brandenburgerTorMarker = new H.map.Marker(
{lat:52.516237, lng: 13.35}),
fernsehturmMarker = new H.map.Marker(
{lat:52.520816, lng:13.409417});
brandenburgerTorMarker.title = 'Brandenburger Tor';
// Marker with HTML Tooltip
fernsehturmMarker.title ='<div>' +
'<h2>Tooltip with HTML content<\/h2>' +
'<img width=\'120\' height=90 src=' +
'\'http://upload.wikimedia.org/wikipedia/commons/' +
'8/84/Berlin-fernsehturm.JPG\' ' +
'alt=\'\'/><br/><b>Fernsehturm, Berlin<\/b>' +
'<\/div>';
// Add the markers onto the map
map.addObjects([brandenburgerTorMarker, fernsehturmMarker]);
}
I have been able to find proper mouse over events for HERE map's markers which are pointerenter and pointerleave and sample code to use these events is:
// After Initializing map with your own credentials.
var map = new H.Map(document.getElementById('map'),
defaultLayers.normal.map,{
center: {lat: LAT_VAL, lng: LNG_VAL},
zoom: 12
});
var domMarker = new H.map.DomMarker(coords, {
icon: domIcon
});
var bubble;
domMarker.addEventListener('pointerenter', function(evt) {
bubble = new H.ui.InfoBubble({lat:"SOME_VALUE",lng:"SOME_VALUE"}, {
content: "Your content come here"
});
ui.addBubble(bubble);
}, false);
domMarker.addEventListener('pointerleave', function(evt) {
bubble.close();
}, false);
map.addObject(domMarker);
Depending on the api version you are using, you may find what you are looking for inside the documentation pdf (or at least start from there).
Supposing you need do make some HTML styled marker, you may need:
DomMarker (instead of a Marker, because it allows you to use ->2)
DomIcon (which can embed html)
An example can be found here https://developer.here.com/apiexplorer-v2-sample-data/template-web-default/examples/map-with-dom-marker/index.html
Anyway, if you need to show informations about the marker, I would suggest to use InfoBubbles, which have been developed for this purpose.
From the 3.0.5 docs:
// Create an info bubble object at a specific geographic location:
ui = H.ui.UI.createDefault(self.map, defaultLayers);
var bubble = new H.ui.InfoBubble({ lng: 13.4, lat: 52.51 }, {
content: '<b>Hello World!</b>'
});
// Add info bubble to the UI:
ui.addBubble(bubble);
To show them, you should attach an event to the marker tap event:
marker.addEventListener('tap', function (evt) {
//create and add the bubble
}
In any case, you can find the documentation of your api version here: https://developer.here.com/documentation/versions
You do not have "hover" listener for marker,
but you can show infoBubble on click
http://heremaps.github.io/examples/explorer.html#infobubble-on-marker-click
If this doesn't work for you, you will have to use jquery and to bind "hover" on HTML marker element. (This is not very easy task)
long-time lurker, first-time poster here, so be gentle...
I'm building a map that uses data parsed in from a MYSQL db via PHP to set the colour of polygons defined by a geoJson file (it uses this example on the Google dev site as a template). The problem I'm having is that the data layer won't automatically initialize when the page loads.
The full javascript/HTML is posted below, but the section of code that was used in the example I'm following to initialize the data layer is:
google.maps.event.addListenerOnce(map.data, 'addfeature', function() {
google.maps.event.trigger(document.getElementById('price_select'),
'change');
});
This gives me the error "Uncaught TypeError: Cannot read property 'setProperty' of undefined". If I comment out the listener, the data layer will load fine, but only after I've manually selected a new input from the drop down (id='price_select').
The geoJson file I'm loading is relatively large (~14mb) so I think what is happening is that the listener is triggering before the whole file is loaded ('addfeature' waits for just the first feature to be added, but I have >2000) and thus the districts parsed in by the PHP don't yet have a corresponding feature ID, which is set by the idPropertyName: 'Name' parameter in the loadGeoJson call. I don't know of a way to set the listener to trigger only once the whole GeoJson file has loaded though. Alternatively, I could be completely wrong about this being the cause of the error.
Anyway, full code below - I know it has some quirks (e.g. I pass the loadData function an argument, but don't use it), but these are mainly because I plan to add more functionality later on. Thanks for bearing with me!
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
var map;
var priceMin = 1000000;
var priceMax = 0;
google.maps.event.addDomListener(window, 'load', function(){
map = new google.maps.Map(document.getElementById('map-canvas'), {
center: new google.maps.LatLng(53.587425,-1.663539),
zoom: 7,
});
// add styles
map.data.setStyle(styleFeature);
map.data.addListener('mouseover', mouseInToRegion);
map.data.addListener('mouseout', mouseOutOfRegion);
// initiate drop down functionality
var selectBox = document.getElementById('price_select');
google.maps.event.addDomListener(selectBox, 'change', function() {
clearData();
loadData(selectBox.options[selectBox.selectedIndex].value);
});
// load polygons
loadMapShapes();
});
function loadMapShapes(){
map.data.loadGeoJson('http://localhost/OS_raw.json',
{ idPropertyName: 'Name' });
//This is the listener that is supposed to initiate the data layer
google.maps.event.addListenerOnce(map.data, 'addfeature', function() {
google.maps.event.trigger(document.getElementById('price_select'),
'change');
});
// End listener
}
function loadData(variable){
var phpdistricts = (<?php echo $phpdistricts; ?>);
var phpprices = (<?php echo $phpprices; ?>);
for(var i=0; i<phpdistricts.length; i++){
var district = phpdistricts[i];
var price = parseInt(phpprices[i]);
// keep track of min and max values
if (price < priceMin) {
priceMin = price;
}
if (price > priceMax) {
priceMax = price;
}
console.log(map.data.getFeatureById(district));
//This is where the error triggers - feature is undefined according to console log above
map.data
.getFeatureById(district)
.setProperty('price', price);}
//end of problematic section
// update and display the legend
document.getElementById('census-min').textContent =
priceMin.toLocaleString();
document.getElementById('census-max').textContent =
priceMax.toLocaleString();
}
function clearData() {
priceMin = 1000000;
priceMax = 0;
map.data.forEach(function(row) {
row.setProperty('price', undefined);
});
document.getElementById('data-box').style.display = 'none';
document.getElementById('data-caret').style.display = 'none';
}
function styleFeature(feature) {
var low = [151, 83, 34]; // color of smallest datum
var high = [5, 69, 54]; // color of largest datum
// delta represents where the value sits between the min and max
var delta = (feature.getProperty('price') - priceMin) /
(priceMax - priceMin);
var color = [];
for (var i = 0; i < 3; i++) {
// calculate an integer color based on the delta
color[i] = (high[i] - low[i]) * delta + low[i];
}
// filters out areas without data
var showRow = true;
if (feature.getProperty('price') == null ||
isNaN(feature.getProperty('price'))) {
showRow = false;
}
var outlineWeight = 0.5, zIndex = 1;
if (feature.getProperty('state') === 'hover') {
outlineWeight = zIndex = 2;
}
return {
strokeWeight: outlineWeight,
strokeColor: '#fff',
zIndex: zIndex,
fillColor: 'hsl(' + color[0] + ',' + color[1] + '%,' + color[2] + '%)',
fillOpacity: 0.75,
visible: showRow
};
}
function mouseInToRegion(e) {
// set the hover state so the setStyle function can change the border
e.feature.setProperty('state', 'hover');
var percent = (e.feature.getProperty('price') - priceMin) /
(priceMax - priceMin) * 100;
// update the label
document.getElementById('data-label').textContent =
e.feature.getProperty('Name');
document.getElementById('data-value').textContent =
e.feature.getProperty('price');
document.getElementById('data-box').style.display = 'block';
document.getElementById('data-caret').style.display = 'block';
document.getElementById('data-caret').style.paddingLeft = percent + '%';
}
function mouseOutOfRegion(e) {
// reset the hover state, returning the border to normal
e.feature.setProperty('state', 'normal');
}
</script>
</head>
<body>
<div id="controls" class="nicebox">
<div>
<select id="price_select">
<option value="price">Jun '14</option>
<option value="price">Jun '14</option>
</select>
</div>
<div id="legend">
<div id="census-min">min</div>
<div class="color-key">
<span id="data-caret">◆</span>
</div>
<div id="census-max">max</div>
</div>
</div>
<div id="data-box" class="nicebox">
<label id="data-label" for="data-value">Area: </label>
<span id="data-value"></span>
</div>
<div id="map-canvas"></div>
</body>
</html>
I don't fully get what you're asking here, but have you tried styling using the data layer events, rather than the maps?
map.data.setStyle(
function(feature){
// Build your styles here based on feature properties.
return style_i_want_for_this_feature;
}
);
That will get called for each feature in your GeoJSON data, and you can treat it appropriately. I load and style feature collections with thousands of features this way.
I'm using OpenLayers to view a map and I'm having an issue with the marker's popup. When the Markers are loaded, I'm assigning them two events the moouseover and mouseout but when any of the markers are triggered with these events only the first created marker's popup is shown, even when I mouseover other markers. Its like I'm only creating these events for the first marker and not for all of them.. Any ideas? Thanks
var listMarkers = getMarkers();
for (var i = 0; i < listMarkers.length; i++) {
var size = new OpenLayers.Size(21, 25);
var offset = new OpenLayers.Pixel(-(size.w / 2), -size.h);
var icon;
if (listMarkers[i].Icon.trim() === "red") {
icon = new OpenLayers.Icon
('http://www.openlayers.org/dev/img/marker.png', size, offset);
}
else {
icon = new OpenLayers.Icon
('http://www.openlayers.org/dev/img/marker-' + listMarkers[i].Icon.trim() + '.png', size, offset);
}
var mark = new OpenLayers.Marker(new OpenLayers.LonLat(listMarkers[i].Longitude,
listMarkers[i].Latitude).transform(new OpenLayers.Projection("EPSG:4326"),
map.getProjectionObject()), icon);
//here add mouseover event
mark.events.register('mouseover', mark, function (evt) {
popup = new OpenLayers.Popup.FramedCloud("Popup",
new OpenLayers.LonLat(listMarkers[i].Longitude,
listMarkers[i].Latitude).transform(new OpenLayers.Projection("EPSG:4326"),
map.getProjectionObject()),
null,
'<div><b>' + listMarkers[i].Title + '</b><br/>' + listMarkers[i].Description + '</div>',
null,
false);
map.addPopup(popup);
});
//here add mouseout event
mark.events.register('mouseout', mark, function (evt) { popup.hide(); });
markers.addMarker(mark);
}
In the mouseover event while creating popup you're referring to listMarkers[i], which in javascript scope would remember last value of given variable i, so for every popup it would get information from listMarkers[listMarkers.length-1]. To fix this, add details (Title, Latitude, Longitude) into the mark object (mark.data.Title = listMarkers[i]), and then read them in event handler from evt or this object (as you're setting it in register call).
I am looking for a Google Map V3 context menu library. I have found some code examples here
Gizzmo's blog
Google API tips
GMap3
How I got ..
Stack overflow question Google maps v3 - Contextual menu available? of April also just came up with the above examples. So did Gmap3 adding a simple context menu .
But maybe somebody has encapsulated the examples in a reusable library or found something in the meantime. Obviously there was something for V2.
-- Updated 2012-05-31 --
I have found another one http://googlemapsmania.blogspot.de/2012/04/create-google-maps-context-menu.html , but did not have the time to test it yet.
I don't think you need a library for this. I'd start by trying:
var contextMenu = google.maps.event.addListener(
map,
"rightclick",
function( event ) {
// use JS Dom methods to create the menu
// use event.pixel.x and event.pixel.y
// to position menu at mouse position
console.log( event );
}
);
This assumes your map was created with:
var map = new google.maps.map( { [map options] } );
The event object inside the callback has 4 properties
latLng
ma
pixel
where pixel.x and pixel.y are the offset where your click event triggered - counted from the upper left corner of the canvas holding the map object.
I have created a working JS Fiddle for showing context menu as well as the ability to have clickable items on this context menu.
It shows a clickable Context Menu when a marker is right clicked on Google map.
Basically it makes use of an OverlayView on map. BTW its just a demo.
var loc, map, marker, contextMenu;
ContextMenu.prototype = new google.maps.OverlayView();
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
ContextMenu.prototype.onAdd = function() {
$("<div id='cMenu' class='context-menu-marker'></div>").appendTo(document.body);
var divOuter = $("#cMenu").get(0);
for(var i=0;i < this.menuItems.length;i++) {
var mItem = this.menuItems[i];
$('<div id="' + mItem.id + '" class="options-marker">' +
mItem.label + '</div>').appendTo(divOuter);
}
this.div_ = divOuter;
// Add the element to the "overlayLayer" pane.
var panes = this.getPanes();
//panes.overlayLayer.appendChild();
panes.overlayMouseTarget.appendChild(this.div_);
var me = this;
for(var i=0;i < this.menuItems.length;i++) {
var mItem = this.menuItems[i];
var func = function() {
me.clickedItem = this.id;
google.maps.event.trigger(me, 'click');
};
google.maps.event.addDomListener($("#" + mItem.id).get(0), 'click', $.proxy(func, mItem));
}
google.maps.event.addListener(me, 'click', function() {
alert(me.clickedItem);
});
};
ContextMenu.prototype.draw = function() {
var div = this.div_;
div.style.left = '0px';
div.style.top = '0px';
div.style.width = '100px';
div.style.height = '50px';
};
// The onRemove() method will be called automatically from the API if
// we ever set the overlay's map property to 'null'.
ContextMenu.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
// Set the visibility to 'hidden' or 'visible'.
ContextMenu.prototype.hide = function() {
if (this.div_) {
// The visibility property must be a string enclosed in quotes.
this.div_.style.visibility = 'hidden';
}
};
ContextMenu.prototype.show = function(cpx) {
if (this.div_) {
var div = this.div_;
div.style.left = cpx.x + 'px';
div.style.top = cpx.y + 'px';
this.div_.style.visibility = 'visible';
}
};
function ContextMenu(map,options) {
options = options || {}; //in case no options are passed to the constructor
this.setMap(map); //tells the overlay which map it needs to draw on
this.mapDiv = map.getDiv(); //Div container that the map exists in
this.menuItems = options.menuItems || {}; //specific to context menus
this.isVisible = false; //used to hide or show the context menu
}
function initialize() {
loc = new google.maps.LatLng(62.323907, -150.109291);
var options = {};
var menuItems=[];
menuItems.push({id:"zoomIn", className:'context_menu_item', eventName:'zoom_in_click', label:'Zoom in'});
menuItems.push({id:"zoomOut", className:'context_menu_item', eventName:'zoom_out_click', label:'Zoom out'});
options.menuItems = menuItems;
//=========================================
map = new google.maps.Map(document.getElementById("map"), {
zoom: 12,
center: loc,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
marker = new google.maps.Marker({
map: map,
position: loc,
visible: true
});
contextMenu = new ContextMenu(map, options);
google.maps.event.addListener(marker, 'rightclick', function(mouseEvent){
contextMenu.hide();
this.clickedMarker_ = this;
var overlayProjection = contextMenu.getProjection();
var cpx = overlayProjection.fromLatLngToContainerPixel(mouseEvent.latLng);
contextMenu.show(cpx);
map.setOptions({ draggableCursor: 'pointer' });
});
// Hide context menu on several events
google.maps.event.addListener(map,'click', function(){
map.setOptions({ draggableCursor: 'grab' });
contextMenu.hide();
});
}
google.maps.event.addDomListener(window, 'load', initialize);
Fiddle link:
http://jsfiddle.net/jEhJ3/3409/
You can add context menu very easily in google map by following these steps:
Add a custom control of google maps, hide that control on page load.
Add a right click event handler on map.
Show that custom control on right click at correct position using pixel property of right click event parameter.
Moreover, Following is working snippet, open it in full page (use you own key to avoid that google billing error):
var map;
var karachi = {
lat: 24.8567575,
lng: 66.9701725
};
$(document).ready(function() {
initMap();
});
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 13.5,
center: karachi
});
let contextMenu = document.getElementById('contextMenu');
map.controls[google.maps.ControlPosition.TOP_CENTER].push(contextMenu);
hideContextMenu();
google.maps.event.addListener(map, "rightclick", function(event) {
showContextMenu(event);
});
google.maps.event.addListener(map, "click", function(event) {
hideContextMenu();
});
}
function showContextMenu(event) {
$('#contextMenu').css("display", "block");
$('#contextMenu').css({
left: event.pixel.x,
top: event.pixel.y
})
}
function hideContextMenu() {
$('#contextMenu').css("display", "none");
}
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
.contextMenu {
background-color: rgb(255, 255, 255);
border: 2px solid rgb(255, 255, 255);
border-radius: 3px;
box-shadow: rgba(0, 0, 0, 0.3) 0px 2px 6px;
cursor: pointer;
font-size: 1rem;
text-align: center;
color: #0d1f49;
width: 20vw;
margin: 1px;/*Please note that this margin is necessary otherwise browser will open its own context menu*/
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAGlM3LLIL2j4Wm-WQ9qUz7I7ZpBsUx1X8">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="map"></div>
<div id="contextMenu" class="contextMenu">
<div onclick="alert('On click of item 1 is called')">
Item 1
</div>
</div>
Go to this demo-purpose website: http://easysublease.org/mapcoverjs/
For context menu, I do not suggest implementing one subclass of the overlayView Class provided by Google Maps API. First, one instance of subclass of overlayView should be added to the five panes provided by Google. More possibly one should add this instance to pane overlayMouseTarget .
But, this instance is "shadowed" by other dom over it. So normal original browser event such mouseover, mouseout cannot reach this instance.
One must use Google Maps API method: addDomListener to handle it(why?). It requires lots of JavaScript code to implement different event handlers, do lots of css class adding and deleting just to realize some visual effects, which could be done using several lines of CSS code if this instance is outside the map container.
So actually converting one external dom outside google map container into one context menu has merit that it can receive original DOM events from browser. Also using some external library can make the target behave better. As context menu, it should not only be able to handle original events, but also those events from Map.
-----------see implementations below------------------------
At the map part HTML, this is the code:
<div id="mapcover">
<div id="mapcover-map"></div> <!-- this is map container-->
<div id="demoControlPanel" class="mc-static2mapcontainer panel">I am map UI control button's container, I think one can use jQuery UI to make me look better<br><br>
<div id="zoom-in-control" class="text-center">zoomIn</div>
<div id="zoom-out-control" class="text-center">zoomOut</div>
</div>
<div id="demoContextPanel" class="mc-ascontextmenu panel">
I am map context menu container, you can sytle me and add logic to me, just as normal DOM nodes.
since I am not in Map Container at all!
<div class="text-center">
<div role="group" aria-label="..." class="btn-group">
<button id="place-marker1" type="button" class="btn btn-default">Marker1</button>
<button id="place-marker2" type="button" class="btn btn-default">Marker2</button>
</div>
</div>
<div class="form-group">
<label for="content-marker1">Content of next Marker1</label>
<input id="content-marker1" type="text" placeholder="New of Marker1!" class="form-control">
</div>
</div>
</div>
It shows how one developer can convert one external DOM (id=demoContextPanel) into one map context menu by just adding one css class ".mc-ascontextmenu"!
That pages uses mapcover.js, which helps developer to manage some key components of Map such as Map control UIs, context menu, and customized markers. Then Developers have full freedom to style its map UIs.
If you need more, you can go to its Github see readme.md: https://github.com/bovetliu/mapcover