How to get notified when my element has been attached to the DOM - dom

I'm adding a custom control that uses SVG to a Google Map.
After the map has been loaded and my control is shown, I need to grab the BBox from the svg element. Since I do not control when my element is attached to the DOM, I'm trying to find an event that will allow me to do the work in a callback.
Here's roughly what I have:
map = new google.maps.Map(...);
...
container = document.createElement("div")
svg = createAndDrawSVGElement(...); //this returns an svg element
container.appendChild(svg);
INSERT_THE_RIGHT_EVENT_HERE(function() {
var bbox = svg.getBBox();
... //bbox will be empty if svg isn't attached
}
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(container);
My current, ugly workaround is a setTimeout. I'd like something more predictable.

I was able to resolve this without an event, by attaching my SVG to a hidden DIV temporarily, so I can get the bbox.
Solution here: https://stackoverflow.com/a/45465286/62024

After looking at the API, I would try this (although the doc does not explicitely say whether this is called before or after attaching the element):
var map = new google.maps.Map(...);
...
var container = document.createElement("div")
var svg = createAndDrawSVGElement(...); //this returns an svg element
container.appendChild(svg);
var controls = map.controls[google.maps.ControlPosition.RIGHT_BOTTOM];
var position = controls.length;
google.maps.event.addListenerOnce(controls, "insert_at", function(i) {
if (i === position) {
var bbox = svg.getBBox();
... //etc
}
}
controls.insertAt(pos, container);

Related

Get leaflet marker from a layer

I'm new to leaflet and am trying to implement a set of markers with different CSS-styles.
So, I am aware that after adding a marker to a map I can access different CSS-attributes by calling getElement() on my marker for example:
marker.addTo(map);
marker.getElement().style.borderColor = '#000';
This works just fine, but when adding a marker to a layer, this can no longer be used since a TypeError occurs (getElement() is undefined). Here is the example code where the error occurs:
myLayer.addLayer(marker);
marker.getElement().style.borderColor = '#000';
Am I overlooking a simpler way to set CSS-Attributes for markers and divicons that are added to layers or is there a similar way to access layer-added markers and divicons in JavaScript?
So I found a solution that is working for me.
The idea is to extend the function that is used to create the icon.
Last answer here github.com/Leaflet/Leaflet/issues/5231 helped a lot.
var borderSize = ...;
L.DivIcon.Custom = L.DivIcon.extend({
createIcon: function(oldIcon) {
var icon = L.DivIcon.prototype.createIcon.call(this, oldIcon);
icon.style.borderSize = borderSize;
...
return icon;
}
})
var icon = new L.DivIcon.Custom({
...
});
var ll = L.latLng(entry.Longitude, entry.Latitude);
var marker = L.marker(ll, {
icon: icon
})
this.myLayer.addLayer(marker);
Welcome to SO!
When not added onto a map (since your parent myLayer may not be added to the map itself), a marker does not have any element.
If you do not need to change too many styles individually and dynamically, you might rather use the className option of your Icon / DivIcon.

Why doesn't marker.dragging.disable() work?

The following code receives an error on the lines for enabling and disabling the marker dragging ("Unable to get property 'disable' of undefined or null reference"). The markers show up on the map just fine and are draggable as the creation line indicates. Placing an alert in place of the enable line produces a proper object so I believe the marker is defined. Is there something I need to do to enable the IHandler interface? Or am I missing something else?
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:'true'})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
if (mode === 0) {
marker.dragging.enable();
} else {
marker.dragging.disable();
}
I had a similar problem today (perhaps the same one) it was due to a bug in leaflet (see leaflet issue #2578) where changing the icon of a marker invalidates any drag handling set on that marker. This makes any calls to marker.dragging.disable() fail.
The fix hasn't made it into leaflets master at time of writing. A workaround is to change the icon after updating the draggable status if possible.
marker.dragging.disable();
marker.setIcon(marker_icon);
Use the following code to make an object draggable. Set elementToDrag to the object you wish to make draggable, which is in your case: "marker"
var draggable = new L.Draggable(elementToDrag);
draggable.enable();
To disable dragging, use the following code:
draggable.disable()
A class for making DOM elements draggable (including touch support).
Used internally for map and marker dragging. Only works for elements
that were positioned with DomUtil#setPosition
leaflet: Draggable
If you wish to only disable the drag option of a marker, then you can use the following code (where "marker" is the name of your marker object):
marker.dragging.disable();
marker.dragging.enable();
I haven't found an answer but my workaround was this:
var temp;
if (mode === 0) {
temp = true;
} else {
temp = false;
}
var marker = L.marker(L.latLng(lat,lon), {icon:myIcon, draggable:temp})
.bindLabel(name, {noHide: true,direction: 'right'});
marker._myId = name;
Fortunately I change my icon when it is draggable.

What is the proper way in OpenLayers (OSM) to trigger a popup for a feature?

I have the feature ID, I can grab the marker layer on GeoRSS loadend, but I'm still not sure how to cause the popup to appear programmatically.
I'll create the popup on demand if that's necessary, but it seems as though I should be able to get the id of the marker as drawn on the map and call some event on that. I've tried using jQuery and calling the $(marker-id).click() event on the map elements, but that doesn't seem to be working. What am I missing?
Since I was asked for code, and since I presumed it to be boilerplate, here's where I am so far:
map = new OpenLayers.Map('myMap');
map.addLayer(new OpenLayers.Layer.OSM());
map.addLayer(new OpenLayers.Layer.GeoRSS(name,url));
//I've done some stuff as well in re: projections and centering and
//setting extents, but those really don't pertain to this question.
Elsewhere I've done a bit of jQuery templating and built me a nice list of all the points that are being shown on the map. I know how to do a callback from the layer loadend and get the layer object, I know how to retrieve my layer out of the map manually, I know how to iter over the layers collection and find my layer. So I can grab any of those details about the popup, but I still don't know how to go about using the built-in methods of the DOM or of this API to make it as easy as element.click() which is what I would prefer to do.
You don't have to click the feature to open a popup.
First you need a reference to the feature from the feature id. I would do that in the loadend event of the GeoRSS layer, using the markers property on the layer.
Assuming you have a reference to your feature, I would write a method which handles the automatic popup:
var popups = {}; // to be able to handle them later
function addPopup(feature) {
var text = getHtmlContent(feature); // handle the content in a separate function.
var popupId = evt.xy.x + "," + evt.xy.y;
var popup = popups[popupId];
if (!popup || !popup.map) {
popup = new OpenLayers.Popup.Anchored(
popupId,
feature.lonlat,
null,
" ",
null,
true,
function(evt) {
delete popups[this.id];
this.hide();
OpenLayers.Event.stop(evt);
}
);
popup.autoSize = true;
popup.useInlineStyles = false;
popups[popupId] = popup;
feature.layer.map.addPopup(popup, true);
}
popup.setContentHTML(popup.contentHTML + text);
popup.show();
}
fwiw I finally came back to this and did something entirely different, but his answer was a good one.
//I have a list of boxes that contain the information on the map (think google maps)
$('.paginatedItem').live('mouseenter', onFeatureSelected).live('mouseleave',onFeatureUnselected);
function onFeatureSelected(event) {
// I stuff the lookup attribute (I'm lazy) into a global
// a global, because there can be only one
hoveredItem = $(this).attr('lookup');
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
}
function onFeatureUnselected(event) {
/* Do something here to indicate the onhover */
// find the layer pagination id
var feature = findFeatureById(hoveredItem);
if (feature) {
// use the pagination id to find the event, and then trigger the click for that event to show the popup
// also, pass a null event, since we don't necessarily have one.
feature.marker.events.listeners.click[0].func.call(feature, event)
}
/* Do something here to stop the indication of the onhover */
hoveredItem = null;
}
function findFeatureById(featureId) {
for (var key in map.layers) {
var layer = map.layers[key];
if (layer.hasOwnProperty('features')) {
for (var key1 in layer.features) {
var feature = layer.features[key1];
if (feature.hasOwnProperty('id') && feature.id == featureId) {
return feature;
}
}
}
}
return null;
}
also note that I keep map as a global so I don't have to reacquire it everytime I want to use it

Dojo drag and drop, how do we save the position

After dojo drag and drop, once the page is submitted, I have to save the position of every item that has been placed into "targetZone". How can we save the position?
Eugen answered it here :
Dojo Drag and drop: how to retrieve order of items?
That would be the right way. If you look at the link above, you can save the resulting "orderedDataItems" object as a JSON ...
Look at the following function. It saves our DND "Lightbox" (dojo.dnd.source) to a JSON.
_it is the current raw dnd item
_it.data.item contains all your stuff you need to keep
in our case _it.data.item.label keeps the customized nodes (pictures, video, docs) as a string, we can use later to dojo.place it
it is the dnd item you want to save without dom nodes
E.g. if you drop items from a dijit tree to a arbitrary dojo dnd source / target:
_RAM or _S in our data.item we made before needs to be overwritten.
LBtoJson: function(){
var that = this;
var orderedLBitems = this.dndSource.getAllNodes().map(function(node){
var _it = that.dndSource.getItem(node.id);
var it = { data:{ item:{} }, label:'', type:'' };
if((_it.data.item._RAM)){_it.data.item._RAM={}}
if((_it.data.item._S)){_it.data.item._S={}}
it.data.item = dojo.clone(_it.data.item);
it.label = it.data.item.label[0]||it.data.item.label;
it.type = _it.type;
console.log( it );
return it;
});
var LBjson = dojo.toJson(orderedLBitems);
return LBjson;
}
By calling getAllNodes(), you'll receive a list of nodes in the order they are shown. So if you wanted to save a list in a specific order, you could do something similar to this:
var data;
var nodes = dndSrc.getAllNodes();
for(var i; i < nodes.length; i++)
{
data.push({id: nodes[i].id, order: i});
}
For more information about Dojo DnD regarding data submission, check out this article about DnD and Form Submission: http://www.chrisweldon.net/2009/05/09/dojo-drag-n-drop-and-form-submission

duplicate layer/ show permanantly on click

I'm starting to learn how to use leaflet. I'm trying to create a map with markers. If you hover them they should display a route. if the mouse leaves the marker the route should be deleted. (This part works)
When you click on the marker the route should stay on the map even when the mouse leaves the marker.
Therefore I would need to duplicate the route layer so that it doesn't get deleted when the mouse leaves the marker. Or there is a better method that I don't know.
function Route() {
DirectionsLayerLong = omnivore.gpx('GPX/ Route_long.gpx');
DirectionsLayerLong.on('ready', function() {
this.setStyle(style_long);
});
DirectionsLayerShort = omnivore.gpx('GPX/Route_short.gpx');
DirectionsLayerShort.on('ready', function() {
this.setStyle(style_short);
});
return DirectionsLayer = L.featureGroup([DirectionsLayerLong, DirectionsLayerShort]);
};
var Marker = L.marker([50, -100], {
icon: iconfu
}).addTo(map);
Marker.on('mouseover', function(e) {
Route();
DirectionsLayer.addTo(map);
});
Marker.on('mouseout', function(e) {
DirectionsLayer.remove()
});
Marker.on('click', function(e) {
DirectionsPermaLayer.remove();
Route();
DirectionsPermaLayer = DirectionsLayer;
DirectionsPermaLayer.addTo(map);
});
I could simply use omnivore with another variable but I'd like to reuse the function.
The simplest solution is just to remove the mouseout event listener when you click on the marker:
Marker.on('click', function(e) {
Marker.off('mouseout');
});
Cloning your route layer would be a little more complicated (not to mention unnecessary, if removing the event listener solves your problem), but it's worth exploring how one might do that. First of all, a concise explanation of why you can't just create a copy using DirectionsPermaLayer = DirectionsLayer can be found on w3schools:
Objects are mutable: They are addressed by reference, not by value.
If y is an object, the following statement will not create a copy of
y:
var x = y; // This will not create a copy of y.
The object x is not a copy of y. It is y. Both x and y points to the
same object.
Any changes to y will also change x, because x and y are the same
object.
There are many ways to go about cloning an object in Javascript, but I suspect that most of these will not work for cloning leaflet layers, as all Leaflet's internal ids will be copied along with the object, causing unpredictable behavior. The best strategy would probably be to convert DirectionsLayerShort and DirectionsLayerLong to GeoJSON using the .toGeoJSON method, then read them back in using L.geoJson:
var Short2 = L.geoJson(DirectionsLayerShort.toGeoJSON()).setStyle(style_short);
var Long2 = L.geoJson(DirectionsLayerLong.toGeoJSON()).setStyle(style_long);
var Directions2 = L.featureGroup([Long2, Short2]).addTo(map);
This could require a little refactoring of your code, but it should do the job.