Mapbox-gl popup.on('open') not firing - mapbox-gl-js

Using Mapbox GL Javascript Web
My popups are opening but the 'open' event isn't firing. I read that this was fixed a while back so is there something I'm doing wrong here:
this.map.on('click', 'listings', (e: any) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const detailURI = e.features[0].properties.detailURI;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(title)
.addTo(this.map)
.on('open', () => {
console.log('Popup opened'); // <--- Not firing
// Add a click listener to the custom button with dynamic URI
document.getElementById('popup-detail-button')
.addEventListener('click', () => {
console.log(`Clicked with link: ${detailURI}`);
});
});
});
If I do it like this:
this.map.on('click', 'listings', (e: any) => {
const coordinates = e.features[0].geometry.coordinates.slice();
const detailURI = e.features[0].properties.detailURI;
new mapboxgl.Popup()
.setLngLat(coordinates)
.setHTML(this.returnPopupHTML(image))
.addTo(this.map);
// Add a click listener
document.getElementById('popup-detail-button')
.addEventListener('click', () => {
console.log(`Clicked with link: ${detailURI}`); // <-- Only works if closing popup before opening another one
});
});
The click listener on the button works but if I don't close a popup before opening another one then the event doesn't fire. This is something that users frequently do: they open a popup and then scroll over and open another one without closing the first. So what I'm really trying to do here is ensure whenever a popup is opened and it's custom button is clicked - the event is registered with the correct URI.
Thanks

I'm sure you figured it out by now. I'm just posting this for anyone else that comes across the situation. But I was having the same problem. Initially I was trying to do this.
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.addTo(this.map);
popup.on('open', e => {
console.log('it is open');
});
What's weird is that when I used 'close' the console.log would work but not with 'open'.
What finally worked was your format, but putting the .on event listener before the .addTo(map):
const layerClicked: mapboxgl.MapboxGeoJSONFeature[] = this.map.queryRenderedFeatures(
e.point,
{
layers: this.currentPointAndMarkerLayerIds,
},
);
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.on('open', e => {
console.log('It is open');
})
.addTo(this.map);
Thanks for setting me in the right direction!
EDIT
If you wanted to add an event listener I just do this.
const popup = new mapboxgl.Popup()
.setLngLat(e.lngLat)
.setHTML(
this.generatePointAndMarkerPopupHtml(layerClicked[0]),
)
.on('open', e => {
if (document.getElementById('drive-time')) {
document
.getElementById('drive-time')
.addEventListener('click', e => {
console.log(e);
});
}
})
.addTo(this.map);
I check if the document element exists or else it will create an error for in the developer console for me.

Related

Add interactivity to POIs from different tilesets

