Openlayers and catching drag event - javascript

Im using OpenLayers and i need to be able to tell difference between when map has been moved by my own scrip or by user. Yeah im aware that i can use moveend. But it also triggers when the same script is moving or repositioning map based on incoming data from ajax calls. So moveend or other map events wont work.
I did some googling and found OpenLayers.Hander.Drag. But all that i managed with it was to stop users from dragging map.
My script:
this.dragger = new OpenLayers.Handler.Drag('',{
'dragStart': function(evt){
this.userdragged = true;
console.log('drag');
}},{
'map':this.mymap
});
this.dragger.activate();
As you can see, i tried to set userdragged variable to true to use this same variable in moveend event later. Unfortunately all this did was to stop my map from beeing draggable.
Can someone assist me please?
Alan

Got it!
What got it working was :
dragcontrol = new OpenLayers.Control.DragPan({'map':this.mymap, 'panMapDone':function(evt){
this.userdragged = true;
console.log('drag');
}});
dragcontrol.draw();
this.mymap.addControl(dragcontrol);
dragcontrol.activate();
Booyaka!
Edit:
Actually this.userdragged wont work in there... the scope of this is different there. you would need to do something like var that = this; before that object initialization and use that.userdragged = true....
Edit2:
I later found, that this panMapDone function overwrites DragPans own method which has same name. With just my previous example, you can end up with map that results in vector features going out of sync with map, when user drags map. To stop that from happening, you should copy the original functionality into that function too... to make it look something like that:
dragcontrol = new OpenLayers.Control.DragPan({'map':this.mymap, 'panMapDone':function(xy){
if(this.panned) {
var res = null;
if (this.kinetic) {
res = this.kinetic.end(xy);
}
this.map.pan(
this.handler.last.x - xy.x,
this.handler.last.y - xy.y,
{dragging: !!res, animate: false}
);
if (res) {
var self = this;
this.kinetic.move(res, function(x, y, end) {
self.map.pan(x, y, {dragging: !end, animate: false});
});
}
this.panned = false;
}
that.userdragged = true;
// do whatever you want here
}});
dragcontrol.draw();
this.mymap.addControl(dragcontrol);
dragcontrol.activate();
Alan

