I use several leaflet maps at one time. They can be created and deleted dynamically. But when a map is destroyed by using the map.remove() there is a memory leak. Detached DOM trees appears. You can see it in Chrome Dev Tools.
Screenshot with a leak.
Example function I use to recreate div and map:
var map, mapDiv;
recreateMap = function(){
// destroy previous map and div
if(map) map.remove();
if(mapDiv) mapDiv.parentNode.removeChild(mapDiv);
// create new map div
var randomDivId = 'mapId' + new Date().getTime();
mapDiv = document.createElement('div');
mapDiv.id = randomDivId;
mapDiv.style.height = '200px';
mapDiv.style.width = '200px';
document.getElementsByTagName('body')[0].appendChild(mapDiv);
// attach map to div
map = L.map(randomDivId).setView([51.505, -0.09], 13);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png').addTo(map);
map.invalidateSize();
};
Working example here.
How to properly destroy the leaflet maps?
Assume you create a leaflet map with some thing like the following
var lat =39, long = 40;
var coords=[lat,long];
var zoomLevel=13;
var mapInstance = leafLet.map(mapContainerId).setView(coords, zoomLevel);
You can remove it using the following code
if (mapInstance && mapInstance.remove) {
mapInstance.off();
mapInstance.remove();
}
If you are working with non blocking javascript code or async calls, if required you can use a timer to ensure that your code does not error out. Following is a sample implementation of the same
var timeoutIndex=0;
var watcher=window.setInterval(function(){
timeoutIndex++;
if (mapInstance && mapInstance.remove) {
mapInstance.off();
mapInstance.remove();
window.clearInterval(watcher);
}
if(timeoutIndex >50) { //wait for 5 seconds before giving up
window.clearInterval(watcher);
}
},100);
It worked for me. Guess it helps you too
If you have several maps, you need to put each map into diffents var.
var map1 = L.map(mapDiv1);
var map2 = L.map(mapDiv2);
You can also create a function to control map initialisation and container :
function BoolMapInit(map, mapDiv) {
return (map != null && map._container.id == divMap);
}
And the fonction to remove existing map :
function RemoveExistingMap(map) {
if (map != null) {
map.remove();
map = null;
}
}
Hope this help ;)
I had the same problem, and after spending much time on it, the best solution for this problem is to put the map container in a div, and when you want to regenerate the map, remove all of the div's HTML and create a new map container:
<div id="map-bx">
<div id="map"></div>
</div>
<script type="text/javascript">
// Map init
</script>
and when you want to regenerate (destroy) the map like so:
<script type="text/javascript">
$("#map-box").html("");
$("#map-box").html('<div id="map"></div>');
// map init code
</script>
in my case, map.remove() or map.unload() do not work.
maybe try to unload map first? (documentation says that map is being unloaded automatically when using remove method but it's worth to give an additional shot and try unload it manually first)
Related
I'm still learning and I'm a bit stuck. I may be trying to do to much at once. I have a MapBox map working great with a clickable layer menu taken from examples on the MapBox site. I also have a MarkerClusterGroup which also works and is always visible on the map. Is there a way I could somehow have the MarkerClusterGroup clickable on/off just like layers identified in var overlays = { ...
Below is the code that I think needs the help:
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
Markers: L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
var clusterGroup = new L.MarkerClusterGroup();
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup);
});
Could be something as simple as misuse of brackets. Thanks in advance.
You have to include your Marker Cluster Group in your overlays object. For example you could instantiate it just before defining overlays, even if your Cluster Group is empty for now.
Then you fill it once it has downloaded its data.
var layers = {
Streets: L.mapbox.tileLayer('mapbox.streets').addTo(map),
Satellite: L.mapbox.tileLayer('mapbox.satellite'),
Light: L.mapbox.tileLayer('mapbox.light'),
};
var clusterGroup = L.markerClusterGroup();
var overlays = {
DataA: L.mapbox.featureLayer().loadURL('/data/ctsnew.geojson'),
DataB: L.mapbox.featureLayer().loadURL('/data/selectZipcodes.geojson'),
Markers: clusterGroup
};
// Since featureLayer is an asynchronous method, we use the `.on('ready'`
// call to only use its marker data once we know it is actually loaded.
L.mapbox.featureLayer('examples.map-h61e8o8e').on('ready', function(e) {
// The clusterGroup gets each marker in the group added to it
// once loaded, and then is added to the map
e.target.eachLayer(function(layer) {
clusterGroup.addLayer(layer);
});
map.addLayer(clusterGroup); // use that line if you want to automatically add the cluster group to the map once it has downloaded its data.
});
I have a map on which im loading the markers with geoJSON.
When the map loads i run a function buildVisibleSys which is responsible to build a list of currently visible systems on the map.
That function looks like this:
buildVisibleSys = function() {
var bounds, visibleSys;
visibleSys = [];
bounds = map.getBounds();
return systemLocations.eachLayer(function(marker) {
var link;
link = onScreenEl.appendChild(document.createElement('a'));
link.href = '#';
link.id = "marker" + marker._leaflet_id;
link.innerHTML = marker.options.title;
link.onclick = function() {
marker.openPopup();
map.panTo(marker.getLatLng());
};
});
};
map.on('load', buildVisibleSys);
In this function, for each layer im getting some data and building a html block with the names of each marker. Each of those names, associated to the link var, have a onclick event attached that will center the map on the correspondent marker. This all works except for the marker.openPopup() call i also have on that onclick event.
Any idea of what am I missing here?
I've also made a demo of the code available here:
http://jsfiddle.net/lmartins/z8wBW/
UPDATE:
Even more confusing to me is that with mouseover the same method works without a problem, that is, in the function above the following code do open the popup:
link.onmouseover = function(ev) {
marker.openPopup();
marker._icon.classList.add('is-active');
};
Change your link handler to
link.onclick = function(e) {
marker.openPopup();
map.panTo(marker.getLatLng());
e.stopPropagation();
e.preventDefault();
};
The click of the link to open the popup is bubbling down to the map and closing the popup right after it's opened.
/// <reference path="openlayers.d.ts" />
class MapComponent {
element: HTMLElement;
map: OpenLayers.Map;
constructor(element: HTMLElement) {
// Setup our map object
this.element = element;
this.map = new OpenLayers.Map(this.element);
}
init() {
// Setup our two layer objects
var osm_layer_map = new OpenLayers.Layer.OSM("OSM");
// Add layers to the map
this.map.addLayers([osm_layer_map]);
// Add a layer switcher control
this.map.addControl(new OpenLayers.Control.LayerSwitcher({}));
// Zoom the map to the max extent
if (!this.map.getCenter()) {
this.map.zoomToMaxExtent();
}
}
}
window.onload = () => {
var el = document.getElementById('map');
var mc = new MapComponent(el);
mc.init();
}
I have the above piece of code to work with a simple HTML file with only 1 of ID, 'map' with style: height and width # 500px.
I have tried several other ways to get the map to display but so far all i got was a white page (blank).
Can anybody point me in the right direction?
Solutions tried so far:
using jquery with ready function
replace window.onload with a call direct from the html, <script><script/>
place document.getElementById() in the new OpenLayers.Map(here); when first creating this.map
placing the window.onload call above and below (currently)
using export class or public init() or both
As of now, I just want it to work.
Seems that creating the map with the element provided and later defining the options doesn't work.
Instead either initialize the map with options
var options = {
projection: "EPSG:3857",
maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
};
this.map = new OpenLayers.Map(this.element, options);
Or call this.map.render(this.element) at the end of your init method.
Also make sure your div is actually visible and has some size specified, otherwise it might be not visible...
How to disable mobile touch event after the bing map is initialized?
We can disable before initializing by below code, using MapOptions object. However I'm looking after the Bing Map is initialized.
// Set the map and view options, setting the map style to Road and
// removing the user's ability to change the map style
var mapOptions = {credentials:"Bing Maps Key",
height: 400,
width: 400,
mapTypeId: Microsoft.Maps.MapTypeId.road,
disableTouchInput : true,
};
// Initialize the map
var map = new Microsoft.Maps.Map(document.getElementById("mapDiv"), mapOptions);
Any help is highly appreciated. Thanks in advance!!!
Most of the MapOptions do work when passed into the setOptions method of the map. For instance try this: map.setOptions({disableTouchInput: true});
Note that I've only tested this in IE. If you simply want to disable panning and zooming you can do this in a number of different ways. The first is to use map options, the other is to use the viewchange event, store the original map position and keep setting the map to the same view to lock it.
Since you can't set most of the MapOptions once the map is created you can only do this by swapping out your map for a new map with the options you want. This is a very basic example, but here is an example that shows and hides the bing logo which is one of the settings that you can't change with setOptions.
function switchMapOptions(active, inactive) {
try {
var newMap = new MM.Map($(inactive)[0], options);
for (var i = 0; i < map.entities.getLength(); i++) {
var loc = map.entities.get(i).getLocation();
newMap.entities.push(new MM.Pushpin(loc));
}
newMap.setView({center: map.getCenter(), zoom: map.getZoom(), animate: false});
map.dispose();
map = newMap;
}
catch (e) {
alert(e.message);
}
}
Full code at Jsfiddle: http://jsfiddle.net/bryantlikes/zhH5g/4/
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.