What is the best way to distinguish events in JavaScript.
Actually there are two points I am interested in. The first one is are there something like id in event (it would be very useful foe debugging purposes). And another point are there better ways to distinguish mousedown and mousedown&touchstart events.
Let me tell you my story. I met the problem that if you add two dom events to a node with triggers mousedown and touchstart, then on mobile devices both mousedown and touchstart run.
The first solution I found was to run
event.preventDefault();
event.stopPropagation();
at the beginning of each listener function.
But then I found out an event delegation pattern and started to work with analytics, all that disallowed to use the previous approach, so I come up with the following solution:
let lastEvent = null;
const specificListener = function(e) {
if (lastEvent === e) {
return; //already run the code due to another listener
}
/*
logic goes here
*/
lastEvent = e;
};
And now I am interested whether or not it is possible to compare events in a different way (different from event1 === event2, hope to find out about something like event1.id === event2.id)?
Thank you.
Instead of trying to differentiate the events, just subscribe to mousedown only, as it is fired anyway. That's the most simple solution I'd say.
Or, you could try to detect which event system is supported and only subscribe to the appropriate one:
var eventType = document.ontouchstart != null ? 'touchstart' : 'mousedown';
document.addEventListener(eventType, (e) => { ... });
A third solution (possibly) would be to use PointerEvents only, but that depends on the platforms you need to support.
https://caniuse.com/#search=pointer%20events
If you for sure cannot use one of these approaches: Every event should have a timestamp property (not sure if it is named that way), maybe you can find a way to distinguish two events with it.
Related
What I want is, when an element receive an event, I want it to do "something" first, that "something" could be is to evaluate whether to call the registered event listener or not.
I able to override manually dispatching event through calling .dispatchEvent(event) method but not event sent/dispatch by browser e.g. user click on a button.
Here my attempt:
let evtDispatcher = EventTarget.prototype.dispatchEvent;
let foo = false;
EventTarget.prototype.dispatchEvent = function(evt) {
if (foo) {
console.log('Event Sent');
evtDispatcher.call(this, evt);
} else {
console.log('No Event Sent');
}
}
function displayMe(evt) {
document.getElementById('span-id').innerHTML = 'Clicked';
}
let bttn = document.getElementById('bttn-id');
bttn.addEventListener('click', displayMe);
//When user click the "button" tag
// Desire result: don't call the event listener and log "No Event Sent" on console
// Actual result: event listener is called and "Event Sent" is log on console
bttn.dispatchEvent(new Event('click'));
//manual dispatch works
Here's my attempt code on JsFiddle
The answer is "sometimes".
I wouldn't recommend trying to override dispatchEvent() or any of the other native methods, because, it won't work for a lot of things. Much of the event handling stuff happens at a level below where you can access with JavaScript.
Instead, you can try to implement it making use of event capture.
If you provide true to the useCapture parameter or capture option of an event, you'll basically give that element priority over others that aren't using capture. If you do this on html or body at the top-level, they'll get a chance to look at, and potentially stopPropagation() of the event, based on whatever arbitrary logic you want.
You can see that happening with this little sample.
const allow = document.querySelector('input');
const div = document.querySelector('div');
const span = document.querySelector('span');
const button = document.querySelector('button');
div.addEventListener('click', event => {
console.log('div');
if (!allow.checked) {
event.stopPropagation();
}
}, { capture: true });
span.addEventListener('click', () => console.log('span'));
button.addEventListener('click', () => console.log('p'));
<label>Allow through? <input type="checkbox"></label>
<div>
<span>
<button>Click Me</button>
</span>
</div>
So, if you do this for every possible event, you can usually do what you want. There are some exceptions though, hence the "sometimes" above, which aren't super well defined and are done by browsers to try to prevent fraud and hacks and whatnot. I don't know of a single comprehensive list of where you can't override, but if you don't truly need EVERY event to do this, it might not matter.
There also isn't a wildcard event, so you'll have to hook it up to each event, but there are ways to implement a wildcard event yourself.
One final note, since you mentioned user agent. If you are trying to do this for any security purposes: don't. Or, at the very least, ensure you also still have full protections on the back-end. Any "security" on the front-end is just security theater and easily bypassed by someone with enough knowledge, no matter how well implemented.
Here is my code:
var e = jQuery.Event( "keydown", { keyCode: 64 } );
There is data being attached to the event in the second parameter. Now I have seem that kind of syntax in a lot of plugins and was wondering, what's the use of attaching arbitrary data to a $.event/custom event?
I have seen the jQuery docs event object and also trigger.
I am wondering, if the only usage of attaching data to a event is as follows:
var e = jQuery.Event( "keydown", { keyCode: 64 } );
// above is the line I am having difficulty understanding
// the usage of arbitrary data with the $.event is quite
// elusive to a novice developer like me
$(window).on('keydown' , function(e){
console.log('key 64 pressed');
});
press = function(){
$(window).trigger(e);
}
setTimeout(function(){
press();
}, 2000);
JS Fiddle here
I.e. triggering an event on a specific key or a specific element, I mean is this the only use of attaching arbitrary data to an $.event?
Sometimes you are using your code that behaves in some way depending on the interaction.
In your example, you are "manually" triggering the keydown event on that input in order to "trigger" the event-listeners listening to this event.
Probably on this example you have a idle logout system going on and you activated something to keep the session alive on your UI as long as needed.
The other day I had to use something similar in order to keep a dropdown open in a third party plugin that was closing a drop down on selection after a search.
My requirements
Because of the asynchronous architecture of my applications I am looking for an 'event' system which has the following two two properties:
The events should be able to fire multiple times (possible with events, but not with promises)
When I start listening for an event that has already been fired, I want the listener to fire once immediately (as with promises)
The reason for 1. is that there are a lot of events (e.g. the updating of certain data) that I want to be able to fire multiple times. But I would like to combine this with 2. so that if an event has already fired upon adding the listener, this listener gets called immediately. This is because I'm not always sure (and I don't want to be sure) which piece of code gets run first.
My 'solution'
I have thought up the following solution. I'm using this in an AngularJS application therefore the AngularJS context, but the question is applicable for Javascript in general. Note that I simplified the code.
app.controller('AppCtrl', function(CustomEventEmitter){
// Broadcast an event. No listener added so nothing happens
CustomEventEmitter.broadcast('event');
// Add the event listener. Because the event allready fired, the listener gets called immediatly
CustomEventEmitter.on('event', function(){
console.log('Event emitted');
});
// Broadcast an other event
CustomEventEmitter.broadcast('event');
});
app.service('CustomEventEmitter', function(){
var
listeners = {},
memory = [];
this.broadcast = function(name){
// The normal broadcasting of the event to the listener
if(listeners[name]) {
listeners[name].forEach(function(listener){
listener();
});
}
// Push the event into the 'memory'
memory.push(name);
};
this.on = function(name, listener){
// The normal adding of the listener
if(!listeners[name]) {
listeners[name] = [];
}
listeners[name].push(listener);
// If an event is already in memory, call the listener
if(memory.indexOf(name) !== -1) {
listener();
}
};
});
My questions
My questions are these:
What is the 'best practice' solution for my requirements?
What do you think of my 'solution'?
Am I missing something completely obvious?
The reason for the last question is that it seems to me that this is a very common design paradigm but I seem unable to find the best way to solve this in simple and concise way.
Note
I understand this can be solved with the adding of extra code (e.g. before adding the listener, check in an other way if the event you are going to listen for already happened) but this is not what I'm looking for.
A "property" from bacon.js does exactly what you are asking for. This falls under the broader category of functional reactive programming (FRP). The most popular two libraries for this in JavaScript are probably
bacon.js
Reactive Extensions
Both of which provide the specific tool you're asking for, along with a vast array of alternatives.
I currently am working on a bookmarklet that opens an iframe, and sets up a communication of postMessage back and forth. That all works fine.
However, seemingly because the bookmarklet is being loaded as an anonymous function, the listeners are multiplying if I run the bookmarklet more than once on a page.
Is there some sort of way to keep track of these addEventListeners so that they don't double-up?
Do I need to define the rp_receive_message outside of the anonymous function?
Here's an example of the code:
var rp_receive_message = function (e) {
var response = e.data;
console.log("got message with "+ response);
};
if (window.addEventListener) {
window.addEventListener('message', rp_receive_message, false);
} else {
window.attachEvent('onmessage', rp_receive_message);
}
var s1 = window.document.createElement('iframe');
s1.setAttribute('src', 'http://mydomain.com/iframe.html');
s1.setAttribute('id', 'testiframe');
s1.setAttribute('width', '700');
s1.setAttribute('height', '550');
s1.setAttribute('frameBorder', '0');
s1.setAttribute('onload', 'this.contentWindow.postMessage(window.location.href, "http://mydomain.com/iframe.html");');
document.getElementById('container').appendChild(s1);
Probably this will solve the problem:
window.onmessage = rp_receive_message;
As you suggest, the code below might be enough by itself. I don't know if addEventListener and attachEvent will add the same function multiple times, but I wouldn't at all be surprised if they will. I suggest just testing it.
window.rp_receive_message = function(){...}
If you dislike either solution, you've got to set up a global variable, which hardly seems any different or greatly superior to above. The global can be a simple boolean to check if the event has been attached, or it can be a list of attached events that you update yourself. AFAIK, and I'm pretty sure, there is no native JS solution to get a list of event listeners have been attached to a particular event. Libraries such as jQuery maintain lists and let you read them; and possibly have other techniques that are elegant solutions to your general problem.
I recently found myself in the situation that I needed to remove a function bound to the resize event of the window by WordPress' media manager (media-upload.js), because it was interfering with the proper use of Thickbox. The event is attached like this:
a(window).resize(function(){tb_position()})
It took me a while, but I finally found out I could do it in this way:
jQuery.each( jQuery(window).data('events')['resize'], function(i, event) {
var thisEvent = event.toString().replace(/\n/g, '').replace(/\t/g, '').split(' ').join('');
var expectedEvent = 'function(){tb_position()}';
if (thisEvent == expectedEvent)
delete jQuery(window).data(‘events’)[‘resize’][i];
})
Here I cycle through the events, removing spaces, tabs and new lines from them and compare them to what I'm looking for, and when I find it I throw it out of the goddamn airlock. It happens in this case that the attached function perhaps doesn't have spaces, tabs or new lines, but this way also works with more complicated functions as far as I can tell.
Is there an easier and/or more elegant way of doing this? Is this a recipe for disaster down the road?
When you register a handler for an event, you can use a qualifier:
$('#something').bind('click.removeMeSomeday', function() { ... });
Then when you need to remove it you can do so without bothering other handlers for "click".
Now, it occurs to me that you may not be able to affect the way that Wordpress binds its event handler.
Another way around might be to use WordPress' system for queueing/unqueueing or registering/deregistering scripts. Unregister media-upload.js, and then queue your own version of it.
http://codex.wordpress.org/Function_Reference/wp_enqueue_script
http://phpxref.ftwr.co.uk/wordpress/nav.html?wp-includes/functions.wp-scripts.php.source.html#l74
http://phpxref.ftwr.co.uk/wordpress/nav.html?wp-includes/functions.wp-scripts.php.source.html#l37
Prem