Looking at the documentation on Drag handlers, it states that it's supposed to be used with a Control object. Are you using it that way? Maybe the code snippet doesn't show it?
"If a handler is being used without a control, the handlers setMap method must be overridden to deal properly with the map."
I haven't tried it, but it seems as you should go for something like this:
var myControl = new OpenLayers.Control();
var dragger = new OpenLayers.Handler.Drag{
control: myControl,
callbacks: { 'done': function() { // do something }},
options: {}
}
myMap.addControl(myControl);
myControl.activate();

Just posting an example here of executing an arbitrary function when the user drag the map, without interfering with the normal click-drag used to pan the map, because this page was the most frequent result during my search to find how to do that.
var CustomDragControl = OpenLayers.Class(OpenLayers.Control, {
defaultHandlerOptions: {
'stopDown': false
/* important, otherwise it prevent the click-drag event from
triggering the normal click-drag behavior on the map to pan it */
},
initialize: function(options) {
this.handlerOptions = OpenLayers.Util.extend(
{}, this.defaultHandlerOptions
);
OpenLayers.Control.prototype.initialize.apply(
this, arguments
);
this.handler = new OpenLayers.Handler.Drag(
this, {
'down': this.onDown //could be also 'move', 'up' or 'out'
}, this.handlerOptions
);
},
onDown: function(evt) {
// do something when the user clic on the map (so on drag start)
console.log('user clicked down on the map');
}
});
then add the control to you map's controls list when creating the map instance, or with a map.addControl(), with
new CustomDragControl ({'autoActivate': true})

i have use that in OpenLayers 6:
map.on('pointerdrag', function (event) {
is_map_center = false;
})
hf gl!

Related

Selecting zones for touch events with jQuery

I'm using https://github.com/n33/jquery.touch for creating touch events.
Wanting to use some gestures around all the body but the only map object with id "map".
I'm trying with:
$('body').not('#map');
and other variations, but these gestures still work in the map.
My code:
var touch = $('#body').not('#map');
touch.enableTouch({useMouse: true});
touch.on('doubleTap', function() { gestures("double"); });
If you are trying to add an event handler you'd need to do something like:
var touch = $('#body'),
exclude = $('#map');
touch.enableTouch({useMouse: true});
touch.on('doubleTap', function(e) {
var $touchedElement = $(e.target);
if (!$touchedElement.is(exclude) {
gestures("double");
}
});
You probably have to set this in the selector itself.
$('body:not(#map)').touchFunction();

Is it possible to cancel a Google Maps API polyline while it's being drawn?

I've built a Google Maps API toolset that allows the user to draw shapes over the map, give them names and record areas. On the completion of each shape, they are prompted to give it a name and set a few other options such as whether or not to show a label on the map.
I'd like to give the user the option to right click and cancel a polyline (or polygon) whilst placing the points, i.e. while they're drawing it.
Based on what I've read in the documentation, I should be able to detect that the user right clicked on the map, but I'm not sure how to cancel the overlay they were drawing, as it won't have been committed to the map yet, which means I won't be able to refer to it as an object.
Any ideas?
Solution
Thanks to Dr Molle for the solution, which was as follows:
...
google.maps.event.addListener(_map, "rightclick", function(){
InitialiseDrawingManager();
});
}
function InitialiseDrawingManager(){
if (_drawingManager != null)
_drawingManager.setMap(null);
_drawingManager = new google.maps.drawing.DrawingManager();
_drawingManager.setMap(_map);
UpdateOverlaySettings();
...
While the code for the accepted answer works, it throws an error, probably due to event listeners not being removed properly.
The following is a better approach:
I used a variable cancelDrawingShape to detect if the Escape button has been pressed while stuff is being drawn. If the cancelDrawingShape is set to true, then I will remove the last drawn shape from the map.
var cancelDrawingShape = false;
google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
var lastDrawnShape = e.overlay;
if (cancelDrawingShape) {
cancelDrawingShape = false;
lastDrawnShape.setMap(null); // Remove drawn but unwanted shape
return;
}
// Else, do other stuff with lastDrawnShape
});
$(document).keydown(function (event) {
if (event.keyCode === 27) { // Escape key pressed
cancelDrawingShape = true;
drawingManager.setDrawingMode(null); // To terminate the drawing, will result in autoclosing of the shape being drawn.
}
});
(Assuming you use the google.maps.drawing.DrawingManager)
The current overlay is not accessible in any manner. What you can do:
On rightclick set the map-property of the DrawingManager-instance to null and create a new DrawingManager-instance.
This will remove the currently edited overlay(but not the already finished overlays)
For Angular 7, This worked for me.
cancelDrawingShape: boolean = false; // declaration of variable
#HostListener('document:keydown', ['$event']) onKeydownHandler(event: KeyboardEvent) {
if (event.key === "Escape" || event.key === "Esc") {
this.cancelDrawingShape = true;
this.drawingManager.setDrawingMode(null); // To terminate the drawing, will result in autoclosing of the shape being drawn.
}
}
google.maps.event.addListener(this.drawingManager,"polygoncomplete",
polygon => {
if(this.cancelDrawingShape){
this.selectedShape = polygon;
this.deleteSelectedShape();
this.cancelDrawingShape = false;
return false;
}else{
// else part
}});

YUI Library - Best way to keep global reference to object?