I'm working on Mapbox Studio Tutorial and practicing adding interactivity on POIs on map.
https://docs.mapbox.com/help/tutorials/add-points-pt-3/
map.on('click', (event) => {
// If the user clicked on one of your markers, get its information.
const features = map.queryRenderedFeatures(event.point, {
layers: ['layer1',"layer2","layer3"]
});
if (!features.length) {
return;
}
const feature = features[0];
/*
Create a popup, specify its options
and properties, and add it to the map.
*/
const popup = new mapboxgl.Popup({ offset: [0, -15] })
.setLngLat(feature.geometry.coordinates)
.setHTML(
`<h3>${feature.properties.title}</h3><p>${feature.properties.description}</p>`
)
.addTo(map);
});
The error I get is that the title of POIs from layer2 and layer3 is shown as "undefined" while layer1's title can be shown when clicking it on the map.
I think "undefined" comes because the title is not stored in the feature property but have no clear idea how to do that correctly.
I tried some codes I got from the internet such as below:
if (features.length > 0) {
// Loop feature and concatenate property as HTML strings
let propertiesHTML = '';
features.forEach(feature => {
Object.entries(feature.properties).forEach(([key, value]) => {
propertiesHTML += `<p><strong>${key}:</strong> ${value}</p>`;
});
});
// Create and add a popup
const popup = new mapboxgl.Popup({ offset: [0, -15] })
.setLngLat(features[0].geometry.coordinates)
.setHTML(propertiesHTML)
.addTo(map);
With code at least map is shown, but there is no interactive popup shown when I click it.

How can i stop this onClick event from rerendering the entire treemap echart i have?

How can i stop this onClick event from rerendering the entire treemap echart i have?
I have basically a echarts treemap https://echarts.apache.org/examples/en/editor.html?c=treemap-disk as a functional component in react. I need to be able to apply filters and "grey out" certain tree nodes that dont fit the criteria. This functionality works currently but it rerenders the echart so that the user must restart from the top level and clicktheir way through all the way to the bottom level. How can i avoid the rerendering? This is a similar example i have where clicking the node displays data but also rerenders the chart losing where the node was in the map.
const onChartClick = params => {
if (params.treePathInfo.length === 9) {
setDrawerData(params);
}
};
useEffect(() => {
props.setDrawerData(drawerData);
}, [drawerData]);
const onEvents = {
click: onChartClick,
}; ```
you can try to put your chart on useMemo it works for me :
const [dataLoaded, setdataLoaded] = useState(true);
const onChartClick = params => {
if (params.treePathInfo.length === 9) {
setDrawerData(params);
}
};
useEffect(() => {
props.setDrawerData(drawerData);
setdataLoaded(false)
}, [drawerData]);
const onEvents = {
click: onChartClick,
};
const MemoChart = useMemo(() => <Charts
option={option}
onEvents={onEvents}
/>, [dataLoaded]);

Is there any better way to get source marker on "popupopen" event in leaflet?

So this is how I do it now:
map.on('popupopen', ({ popup }) => {
if (popup instanceof L.Popup) {
const marker = popup._source as L.Marker;
}
});
I really don't like accessing private variables in leaflet. I still have not found in leaflet api clean method to get marker that is binded to active popup.
Better way is to emit new event on marker popupopen and access it from wherever you want.
popupopen: () => {
map.fire('someevent', { somemarker });
},
map.on({
'someevent': (event) => {} // <- event has marker
});

add leave or stay on the page alert in ionic 3?

I'm trying to add a popup that will be shown on a page when the user starts to populate data in the form and then he decided to go somewhere else in the app.
This popup will show this message: 'Do you want to leave this page and save your changes?'
three buttons are available: Stay, Leave and Save before leaving.
I'm new to ionic logic and I couldn't figure out how to do this.
I started by adding a button in the page that shows the popup (Still don't know how to trigger the event when the user clicks on any link of the sidebar for example). when the user clicks on that button the popup is shown with the three buttons.
The problem is that I don't know how to implement the handlers of these buttons.
This is what I have in the ts file :
leaveOrStayModal() {
let e = event || window.event;
e.stopPropagation();
this.alertMixin.presentAlert(
'Do you want to leave this site?\n',
"You haven't saved your changes!",
'Stay',
'Leave',
'Save',
null,
() => {
console.log('leave handler')
// this.navCtrl.push() Here I don't know how to get the exact link clicked from the sidebar ? to go to
},
() => {
console.log('Save handler')
//here I want to save the form ?
}
)
}
the popup code:
presentAlert(title: string, message: string, btnOneText: string, btnTowText: string, btnThreeText: string,
btnOneHandler?: () => void, btnTowHandler?: () => void, btnThreeHandler?: () => void,
present: boolean = true) {
let confirm = this.alertCtrl.create({
title: title,
message: message,
buttons: [
{
text: btnOneText,
handler: () => {
if (btnOneHandler) {
btnOneHandler();
}
}
},
{
text: btnTowText,
handler: () => {
if (btnTowHandler) {
btnTowHandler();
}
}
},
{
text: btnThreeText,
handler: () => {
if (btnThreeHandler) {
btnThreeHandler();
}
}
}
]
});
if (present) {
confirm.present().then();
}
return confirm;
}
And this is the button that shows the popup (to be removed )
<button ion-button icon-left item-right type="button" (click)="leaveOrStayModal()"> Click to show modal </button>
You should leverage life cycle hook ionViewCanLeave for that. Some basic documentation here: https://ionicframework.com/docs/api/navigation/NavController/
For your context I just drafted the way I would do it (its a bit dirty):
userCanLeave = false;
ionViewCanLeave() {
// here you can use other vars to see if there are reasons we want to keep user in this page:
if (!this.userCanLeave) {
return new Promise((resolve, reject) => {
let alert = this.alertCtrl.create({
title: 'Are you sure?',
message: 'The form data may be lost',
buttons: [
{
text: 'Stay',
role: 'cancel',
handler: () => {
console.log('User stayed');
this.userCanLeave = false;
reject();
}
},
{
text: 'Leave',
handler: () => {
console.log('User leaves');
this.userCanLeave = true;
resolve();
}
},
{
text: 'Save',
handler: () => {
console.log('User saved data');
// do saving logic
this.userCanLeave = true;
resolve();
}
}
]
});
alert.present();
});
} else { return true }
}
userCanLeave - here is just example of a var that defines if the page has the state where we would not want a user to leave "freely".
then we use promise to ensure that user can not leave without answering dialogue options, we wait for their answers to define whether life cycle hook gets true/false flag to proceed.
Please note also that this life cycle hook only "kicks in" when a view (page) gets off the stack (pops) if you would push in a new view - it won't guard that. But in this case new pushed in page won't destroy user's data in the form anyway and user can safely return to it once you dismiss that newly pushed in page.
Hope this helps.

How to show different popups on click and on mouseover?

The SelectFeature method in Control class provides a way of adding and removing popups on the Vector layer by listening to events featureselected and featureunselected respectively. Below shows a sample code that I obtained from an example in the openlayers website:
// create the layer with listeners to create and destroy popups
var vector = new OpenLayers.Layer.Vector("Points",{
eventListeners:{
'featureselected':function(evt){
var feature = evt.feature;
var popup = new OpenLayers.Popup.FramedCloud("popup",
OpenLayers.LonLat.fromString(feature.geometry.toShortString()),
null,
"<div style='font-size:.8em'>Feature: " + feature.id +"<br>Foo: </div>",
null,
true
);
feature.popup = popup;
map.addPopup(popup);
},
'featureunselected':function(evt){
var feature = evt.feature;
map.removePopup(feature.popup);
feature.popup.destroy();
feature.popup = null;
}
}
});
vector.addFeatures(features);
// create the select feature control
var selector = new OpenLayers.Control.SelectFeature(vector,{
hover:true, # this line
autoActivate:true
});
The code above will allow a popup to be shown upon mouseover on the Geometry object (icon or marker on the map). If the line hover:true is removed, the popup will be shown only upon a mouse click on the Geometry object.
What I want, is to be able to display one type of popup (example, an image plus a title) upon mouseover and another type (example, detailed description) upon a mouse click. I am not sure how this could be done. Some help would be much appreciated. Thanks.
Also, there another way, it's rather hack than correct usage of API, but seems to work. You can overwrite over and out callbacks.
var selectControl = new OpenLayers.Control.SelectFeature(vectorLayer, {
callbacks: {
over: function(feat) {
console.log('Show popup type 1');
},
out: function(feat) {
console.log('Hide popup type 1');
}
},
eventListeners: {
featurehighlighted: function(feat) {
console.log('Show popup type 2');
},
featureunhighlighted: function(feat) {
console.log('Hide popup type 2');
}
}
});
Here's working example: http://jsfiddle.net/eW8DV/1/
Take a look on select control's source to understand details.