Top level mouse event handler - javascript

Hm. Maybe Is that I can't figure out the correct search keywords, since I'm new in some areas of javascript.
I am making a two.js based interactive game that comprises dragging stuff around. I need to detect when a mouse is released regardless the elements that are (or aren't) over the canvas. The elements (which are SVG canvas elements) capture the mouse events and prevent the canvas mouse event detection.
Mouse events:
$("#canvas").on("mousemove", function(e){
//do stuff
}).on("mousedown",function(){
//do stuff
}).on("mouseup",function(){
//do stuff
})
So, I can either address a event handler to an object, which will happen only within that object, or address it to the canvas, which will only happen when there is no object over. Appending to both will be unelegant, and will need a major re estructuration in the code that is huge (and I admit, messy);
Example elements that steal the mouse events in the inspector
I hope not to be re-asking. I have looked up and tried for some hours. Thanks!

Ok. So I tried an approach trying to think more javascript wise. Mainly I come from the other OOP school of thought; which is why is hard to unlock my understanding of prototypical objects.
I made a global function named pointer, and other named draggable, which goes in the prototype of each object to be dragged. On this function and over any object over which things could be dragged (the canvas), I attached the functions onRelease and release. When the mouse button is released, it runs its release function, which triggers a pointer.release(). The pointer will have an array of any object being dragged, so when it receives the release, it will trigger the onRelease of each dragged element, passing the object under the mouse. So the object being dragged, at the end receives the event handling object (the one upon which mouse was released).
I hope this method doesn't produce any face-palm. If so, I'd like to learn how it was better.
var pointer={
pos:{x:0,y:0},
dragging:false,
//who will be the object that detected the mouse released. see below
mouseUp:function(who){
for(drg in this.dragging){
//look if the -being-dragged- object does have the function
if(typeof(this.dragging.onRelease)=="function")
this.dragging.onRelease(who);
this.dragging=false;
}
}
}
This is the draggable class. The last part is the only concerning one, the rest is for context purpose:
function Draggable(){
this.main=this;
if(!this.pos)
this.pos={x:0,y:0};
this.dragging=false;
this.hover=false;
this.sprite=false;
this.move=function(pos){
this.sprite.translation.x=pos.x;
this.sprite.translation.y=pos.y;
this.pos=pos;
this.moving();
}
this.moving=function(){}
this.release=function(){
pointer.mouseUp(this);
}
this.init=function(){
//fallback sprite is a circle
if(!this.sprite)
this.sprite=two.makeCircle(0,0,this.rad);
this.move(this.pos);
two.update();
this.elem=$(domElem(this.sprite));
//attach a this to the dom element to make back-scope
this.elem[0].main=this;
//append jquery functions to this object's dom sprite
//pendiente: this may need to go in the pointer object, isn't it?
this.elem.on("mouseenter",function(){
this.main.sprite.fill="rgba(127,127,255,0.7)";
this.main.hover=true;
}).on("mouseleave",function(){
this.main.sprite.fill="rgba(127,127,255,0.3)";
this.main.hover=false;
//avoid pieces stuck to mouse. should this be?
/*this.main.dragging=false;
this.main.release();
pointer.dragging=false;*/
}).on("mousedown",function(){
this.main.dragging=this.main.hover;
pointer.dragging=this.main;
}).on("mouseup",function(){
//the important part is here: it triggers an pointer.mouseUp
this.main.dragging=false;
this.main.release();
pointer.mouseUp(this);
});
}
};
And so the node which will be prototype-blended with the draggable
function Node(ind){
//just to make more console-friendly
this.name="node"+ind;
this.pos=a;
this.ind=ind;
this.par=par;
this.broc;
this.sprite=two.makeCircle(0,0,brocSize*1.2);
this.sprite.addTo(layer[2]);
this.$elem=$(domElem(this.sprite));
main=this;
//these are triggered by their draggable bit
//who, is the subject over which mouse was released
this.onRelease=function(who){
//if(typeof(who)=="Broc")
console.log(who);
this.move(this.broc.pos);
console.log("this:");
console.log(this);
}
//pendant: make the abspos function once an unpack
this.son=false;
this.active=false;
};
This implements a bit of jQuery, which may later be replaced by plain javascript, for prototype and learn purposes I feel more comfortable with jQuery.

Related

How to convert Phaser keyboard event into touch event

I am following a Fruit Ninja tutorial using Phaser. For the main menu, the author has used
if (this.game_state.game.input.keyboard.isDown(Phaser.Keyboard.SPACEBAR)) {
this.menu_items[this.current_item_index].select();
How would I implement that same logic using touch events so I can use it on my tablet/phone? The rest of the swipe logic is using touch, except for the menu. I was wondering how I could change that.
This looks to me to be more than a just simple "change this to that". The code snippet you've posted makes it look to me that the menu keeps some kind of a current index of which menu item is highlighted (maybe using the arrow keys?).
If your menu is composed of sprites for example, you can attach a touch event to each sprite. Say you have a sprite called exitGameMenuItem in the menu. You need to enable its input and attach an onInputDown event callback to it (and it will work with both mouse and touch).
exitGameMenuItem.inputEnabled = true;
exitGameMenuItem.events.onInputDown.add(function() {
// call the exit game function here
}, this);
Alternatively you can add the callback directly (without wrapping it in a function) like this:
exitGameMenuItem.events.onInputDown.add(this.exitGame, this);
(This assumes that you have an exitGame() function in the same state).

How to prevent duplicate event listeners upon appendChild?

I've been trying to create a scene that allows a user to switch between cameras upon clicking a "swap button" entity. I created a smaller CodePen demo to present the setup. Clicking on the swap entity (the sphere, here) will move the cursor between the old and the newly activated camera entities. When it does this, it has to call the init handler again. However, I'm having difficulty preventing this from causing an dogpile of event listeners on the cursor.
For the demo, the print-onenter component logs whether the cursor's over the a-sphere/box/cylinder/plane. Yet every additional camera swap by clicking on the sphere results in an additional print-out between mouseenter events (and you have to click it twice after, presumably because the clicks to swap will be undone by the additional click--but in the process add a different even/odd amount of listeners enabling you to actually swap next [set of] click event). I tried to apply a .removeEventListener, and to move print-onenter's function handler into a global scope so .removeEventListener would see it (since I assume anon handlers are effectively treated as unique types and therefore not treated as a duplicate listener and ignored).
function listenerTest (event) {
console.log("Object Entered: " + event.detail.intersectedEl.nodeName);
};
AFRAME.registerComponent( 'print-onenter', {
init: function() {
console.log('Reinitialized print-onenter.');
this.el.addEventListener( 'mouseenter', listenerTest );
},
remove: function() {
console.log('Deinitialized print-onenter.');
this.el.removeEventListener( 'mouseenter', listenerTest );
}
} );
Nevertheless, the same problem persists. Any help would be greatly appreciated.
Please see ngokevin's suggestion in the comments to the question above for a better way to handle this without reparenting entities.
Workaround: You could have two cursor enitites, one that has the cursor and one that doesn't. When you flip, then removeAttribute one cursor on one entity, and setAttribute another cursor on the other.

Triggering mousemove on the mouse's current position

Suppose we have a <div> with a mousemove handler bound to it. If the mouse pointer enters and moves around this div, the event is triggered.
However, I am dealing with a rich web application where <div>s move around the screen, appear and disappear... So it may happen that a <div> appears under the mouse pointer. In this case, mousemove is not triggered. However, I need it to be. (Note that replacing mousemove with mouseover does not change this behavior.)
Specifically, the <div> has to be highlighted and I deem it as a UI flaw to require the user to do a slight mouse move in order to trigger the highlighting.
Is it possible to trigger the mousemove event programatically? And I do not mean
document.getElementById('mydiv').onmousemove();
because onmousemove is parametrised by the event object, which I do not have.
Is it possible to make browser behave as if onmousemove was triggered on the current mouse's position (although in fact the mouse didn't move)?
You could modify your mousemove to keep a state variable with the current mouse coordinates, and use that information to perform a collision detection that you call both on mouse move, and on moving a div.
A little example of what that might look like
You actually can create a mousemove event object to pass in, using something like this:
window.onload = function () {
document.getElementById("test").onmousemove = function(e) { console.log(e); };
document.getElementById("test").onclick = function(e) {
var e = document.createEvent('MouseEvents');
e.initMouseEvent('mousemove',true,true,document.defaultView,<detail>,<screenX>,<screenY>,<mouseX>,<mouseY>,false,false,false,false,<button>,null);
this.onmousemove(e);
};
};
Of course, here I'm firing it on a click, but you can do it on whatever event you want, such as when your div becomes visible, check to see if the mouse is within it. You just need to make sure your parameters are right, and you need to track the mouse position on your own. Also, there's some differences in IE, I think. Here's my source: http://chamnapchhorn.blogspot.com/2008/06/artificial-mouse-events-in-javascript.html. He added a little extra code to account for it.
Here's a fiddle to play around with. http://jsfiddle.net/grimertop90/LxT7V/1/

Determine what is being dragged from dragenter & dragover events

I'm trying to use the HTML5 draggable API (though I realize it has its problems). So far, the only showstopper I've encountered is that I can't figure out a way to determine what is being dragged when a dragover or dragenter event fires:
el.addEventListener('dragenter', function(e) {
// what is the draggable element?
});
I realize I could assume that it's the last element to fire a dragstart event, but... multitouch. I've also tried using e.dataTransfer.setData from the dragstart to attach a unique identifier, but apparently that data is inaccessible from dragover/dragenter:
This data will only be available once a drop occurs during the drop event.
So, any ideas?
Update: As of this writing, HTML5 drag-and-drop does not appear to be implemented in any major mobile browser, making the point about multitouch moot in practice. However, I'd like a solution that's guaranteed to work across any implementation of the spec, which does not appear to preclude multiple elements from being dragged simultaneously.
I've posted a working solution below, but it's an ugly hack. I'm still hoping for a better answer.
I wanted to add a very clear answer here so that it was obvious to everyone who wanders past here. It's been said several times in other answers, but here it is, as clear as I can make it:
dragover DOES NOT HAVE THE RIGHTS to see the data in the drag event.
This information is only available during the DRAG_START and DRAG_END (drop).
The issue is it's not obvious at all and maddening until you happen to read deeply enough on the spec or places like here.
WORK-AROUND:
As a possible work-around I have added special keys to the DataTransfer object and tested those. For example, to improve efficiency I wanted to look up some "drop target" rules when my drag started instead of every time a "drag over" occurred. To do this I added keys identifying each rule onto the dataTransfer object and tested those with "contains".
ev.originalEvent.dataTransfer.types.includes("allow_drop_in_non_folders")
And things like that. To be clear, that "includes" is not a magic bullet and can become a performance concern itself. Take care to understand your usage and scenarios.
The short answer to my question turns out to be: No. The WHATWG spec doesn't provide a reference to the element being dragged (called the "source node" in the spec) in the dragenter, dragover, or dragleave events.
Why not? Two reasons:
First, as Jeffery points out in his comment, the WHATWG spec is based on IE5+'s implementation of drag-and-drop, which predated multi-touch devices. (As of this writing, no major multi-touch browser implements HTML drag-and-drop.) In a "single-touch" context, it's easy to store a global reference to the current dragged element on dragstart.
Second, HTML drag-and-drop allows you to drag elements across multiple documents. This is awesome, but it also means that providing a reference to the element being dragged in every dragenter, dragover, or dragleave event wouldn't make sense; you can't reference an element in a different document. It's a strength of the API that those events work the same way whether the drag originated in the same document or a different one.
But the inability to provide serialized information to all drag events, except through dataTransfer.types (as described in my working solution answer), is a glaring omission in the API. I've submitted a proposal for public data in drag events to the WHATWG, and I hope you'll express your support.
A (very inelegant) solution is to store a selector as a type of data in the dataTransfer object. Here is an example: http://jsfiddle.net/TrevorBurnham/eKHap/
The active lines here are
e.dataTransfer.setData('text/html', 'foo');
e.dataTransfer.setData('draggable', '');
Then in the dragover and dragenter events, e.dataTransfer.types contains the string 'draggable', which is the ID needed to determine which element is being dragged. (Note that browsers apparently require data to be set for a recognized MIME type like text/html as well in order for this to work. Tested in Chrome and Firefox.)
It's an ugly, ugly hack, and if someone can give me a better solution, I'll happily grant them the bounty.
Update: One caveat worth adding is that, in addition to being inelegant, the spec states that all data types will be converted to lower-case ASCII. So be warned that selectors involving capital letters or unicode will break. Jeffery's solution sidesteps this issue.
Given the current spec, I don't think there is any solution that isn't a "hack". Petitioning the WHATWG is one way to get this fixed :-)
Expanding on the "(very inelegant) solution" (demo):
Create a global hash of all elements currently being dragged:
var dragging = {};
In the dragstart handler, assign a drag ID to the element (if it doesn't have one already), add the element to the global hash, then add the drag ID as a data type:
var dragId = this.dragId;
if (!dragId) {
dragId = this.dragId = (Math.random() + '').replace(/\D/g, '');
}
dragging[dragId] = this;
e.dataTransfer.setData('text/html', dragId);
e.dataTransfer.setData('dnd/' + dragId, dragId);
In the dragenter handler, find the drag ID among the data types and retrieve the original element from the global hash:
var types = e.dataTransfer.types, l = types.length, i = 0, match, el;
for ( ; i < l; i++) {
match = /^dnd\/(\w+)$/.exec(types[i].toLowerCase());
if (match) {
el = dragging[match[1]];
// do something with el
}
}
If you keep the dragging hash private to your own code, third-party code would not be able to find the original element, even though they can access the drag ID.
This assumes that each element can only be dragged once; with multi-touch I suppose it would be possible to drag the same element multiple times using different fingers...
Update: To allow for multiple drags on the same element, we can include a drag count in the global hash: http://jsfiddle.net/jefferyto/eKHap/2/
To check if it is a file use:
e.originalEvent.dataTransfer.items[0].kind
To check the type use:
e.originalEvent.dataTransfer.items[0].type
i.e. I want to allow only one single file jpg, png, gif, bmp
var items = e.originalEvent.dataTransfer.items;
var allowedTypes = ["image/jpg", "image/png", "image/gif", "image/bmp"];
if (items.length > 1 || items["0"].kind != "file" || items["0"].type == "" || allowedTypes.indexOf(items["0"].type) == -1) {
//Type not allowed
}
Reference: https://developer.mozilla.org/it/docs/Web/API/DataTransferItem
You can determine what is being dragged when the drag starts and save this in a variable to use when the dragover/dragenter events are fired:
var draggedElement = null;
function drag(event) {
draggedElement = event.srcElement || event.target;
};
function dragEnter(event) {
// use the dragged element here...
};
In the drag event, copy event.x and event.y to an object and set it as the value of an expando property on the dragged element.
function drag(e) {
this.draggingAt = { x: e.x, y: e.y };
}
In the dragenter and dragleave events find the element whose expando property value matches the event.x and event.y of the current event.
function dragEnter(e) {
var draggedElement = dragging.filter(function(el) {
return el.draggingAt.x == e.x && el.draggingAt.y == e.y;
})[0];
}
To reduce the number of elements you need to look at, you can keep track of elements by adding them to an array or assigning a class in the dragstart event, and undoing that in the dragend event.
var dragging = [];
function dragStart(e) {
e.dataTransfer.setData('text/html', '');
dragging.push(this);
}
function dragEnd(e) {
dragging.splice(dragging.indexOf(this), 1);
}
http://jsfiddle.net/gilly3/4bVhL/
Now, in theory this should work. However, I don't know how to enable dragging for a touch device, so I wasn't able to test it. This link is mobile formatted, but touch and slide didn't cause dragging to start on my android. http://fiddle.jshell.net/gilly3/4bVhL/1/show/
Edit: From what I've read, it doesn't look like HTML5 draggable is supported on any touch devices. Are you able to get draggable working on any touch devices? If not, multi-touch wouldn't be an issue and you can resort to just storing the dragged element in a variable.
From what I have read on MDN, what you are doing is correct.
MDN lists some recommended drag types, such as text/html, but if none are suitable then just store the id as text using the 'text/html' type, or create your own type, such as 'application/node-id'.
I think you can get it by calling e.relatedTarget See: http://help.dottoro.com/ljogqtqm.php
OK, I tried e.target.previousElementSibling and it works, sorta.... http://jsfiddle.net/Gz8Qw/4/ I think it hangs up because the event is being fired twice. Once for the div and once for the text node (when it fires for text node, it is undefined). Not sure if that will get you where you want to be or not...

Duplicate events in prototype

I have a setup where I have a grid of elements and when I rollover each element a popup appears like an advanced tooltip. I first check if the popup needs to follow the moused over elements mouse position, if so I use a mousemove event. I first stopObserving in case there was one set before, then I start observing. Do I really need to do this or is prototype smart enough to not to add duplicate events on the same element.
show:function(param){
if(this.isFollow){
$(param.target).stopObserving('mousemove', this.onMouseMove);
$(param.target).observe('mousemove', this.onMouseMove);
}
},
//param.target is the element that is being rolled over. I pass this in to my show method to then find its x and y position.
onMouseMove:function(event){
var xPos = Event.pointerX(event);
var yPos = Event.pointerY(event);
_self._popup.setStyle({left: xPos + 10 + "px", top:yPos + 10 + "px"});
}
Second question. When I move my mouse across the elements really fast my popup that is following the mouse sometimes lags and the mouse goes over the popup obstructing the mouseover event on the element below it.
I presume this is the nature of the mousemove as its not rendering fast enough. Should I be using setTimeout or something like that instead of mousemove, to prevent this lag.
1) No, Prototype won't set the same event handler twice. It'll only happen if you declare your handler function in-line (i.e. element.observe('click', function(){…})) since the handler will be sent a newly created function each time, and never the exact same instance of a function.
But in your case, where you're referring to the onMouseMove function, Prototype will check whether that particular function is already registered for that particular event, on that particular element. And if it is, it won't be registered again.
2) You can't avoid the lag on fast mouse movements, no. The browser won't send the mousemove events fast enough. You could use a timer, but I'd probably try registering a single mousemove handler for the parent element of all the grid-elements (or maybe even document itself), and use the X/Y coordinates to figure out which grid-element to show the tooltip for. Then you don't have to bother with setting event handlers for each element. I.e. if the grid was a standard table, I'd listen for events on the <table> element itself, rather than on each and every <td>. Especially if you still want to implement a timer, I should think, it'd be easier to deal with everything in one place (otherwise, a timer might accidentally execute on some element you've already moused out of, and your tooltip will flicker back and forth or something. If you only want 1 tooltip at a time, it's easier to manage it in 1 place.)

Categories

Resources