I'm trying to use the yahoo ui history library. I don't see a great way to avoid wrapping all my function contents with the Y.use so that I can get access to the history object. I tried declaring it globally outside of the use() command, but this didn't seem to work. If you look at my showDashboard() and showReport1() methods, you can see I'm wrapping the contents, which seems redundant to have to do this for every function that uses the history. Is there a better way to do this?
All of the yahoo examples I've seen don't se functions at all and keep the entire sample inside a single use method.
<div>
Dashboard |
Report 1
</div>
<script type="text/javascript">
// Global reference to Yahoo UI object
var Y = YUI();
function showDashboard() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
function showReport1() {
Y.use('*', function (Y) {
var history = new Y.HistoryHash();
history.addValue('report', "report1");
//var x = { 'report': 'report1', 'date': '11/12/2012' };
//history.addValue("report", x);
});
}
Y.use('history', 'tabview', function (Y) {
var history = new Y.HistoryHash();
var tabview = new Y.TabView({ srcNode: '#demo' });
// Render the TabView widget to turn the static markup into an
// interactive TabView.
tabview.render();
// Set the selected report to the bookmarked history state, or to
// the first report if there's no bookmarked state.
tabview.selectChild(history.get('report') || 0);
// Store a new history state when the user selects a report.
tabview.after('selectionChange', function (e) {
// If the new tab index is greater than 0, set the "tab"
// state value to the index. Otherwise, remove the "tab"
// state value by setting it to null (this reverts to the
// default state of selecting the first tab).
history.addValue('report', e.newVal.get('index') || 0);
});
// Listen for history changes from back/forward navigation or
// URL changes, and update the report selection when necessary.
Y.on('history:change', function (e) {
// Ignore changes we make ourselves, since we don't need
// to update the selection state for those. We're only
// interested in outside changes, such as the ones generated
// when the user clicks the browser's back or forward buttons.
if (e.src === Y.HistoryHash.SRC_HASH) {
if (e.changed.report) {
// The new state contains a different report selection, so
// change the selected report.
tabview.selectChild(e.changed.report.newVal);
} else if (e.removed.report) {
// The report selection was removed in the new state, so
// select the first report by default.
tabview.selectChild(0);
}
}
if (e.changed.report) {
alert("New value: " + e.changed.report.newVal);
alert("Old value: " + e.changed.report.prevVal);
}
});
});
</script>
</form>
</body>
</html>
Instead of using plain function on click, attach handlers with YUI.
If you can change the HTML code - add id or class to the links, for example
<a id="btnShowDashboard" href="#">Dashboard</a>
Then in your use() add click handler to the buttons
Y.use('history', 'tabview', 'node', 'event', function (Y) {
var bntShowDashboard = Y.one('#btnShowDashboard');
if (bntShowDashboard) {
bntShowDashboard.on('click', function(e) {
e.preventDefault();
var history = new Y.HistoryHash();
history.addValue("report", "dashboard");
});
}
...
})
That way you will be sure than on the moment of execution "history" is loaded.
BUT there is one drawback - until YUI modules are loaded, if you click the links nothing will happen.

Webkit transitionEnd event grouping

I have a HTML element to which I have attached a webkitTransitionEnd event.
function transEnd(event) {
alert( "Finished transition!" );
}
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
Then I proceed to change its CSS left and top properties like:
node.style.left = '400px';
node.style.top = '400px';
This causes the DIV to move smoothly to the new position. But, when it finishes, 2 alert boxes show up, while I was expecting just one at the end of the animation. When I changed just the CSS left property, I get one alert box - so this means that the two changes to the style are being registered as two separate events. I want to specify them as one event, how do I do that?
I can't use a CSS class to apply both the styles at the same time because the left and top CSS properties are variables which I will only know at run time.
Check the propertyName event:
function transEnd(event) {
if (event.propertyName === "left") {
alert( "Finished transition!" );
}
}
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
That way, it will only fire when the "left" property is finished. This would probably work best if both properties are set to the same duration and delay. Also, this will work if you change only "left", or both, but not if you change only "top".
Alternatively, you could use some timer trickery:
var transEnd = function anon(event) {
if (!anon.delay) {
anon.delay = true;
clearTimeout(anon.timer);
anon.timer = setTimeout(function () {
anon.delay = false;
}, 100);
alert( "Finished transition!" );
}
};
var node = document.getElementById('node');
node.addEventListener( 'webkitTransitionEnd', transEnd, false );
This should ensure that your code will run at most 1 time every 100ms. You can change the setTimeout delay to suit your needs.
just remove the event:
var transEnd = function(event) {
event.target.removeEventListener("webkitTransitionEnd",transEnd);
};
it will fire for the first property and not for the others.
If you prefer it in JQuery, try this out.
Note there is an event param to store the event object and use within the corresponding function.
$("#divId").bind('oTransitionEnd transitionEnd webkitTransitionEnd', event, function() {
alert(event.propertyName)
});
from my point of view the expected behaviour of the code would be to
trigger an alert only when the last transition has completed
support transitions on any property
support 1, 2, many transitions seamlessly
Lately I've been working on something similar for a page transition manager driven by CSS timings.
This is the idea
// Returs the computed value of a CSS property on a DOM element
// el: DOM element
// styleName: CSS property name
function getStyleValue(el, styleName) {
// Not cross browser!
return window.getComputedStyle(el, null).getPropertyValue(styleName);
}
// The DOM element
var el = document.getElementById('el');
// Applies the transition
el.className = 'transition';
// Retrieves the number of transitions applied to the element
var transitionProperties = getStyleValue(el, '-webkit-transition-property');
var transitionCount = transitionProperties.split(',').length;
// Listener for the transitionEnd event
function eventListener(e) {
if (--transitionCount === 0) {
alert('Transition ended!');
el.removeEventListener('webkitTransitionEnd', eventListener);
}
}
el.addEventListener('webkitTransitionEnd', eventListener, false);
You can test here this implementation or the (easier) jQuery version, both working on Webkit only
If you are using webkit I assume you are mobilizing a web-application for cross platform access.
If so have you considered abstracting the cross platform access at the web-app presentation layer ?
Webkit does not provide native look-and-feel on mobile devices but this is where a new technology can help.

