I'm trying to figure out when some custom events are firing. This site is designed with jQuery. So I want to add an event listener for all of the custom .trigger events. I tried:
$('*').on('*', function(e) {
console.log('custom event name that just triggered:', e.eventName, 'selector of element that it triggered on:', e.target);
});
This didn't work. I know if I watched all it would be bad perf, so that's why I'm trying to not watch the standard events, just the custom ones.
There's a way you can generate a list of all events added to your page, however I'd advise against doing it in production code. If you can avoid doing it altogether, that's superb, but basically you want to run the following snippet before loading any other JavaScript on your page.
var events = {};
var original = window.addEventListener;
window.addEventListener = function(type, listener, useCapture) {
events[type] = true;
return original(type, listener, useCapture);
};
This will give you an object whose keys are your triggers.
You can then use these keys to generate a stringy list of your events which you can pass to jQuery.
var list = Object.keys(events).join(" ");
$('*').on(list, function(e) {
console.log(
'custom event name that just triggered:', e.eventName,
'selector of element that it triggered on:', e.target);
});
Related
Giving you an example, we can do something like the following to make our listeners trigger only in the event capturing phase:
element.addEventListener(event, function, true);
Or,
element.addEventListener(event, function, {passive: true});
..to make the listener passive. But, these all settings are only limited to setting 'em up through JavaScript code. What if, we are adding a listener to a DOM element in our HTML/Template code like:
<element onevent="function">
How can we make all those settings on listeners in this case? These setting are desirable especially when we are using frameworks like React where we use to attach event handlers directly in our template only and almost never using element.addEventListener().
React provides you a way to use events in the capture phase by appending Capture at the end of event name.
As per the docs:
The event handlers are triggered by an event in the bubbling phase. To
register an event handler for the capture phase, append Capture to the
event name; for example, instead of using onClick, you would use
onClickCapture to handle the click event in the capture phase.
Directly in HTML it is not possible.
But using JavaScript this is very simple like follows:
You have to write all your event settings to HTML element attributes;
Then you can read all this attributes and create an event listener.
Snippet example
function myFunction()
{
console.log('it works!');
}
var myelements = document.querySelectorAll('myelement');
for(var i = myelements.length; i--;)
{
var eventName = myelements[i].getAttribute('event-name'),
eventFunction = myelements[i].getAttribute('event-function-name'),
eventSetting = myelements[i].getAttribute('event-setting');
eventFunction = window[eventFunction];
eventSetting = eventSetting.indexOf('{') == 0 ? JSON.parse(eventSetting) : !!eventSetting;
//if e.g. eventSetting == "true" then after !!eventSetting it will be a boolean true
myelements[i].addEventListener(eventName, eventFunction, eventSetting);
}
myelement
{
width:50px;
height:50px;
background:green;
display:block;
}
<myelement event-name="click"
event-function-name="myFunction"
event-setting='{"passive":true}'></myelement>
Is it ok to add a custom property to an existing DOM event? I would like to "mark" click events in a leaf element (say, a <span>) and catch the event (after bubbling) in an ancestor (say, <body>) and take a decision based on that mark.
Sure you can add new properties to an event.
If I understand you correctly you are looking for a way to uniquely identify a specific event so you can de-dupe or count unique events or whatever.
For instance if you have one event handler handling some events on different elements and you want to make sure you handle each event only once. If an event bubbles you might handle the same event on multiple elements. Some events might bubble, some might not (e.g. because another handler called Event.stopPropogation() lower in the dom tree. Maybe you don't have that much control over where you attach your event handlers and have to rely on bubbling in some cases.
Example...
<div id="parent">
<div id="child"></div>
</div>
var parentEl = document.querySelector('#parent'),
childEl = document.querySelector('#child');
child.addEventListener('click', function (e) {
var rand = ''+Math.floor(Math.random()*1000000);
console.log('child', rand);
e.custField = rand;
});
parent.addEventListener('click', function (e) {
console.log('parent', e);
});
child.click();
Downside of this is that you are polluting someone else's objects that might be handled by code that isn't your own. Not usually a great idea.
Alternatively
1) Use Event.timeStamp
Your event handler can keep a cache of Event.timeStamp and use that to de-dupe handled events.
var eventHandler = (function () {
var eventTimeStampCache = {};
return function (evt) {
if (evt.timeStamp in eventTimeStampCache) {
console.log('Ignore this event.', evt);
return;
}
eventTimeStampCache[evt.timeStamp] = true;
console.log('Handle this event.', evt);
};
})();
child.addEventListener('click', eventHandler);
parent.addEventListener('click', eventHandler);
child.click();
2) Use CustomEvent
If you are firing the events in the first place, use a CustomEvent that is all your own and feel free to make custom APIs and add event listeners that listen for your custom event.
I'd suggest firing a CustomEvent in response to the standard event except you have the same problem of knowing if this has been done already or not.
You can add a dataset item to the element called "data-xxx"
<span id="id" data-myvar="myvalue"></span>
And then later on
console.log( document.getElementbyID('id').dataset.myvar ) // myvalue
I want to remove the click of an element when it is clicked (I'm using off() for this), and want to reattach the click when the element isn't clicked ('actived'). To indicate it is not clicked, I'm using a class named 'disabled'.
But when I remove, I cant add it again. It just doesn't attach the event again!
This is what I'm trying to do:
$('.my-element').on('click', function() {
$('.my-element').off('click');
});
var disabled = setInterval(function() {
if($('.my-element').hasClass('not-clicked')) {
$('.my-element').on();
}
}, 1000);
I'm using setInterval() to watch whether the element isn't clicked. If it isn't, it can be using on() again.
I have even tried to remove the event handler in the browser console:
$('.my-element').off();
But when I try to reattach the event handler...
$('.my-element').on();
It doesn't work, and will not repeat the behavior.
jQuery.on() doesn't remember removed events, but you can save the current events before removing them with:
var handlers;
$('.my-element').on('click', function() {
handlers = $.extend(true, {}, $._data( this, "events" ));
$('.my-element').off('click');
});
and then attach with:
$(".my-element").on("click", handlers.click[0].handler);
Using on and off without any parameters has no effect. Ideally you should pass the name of the event and it's handler to those methods.
var handler = function() {
// ...
}
$('.my-element').on('click.namespace', handler);
// ...
$('.my-element').off('click.namespace', handler);
If the handler should be called only once you could also consider using the one method. Also using setIntevarl here is a bad idea. You should bind the handler right after adding the class. In this case I'd suggest using event delegation technique:
$('#aStaticParent').on('click', '.my-element.not-clicked', function() {
// the handler is called only if the element has `not-clicked` className
$(this).removeClass('not-clicked');
});
In jQuery, you can do the following:
$('#j_unoffered').on('click', '.icon_del', function () {...
This puts one handler on the element j_unoffered that fires if any descendant element with class icon_del is clicked. It applies, furthermore, to any subsequently created icon_del element.
I can get this working fine in Closure where the click is on the element itself.
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {...
How can I specify a parent event target in Closure that works for its children/descendants in the same way as the jQuery example?
I'm assuming I need to use setParentEventTarget somehow, but I'm not sure how to implement it for DOM events. Most of the documentation I've found pertains to custom dispatch events.
-- UPDATE --
I'm wondering if there is anything wrong with this rather simple solution:
goog.events.listen(
goog.dom.getElement('j_unoffered'),
goog.events.EventType.CLICK,
function(e) {
if (e.target.className.indexOf('icon_del') !== -1) {...
It still leaves this bound to the parent, but e.target allows a work-around. The fifth argument in listen (opt_handler) allows you to bind this to something else, so I guess that's an avenue, too.
I don't know about such possibility too, so I suggest other piece of code:
var addHandler = function(containerSelector, eventType, nestSelector, handler) {
var parent = goog.isString(containerSelector) ?
document.querySelector(containerSelector) :
containerSelector;
return goog.events.listen(
parent,
eventType,
function(e) {
var children = parent.querySelectorAll(nestSelector);
var needChild = goog.array.find(children, function(child) {
return goog.dom.contains(child, e.target);
});
if (needChild)
handler.call(needChild, e);
});
});
Usage:
addHandler('#elem', goog.events.EventType.CLICK, '.sub-class', function(e) {
console.log(e.target);
});
Update:
If you will use this e.target.className.indexOf('icon_del') there will be possibility to miss the right events. Consider a container div with id = container, it has couple of divs with class innerContainer, and each of them contains couple of divs with class = finalDiv. And consider you will add event handler with your code above, which will check e.target for innerContainer class. The problem is when user will click on finalDiv your handler will be called, but the event target will be finalDiv, which is not innerContainer, but contained by it. Your code will miss it, but it shouldn't. My code checks if e.target has nested class or contained by it, so you will not miss such events.
opt_handler can't really help you either, because there might be many nested elements you want to hanlde (which of them will you pass here? maybe all, but not that helpful, you can get them in event handler whenever you want), moreover they can be added dynamically after, so when you add handler you could not know about them.
In conclusion, I think doing such a job in an event handler is justified and most efficient.
What you are referring to is called event delegation
It seems that this is not possible (out of the box) with Google Closure Library; So my recommandation is to use jQuery or another similar event handling library that offers this functionality. If this is not possible or if you wanna do it by hand here's one possible approach (NOTE: this is not for production use)
var delegateClick = function(containerId, childrenClass, handler){
goog.events.listen(goog.dom.getElement(containerId), goog.events.EventType.CLICK, function(event){
var target = event.target;
//console.log(event);
while(target){
if ( target.className && target.className.split(" ").indexOf(childrenClass)!== -1) {
break;
}
target = target.parentNode;
}
if(target){
//handle event if still have target
handler.call(target, event);
}
});
}
//then use it, try this here: http://closure-library.googlecode.com/git/closure/goog/demos/index.html
//..select the nav context
delegateClick( 'demo-list' ,'goog-tree-icon', function(event){console.log(event);})
Here's a more in depth analysis of event delegation
Again, you should use a proven library for this, here are some options: jQuery, Zepto, Bean
I know how to bind multiple events and all that stuff. What I want to do is have multiple events occur to trigger a function.
Like
$(this).click and $(this).mousemove triggers a function
Is there a way to do this? Is it possible or am I just dreaming.
With a better understanding now, one thing you could do is have one event bind and unbind the other:
Example: http://jsfiddle.net/ZMeUv/
$(myselector).mousedown( function() {
$(document).mousemove(function() {
// do something
});
$(document).mouseup(function() {
$(this).unbind(); // unbind events from document
});
});
This prevents the mousemove from constantly firing when you have no need for it.
You can use jQuery's special events to package everything nicely and optimize things in the process. A mousedown and mousemove combo also commonly goes by the name "drag", so here's an example of creating a drag event that you can bind to elements. Note, that this code is specific to jQuery 1.4.2
One of the advantages to using this is that you only bind the mousemove, mouseout, and mousedown handlers once each element, no matter how many times that element is bound to the drag event. Now this isn't the most optimal way of doing it, and you can setup just 3 handlers on the document and manage everything with it, which is equally easy to do with the special events API. It just provides a nicely packaged way of building complex interactions than would be possible with just native events or custom events, in the jQuery sense.
$("..").bind("drag", function() {
...
});
I will try and add more documentation on what's actually going on, as it looks pretty unintuitive, I must confess. Checkout another nice article on the topic.
See an example of this here. To create this custom special event, use:
jQuery.event.special.drag = {
// invoked each time we bind drag to an element
add: function(obj) {
var originalHandler = obj.handler;
obj.handler = function(event) {
var el = jQuery(this);
if(el.data('mousePressed')) {
return originalHandler.apply(this, arguments);
}
};
},
// invoked only the first time drag is bound per element
setup: function(data, namespaces) {
var el = jQuery(this);
el.data('mousePressed', false);
el.bind('mousedown', function() {
jQuery(this).data('mousePressed', true);
});
jQuery(document).bind('mouseup', function() {
el.data('mousePressed', false);
});
el.bind('mousemove', jQuery.event.special.drag.handler);
},
// invoked when all drag events are removed from element
teardown: function(namespaces) {
var el = jQuery(this);
jQuery.removeData(this, 'mousePressed');
el.unbind('mousedown');
el.unbind('mouseup');
},
// our wrapper event is bound to "mousemove" and not "bind"
// change event type, so all attached drag handlers are fired
handler: function(event) {
event.type = 'drag';
jQuery.event.handle.apply(this, arguments);
}
};
Try something like this?
var isDown = false;
$(sel).mousedown(function() {
isDown = true;
});
$(sel).mouseup(function() {
isDown = false;
});
$(sel).mousemove(function() {
if (isDown) {
// Mouse is clicked and is moving.
}
});
If I'm reading your question correctly, you're asking about requiring the combination of multiple events to trigger a single function. It's possible to achieve this sort of thing, but I think it will depend greatly on the specific events and the logic or illogic of their combination. For example, the mousemove event:
...is triggered whenever the mouse
pointer moves, even for a pixel. This
means that hundreds of events can be
generated over a very small amount of
time.
Contrast that with the mousedown event, which is -- well, one per click. How to combine? The jQuery API goes on to state:
A common pattern is to bind the
mousemove handler from within a
mousedown hander [sic], and to unbind it
from a corresponding mouseup handler.
If implementing this sequence of
events, remember that the mouseup
event might be sent to a different
HTML element than the mousemove event
was. To account for this, the mouseup
handler should typically be bound to
an element high up in the DOM tree,
such as <body>.
Perhaps another approach would be to create a primitive finite state machine to ingest as inputs the various relevant events you have in mind, update its state accordingly, and then trigger a custom event when appropriate states are achieved. This all smells a little bit like reinventing the wheel, but maybe your requirement is very specific or unusual.
References: jQuery API: mousemove()
Alright, thanks for your idea Patrick. It reminded of a way I had done something like this in Java.
var m_down = false;
$(this).mousedown(function() {
m_down = true;
});
$(this).mouseup(function() {
m_down = false;
});
$(this).mousemove(function() {
// Code to occur here
});