I have a map with draw controls to create, edit and remove markers.
All works fine, but I would like to remove the marker just with one click... without save confirm.
I haven't found any posts explaining how I can do this ... or my search is incorrect.
only this code
L.EditToolbar.Delete.include({
enable: function () {
this.options.featureGroup.clearLayers();
}
});
but this remove all markers... I want remove the markers one by one without confirm
i used this methods and its worked fine:
myMarker = L.marker(map.getCenter(), {icon: ImgSrc}).bindPopup("<button type='button' class='btn btn-danger btn-curve' onClick='removethismarker("+count+",\"Src\")'><i class='fa fa-trash'></i></button>").addTo(map);
myMarker._id = count;
map.addLayer(myMarker);
Related
Hi is there any way to pass jsx component to bindPopup function so I can push redux commands on button click?
pointToLayer={(
geoJsonPoint: Feature<Point, DeviceProperties>,
latlng,
) => {
const marker = L.marker(latlng);
marker.setIcon(
markerIcon({ variant: geoJsonPoint.properties.relation }),
);
const sddds = (
<div className="font-quicksand">
<h2>{geoJsonPoint.properties.id}</h2>
<h2>{geoJsonPoint.properties.name}</h2>
<p>{geoJsonPoint.properties.description}</p>
<p>{geoJsonPoint.properties.ownerId}</p>
<a
onClick={() => {
dispatch(setDevice(geoJsonPoint.properties));
}}
>
Open device details
</a>
</div>
);
marker.bindPopup(renderToString(sddds));
return marker;
}}
I know I can use react leaflet component but that way I cant pass props into every marker options (I mean marker as layer).
So this has been discussed a bit. There is an issue in the react-leaflet repo discussing this, whose conclusion is to simply use vanilla JS within the bindPopup method to create your popup. I don't like this solution at all, especially when you're trying to use very react oriented event handlers (like react-redux actions) from within a popup.
The question React-leaflet geojson onEachFeature popup with custom react component was asked, which you may have read, as you use react's renderToString method in your code. But as you've probably discovered, this does not maintain any interactivity or JS that your JSX may include. The answerer there came up with the idea of using a modal instead of a popup, but that doesn't exactly answer your question or truly using JSX in a popup based off of a point-layer geojson.
Ultimately, you will not be able to return JSX from the pointToLayer function that is interactive. I think this would be a nice feature that react-leaflet doesn't currently implement. Within the closure of the pointToLayer function, there's no good way to directly write fully functional JSX.
I played with this for a bit, trying to harness pointToLayer and save the feature of each iteration to state, and then render a Marker with Popup from that, but it got me thinking - why bother? Just ditch the GeoJSON component altogether and render your Markers and Popups directly from the JSON object. Like this:
{myGeoJson.features.map((feature, index) => {
return (
<Marker
key={index}
position={L.latLng(feature.geometry.coordinates.reverse())}
>
<Popup>
<button
onClick={() => { yourReduxAction() }}
>
Click meeee
</button>
</Popup>
</Marker>
);
})}
Working sandbox
In this way, you need to work a little harder by manually transforming your GeoJSON into Markers with Popups, but not nearly as hard as trying to bend over backwards by going from JSX (<GeoJSON />) to vanilla JS (pointToLayer) back to JSX (<Popup />).
These are two solutions I have come to and want to share if someone is having same problem.
My problem with using leaflet-react Popup component is that it will not pass geojson properties to marker layer when I just map over geojson object because react-leaflet Marker does not have api for feature like geojson layer does and I need to access those properties via marker layers in other parts of map.
Solution 1:
Use ReactDOM.render() inside pointToLayer method, react will show warning about pure functions but it will work. You just shoud not render imported component because it will complain about store and redux provider, instead paste component code inside render. If you want to avoid warnings create another function / hook and render code inside its useEffect() to container (div or something).
Here is example:
const popup = L.popup();
const marker = L.marker(latlng);
const container = L.DomUtil.create('div');
render(
<div>
<h2>{props.id}</h2>
<h2>{props.name}</h2>
<p>{props.description}</p>
<p>{props.ownerId}</p>
<a onClick={() => dispatch(setDevice(geoJsonPoint.properties))}></a>
</div>,
container,
);
popup.setContent(container);
marker.bindPopup(popup);
return marker;
With custom hook / function:
const useRenderPopup = (props) => {
const container = L.DomUtil('div');
const dispatch = useAppDispatch()
useEffect(() => {
render(
<div>
<h2>{props.id}</h2>
<h2>{props.name}</h2>
<p>{props.description}</p>
<p>{props.ownerId}</p>
<a onClick={() => dispatch(setDevice(props.geoJsonPoint.properties))}></a>
</div>,
container,
);
},[])
return container;
}
and just call this function like popup.setContent(useRenderPopup(someprop)), this way there will be no warning.
Solution 2:
Render everything static with renderToString() and other stuff that need to trigger redux update attach event listeners.
const popup = L.popup();
const marker = L.marker(latlng);
const link = L.DomUtil.create('a');
const container = L.DomUtil.create('div');
const content = <DeviceSummary {...geoJsonPoint.properties} />;
marker.setIcon(markerIcon({ variant: geoJsonPoint.properties.relation }));
link.addEventListener('click', () =>
dispatch(setDevice(geoJsonPoint.properties)),
);
link.innerHTML = 'Show device details';
container.innerHTML = renderToString(content);
container.appendChild(link);
popup.setContent(container);
marker.bindPopup(popup);
return marker;
Here DeviceSummary component is static so I render it as a string and later append link with redux callback added as event listener to it.
(both solutions except custom function example goes into pointToLatyer method inside geoJSON layer)
I am using the following leaflet plugin:
https://github.com/jwasilgeo/Leaflet.Canvas-Flowmap-Layer
I am having issues adding a popup to the map when a user clicks on a point.
L.marker([pts[p].lat, pts[p].lng], {
icon: new L.DivIcon({
html: '<div>Test</div>'
})
}).addTo(map).bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
.openPopup();
The popup shows, but I am unable to get the actual flowmap lines to show up. Is there anyway to allow for a popup and to allow for the lines to show up underneath it?
You can use the fact that the CanvasFlowmapLayer extends L.GeoJSON
You just need to overload the method creating the marker and add your popup there ...
var oneToManyFlowmapLayer = L.canvasFlowmapLayer(geoJsonFeatureCollection, {
pointToLayer: function(geoJsonPoint, latlng) {
var marker = L.circleMarker(latlng);
return marker.bindPopup('' + latlng)
},
// et caetera
Check it out here: https://yafred.github.io/Leaflet.Canvas-Flowmap-Layer/docs/main/
I declare a leaflet map with
<div id="map" class="map-div"></div>
end initialize it with
var map = L.map('map').setView([51.178882, -1.826215],16);
$scope.map = map;
// OSM Mapnik
var osmUrl = "<a href='http://www.openstreetmap.org'>Open StreetMap</a>";
L.tileLayer(
'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© ' + osmUrl,
maxZoom: 18,
}).addTo(map);
I grab some data from my server, and and markers to the map, in a loop, by calling this function (it's AngularJS, but I doubt that that plays a role):
$scope.AddMarkerToMap = function(companyData, index, array)
{
var companyName = companyData.company_name;
var latitude = companyData.latitude;
var longitude = companyData.longitude;
var cssClassname = 'comapny_has_no_present_workers';
if (companyData['currentWorkers'] > 0)
cssClassname = 'comapny_has_present_workers';
var pubLatLng = L.latLng(latitude,longitude);
// see https://leafletjs.com/reference-1.4.0.html#marker
var marker = L.marker(pubLatLng,
{
// this is the tooltip hover stuff
title: companyData['currentWorkers'] + ' current matches ' + companyData['previousWorkers'] + ' previous matches',
// see https://leafletjs.com/reference-1.4.0.html#icon
// this is a permanent label.
icon: new L.DivIcon({
className: cssClassname,
////html: '<img class="my-div-image" src="http://png-3.vector.me/files/images/4/0/402272/aiga_air_transportation_bg_thumb"/>'+
//// '<span class="my-div-span">RAF Banff Airfield</span>'
html: '<span>' + companyName + '</span>'
})
}).addTo($scope.map);
// see https://leafletjs.com/reference-1.4.0.html#popup
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
}; // AddMarkerToMap()
And the entire map is suddenly grey - with no problems reported in the developer console.
If I comment out the line
marker.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
then everything displays as expected.
The code seems correct, as per the Leaflet documentation.
[Updtae] I just checked and if I only marker.bindPopup("<b>Hello world!</b><br>I am a popup."), the the map displays and I can click on the marker to display the popup. But when I try to programmatically open it with .openPopup(); the map is all grey.
[Update++] the map and its markers display just fine, with any one of
marker.bindPopup("<b>Hello world!</b><br>I am a popup.");
$scope.map.fitBounds(bounds, {padding: [50, 50]});
but with both, the map is grey :-(
What am I doing wrongly?
I think the issue comes from trying to change the map view (possibly through openPopup with autoPan, which is on by default) too often, typically in a loop without giving any delay for the map to actually set the view between each call.
IIRC, this is already identified as a limitation in Leaflet, but I could not find the exact thread in the issue tracker unfortunately.
Normally, a very simple fix is simply to remove the map view changes within your loop, and keep only the very last one.
In your case, if you have the default behaviour of only 1 Popup being opened at a time, then that would definitely be a valid solution: just open the popup of your last Marker.
If you did configure your map to keep several Popups open simultaneously, and you do want to open all of them through your loop, then make sure to disable autoPan (at least during your loop).
First of all I would like to thank you all for amazing libraries like leaflet/leaflet.draw and leaflet.snap.
What I want to do is outside leaflet.draw control with supporting leaflet snap. This is nicely working with in side map draw control.
Below I show how did I call outside leaflet draw control:
<div><button id="draw_mark1" onclick="drawMarker1()" >Draw Marker1</button></div>
<div><button id="draw_polyline1" onclick="drawPolyline1()" >Draw Polyline1</button></div>
function drawMarker1(){
var markerDrawer1 = new L.Draw.MarkerA(map, { icon: new myIcon_xx() });
markerDrawer1.enable();
}
function drawPolyline1(){
var polylineDrawer1 = new L.Draw.PolylineType1(map);
polylineDrawer1.enable();
}
note:- leaflet.snap not in the tag list. I want to tag it too.
I am able to do this using an ID prefix as the selector, but I need to be able to do it with classes instead. It's an each function for opening up different modal windows on the same page. I need to avoid using ID names because I have some modal windows that will have multiple links on the same page, and when using IDs, only the first link will work.
So here's the function as it works with IDs:
$('div[id^=ssfamodal-help-]').each(function() {
var sfx = this.id,
mdl = $(this),
lnk = $('.link-' + sfx),
cls = $('.ssfamodal-close'),
con = $('.ssfamodal-content');
lnk.click(function(){
mdl.show();
});
cls.click(function(){
mdl.hide();
});
mdl.click(function() {
mdl.hide();
});
con.click(function() {
return false;
});
});
and I'm trying to change it to classes instead, like:
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = this.attr('class'),
etc.
But I cannot get it to work without using IDs. Is it possible?
EDIT Fixed error with semi-colon at end of Vars, and updated Fiddle with the fix. Still not working though.
Here's a Fiddle
** UPDATE **
To be clearer, I need to be able to refer to the same modal more than once on the same page. E.g.:
MODAL 1
MODAL 2
MODAL 3
MODAL 4
LINK TO MODAL 1
LINK TO MODAL 2
LINK TO MODAL 3
LINK TO MODAL 4
OTHER STUFF
LINK TO MODAL 1
LINK TO MODAL 4
LINK TO MODAL 3
OTHER STUFF
LINK TO MODAL 2
ETC.
When using classes get rid of the ID habit :
className1, className2, className3 ... etc
simply use
className
HTML:
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy
</div>
</div>
<div class="ssfamodal-help-base ssfamodal-backdrop">
<div id="help-content" class="ssfamodal-content">
<span class="ssfamodal-close">[x]</span>
Howdy Ho
</div>
</div>
<span class="link-ssfamodal-help-base">One</span>
<span class="link-ssfamodal-help-base">Two</span>
LIVE DEMO
var $btn = $('.link-ssfamodal-help-base'),
$mod = $('.ssfamodal-help-base'),
$X = $('.ssfamodal-close');
$btn.click(function(i) {
var i = $('[class^="link-"]').index(this); // all .link-** but get the index of this!
// Why that?! cause if you only do:
// var i = $('.link-ssfamodal-help-base').index();
// you'll get // 2
// cause that element, inside a parent is the 3rd element
// but retargeting it's index using $('className').index(this);
// you'll get the correct index for that class name!
$('.ssfamodal-help-base').eq(i).show() // Show the referenced element by .eq()
.siblings('.ssfamodal-help-base').hide(); // hide all other elements (with same class)
});
$X.click(function(){
$(this).closest('.ssfamodal-help-base').hide();
});
From the DOCS:
http://api.jquery.com/eq/
http://api.jquery.com/index/
http://api.jquery.com/closest/
Here I created a quite basic example on how you can create a jQuery plugin of your own to handle modals: http://jsbin.com/ulUPIje/1/edit
feel free to use and abuse.
The problem is that class attributes can consist of many classes, rather than IDs which only have one value. One solution, which isn't exactly clean, but seems to work is the following.
$('div').filter(function () {
var classes = $(this).attr('class').split(/\s+/);
for (var i = 0; i < classes.length; i++)
if (classes[i].indexOf('ssfamodal-help-') == 0)
return true;
return false;
}).each(function() {
// code
});
jsFiddle
Or, equivalently
$('div').filter(function () {
return $(this).attr('class').split(/\s+/).some(function (e) {
return e.indexOf('ssfamodal-help-') == 0;
});
}).each(function() {
// code
});
jsFiddle
If there is one-to-one relationship between the modal helps and the modal links which it appears there is...can simplfy needing to match class values by using indexing.
For this reason you don't need unique class names, rather they just overcomplicate things. Following assumes classes stay unique however
var $helps=$('div[id^=ssfamodal-help-]');
var $help_links=$('div[id^=link-ssfamodal-help-]');
$help_links.click(function(){
var linkIndex= $help_links.index(this);
$helps.hide().eq( linkIndex ).show();
});
/* not sure if this is what's wanted, but appeared original code had it*/
$helps.click(function(){
$(this).hide()
})
/* close buttons using traverse*/
$('.ssfamodal-close').click(function(){
$(this).closest('div[id^=ssfamodal-help-]' ).hide();
});
Also believe that this code is a little more readable than original apporach
DEMO
Can you try this,
$('div[class^=ssfamodal-help-]').each(function() {
var sfx = $(this).attr('class');
console.log(sfx);
/*console log:
ssfamodal-help-base ssfamodal-backdrop
ssfamodal-help-base2 ssfamodal-backdrop
*/
});
Demo: http://jsfiddle.net/xAssR/51/
why don't you write like
$('div.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
or
$('.classname').each(function() {
// you can write your desired code here
var sfx = this.attr('class');
var aa= this.attr('id');
});
where classname is the name of the class used for the div in html
Thanks.