Get DOM Element of a marker in Google Maps API 3

I'm trying to find a way to get the DOM element of a marker. It seems that Google uses different elements for showing a marker and one of handling the event.
I've figured out 2 things so far.
There's a generated variable called fb that holds the DOM element on a marker object, but I think the next time Google updates the API, it will be named something different. So no go.
Second, if one attach a click event to a marker the API send the event DOM element as arguments to what ever function you have specified. While looking through it in Firebug I can't find any relations between the two.
What I'm trying to accomplish is to manipulate the DOM element() and feed it more information then just a 'div' element with a background.
I've done this in version 2 using a name property(undocumented) that generates an id on the div element.
Does anyone have an idea?
I've found this method to be useful, although it might not be suitable for all environments.
When setting the marker image, add a unique anchor to the URL.
For example:
// Create the icon
var icon = new google.maps.MarkerImage(
"data/ui/marker.png",
new google.maps.Size(64, 64),
new google.maps.Point(0,0),
new google.maps.Point(48, 32)
);
// Determine a unique id somehow, perhaps from your data
var markerId = Math.floor(Math.random() * 1000000);
icon.url += "#" + markerId;
// Set up options for the marker
var marker = new google.maps.Marker({
map: map,
optimized: false,
icon: icon,
id: markerId,
uniqueSrc: icon.url
});
Now you have a unique selector i.e.:
$("img[src='data/ui/marker.png#619299']")
or if you have the marker:
$("img[src='" + marker.uniqueSrc + "']")
I was looking for the DOM element too in order to implement a custom tooltip.
After a while digging into google overlays, custom libraries and so on, i've ended up with the following solution based on fredrik's title approach (using jQuery) :
google.maps.event.addListener(marker, 'mouseover', function() {
if (!this.hovercardInitialized) {
var markerInDOM = $('div[title="' + this.title + '"]').get(0);
// do whatever you want with the DOM element
this.hovercardInitialized = true;
}
});
Hope this helps someone.
I found a really really bad workaround. One can use the title attribute to pass a id property.
fixMarkerId = function () {
$('div[title^="mtg_"]').each(function (index, elem) {
el = $(elem);
el.attr('id', el.attr('title'));
el.removeAttr('title');
});
},
tryAgainFixMarkerId = function () {
if ($('div[title^="mtg_"]').length) {
fixMarkerId();
} else {
setTimeout(function () {
tryAgainFixMarkerId();
}, 100);
};
}
if ($('div[title^="mtg_"]').length) {
fixMarkerId();
} else {
setTimeout(function () {
tryAgainFixMarkerId();
}, 100);
};
I would strongly not recommend this solution for any production environment. But for now I use it so that I can keep developing. But still looking for a better solution.
Customize overlays (by implements onAdd, draw, remove) while drag it has some issues.
Maybe you can get the marker dom element by the class name gmnoprint and the index it has been appended in.
I have checked that google maps marker click event has a MosueEvent property in it and the target of the mouse event is the dom element of the marker.
It worked for me until i found out that the property name has changed from tb to rb. I did not know why and I could not find an explaination for that in the google maps api docs but I have created a work around.
I check the click event object properties for the one which is an instance of MouseEvent and I use its target as the marker dom element.
marker.addListener('click', (e) => {
let markerElement;
// with underscore
_.toArray(markerClickEvent).some(item => {
if (item instanceof MouseEvent) {
markerElement = item.target;
return true;
}
});
// or maybe with for loop
for (let key in markerClickEvent) {
if (markerClickEvent.hasOwnProperty(key) && markerClickEvent[key] instanceof MouseEvent) {
markerElement = markerClickEvent[key].target;
break;
}
}
});

Categories

Resources