I'm looking into custom events in JavaScript.
According to MDN, using the CustomEvent constructor, there is an option to make the event "bubble up" (false by default):
https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent#CustomEventInit
Example:
// add an appropriate event listener
obj.addEventListener("cat", function(e) { process(e.detail) });
// create and dispatch the event
var event = new CustomEvent("cat", {"detail":{"hazcheeseburger":true}});
obj.dispatchEvent(event);
I tested it on jsfiddle:
http://jsfiddle.net/ppx4gcxe/
And the bubble up functionality seems to work. But I'd like my custom event to "trickle down", that is to trigger even listeners on child elements; the opposite of bubbling up.
I vaguely remember some default browser events "trickling down". This was supposedly one of these points of contention in the early browser days.
Anyway, is there any way to get this functionality on my custom events? Any relatively easy and straightforward way, of course. I don't really want to write a function to traverse all child elements and manually trigger any listeners on them. I hope there's another way.
The behavior you're looking for is called event capturing (the opposite of event bubbling). You can enable event capturing by passing in true as the third argument to addEventListener.
See: http://jsfiddle.net/zs1a6ywo/
NOTE: event capturing is not supported in IE 8 or below.
For more information, see: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
Related
How would i replace internally triggered events of medium-editor with my custom ones or simply change internally designed behaviour?
In this hierarchy
<div>
<textarea class='editable'></textarea>
</div>
I bind a click handler to the div and do e.stopPropagation() and e.preventDefault().
I also try adding after the instantiating of medium-editor.
var editor = new MediumEditor('.editable')
.subscribe("editableClick", function(e){
e.preventDefault();
});
Every way i try textarea gets focused and cursor starts to blink.
For example intial click event adds an element to the dom with a class .medium-editor-element should i dive to source to modify this behaviour?
Or maybe i would like it to work with not a click but a double click.
Anyone familiar with the internal workings of medium-editor?
After trial and error and the help of dev tools i found the way to do what i want.
But i think this question is still answerable because i did it by modifying the source of medium-editor.
So, in medium-editor.js in line 2725 there is setupListener function.
There are 3 main events attached there to case 'externalInteraction': 2731th line.
mousedown,click,focus.
Starting from 2959th line there are the attached handlers for those events.
handleBodyClick,handleBodyFocus,handleBodyMousedown
The mousedown is important for my case because it is the first one that fires and should be prevented and accepted in different cases.
In the end i added a dblclick and handleBodyDblClick to source then put some logic in handleBodyMousedown to prevent the default behaviour of mousedown event in some cases.
Anyway, from the source as i can understand there are no override methods or hooks to modify medium-editor internal events.
It would be nice to have that feature.
Or if i am wrong i would like to know if there is a better way to do all these.
I'm thinking about building a tool that converts all browser events (either native dom events like .click() or jQuery events) to a standard form.
The standard form is: HTMLElement.dispatchEvent(new Event(eventType, eventInitDict))
For example, I want to change HTMLElement.click() to HTMLElement.dispatchEvent(new Event("click", {"bubbles": true, ...})) for all events.
My question:
Is there a complete mapping from events to this standard form and if
so, is it documented anywhere?
Are there any events that could be fired that couldn't be converted
to this standard form?
Does jQuery do anything fancy where I wouldn't be able to do this
conversion.
It is imperative that I completely convert all events into this format... None can be spared!
Thanks!
Why am I trying to do this?
I am trying to capture all events fired by a Chrome extension. To do this, I've decided to modify an extensions content script before it is injected into the page (I don't care about background page or popup pages) so all events triggered are "tagged" as originating from an extension (this will be added to the eventInitDict in the examples above. I'm modifying Chromium to do this.
PS. I couldn't think of a better question title but if you have one, please let me know / change it.
To capture all events you'll need to add event listeners to the DOM for each event.
There are a lot of events to capture and this is laborious. I know because I've had to do this in a project i'm currently working on.
Normally in a SO answer it's advisable to place all the relevant text into the answer, in this instance because of the number of events available, I'm not going to do that. You can find a detailed list of DOM Events here (MDN)
Now fortunately for you events can be captured regardless of what triggers them. Underneath, JQuery most likely triggers DOM events although as I haven't looked at the JQuery source, I couldn't say for sure.
When adding event listeners, you'll want to pass a boolean set to true for useCapture on the addEventListener function.
target.addEventListener(type, listener, useCapture);
Further documentation on addEventListener here
It's likely you'll want to declare a JSON array of events you want to capture, or a static array in your code so that you can maintain the list of events you wish to capture.
Furthermore for triggering events, you'll need to store a reference to the element that they targeted. I've had to do this as well. You'll want to find a way to get the selector for whatever element fired the event.
something like the following
var eventList = ['click', 'keydown'];
for(var i = 0;i < eventList.length; i++) {
var eventName = eventList[i];
document.addEventListener(eventName, function (evt) {
var selector = getElementSelector(evt.target); // where getElementSelector will be a function that returns an id, css path or xpath selector.
var captured = { eventName: eventName, selector: selector };
});
}
To trigger the captured event you might do the following, assuming you use the object structure above
var eventObject = { eventName: 'click', selector: '#targetId'};
var target = document.querySelector(eventObject.selector);
var event = new Event(eventName, {
view: window,
bubbles: true,
cancelable: true
});
target.dispatchEvent(event);
There is a caveat, for each event captured, you'll want to capture properties on the event that may be specific to that event type, and save that in your custom object that stored event properties like eventName, selector and the like.
The 'standard' form of new Event won't suffice. Some events have different initialisation dictionaries which will be ignored by a plain Event if the properties aren't standard for the Event and this is why you should instantiate the correct object type, e.g. new MouseEvent, new KeyboardEvent. etc.
You can use new CustomEvent for capturing synthetic events that add none standard properties to the detail property of the event, but this will not suffice for regular DOM events.
It's not a small task by any measure.
event.preventDefault() will override default event behavior of an element. How can I temporarily override all click bindings and not just default ones?
Or is there a way to save all the click bindings so I can unbind them and use them later?
Well this is not a proper answer but a workaround. We can push the required handler on top of the stack and then used return false to stop other bindings. https://github.com/private-face/jquery.bind-first
You can use jQuery.clone(true) what this does is return data for an element. The parameter that is set to true means to also copy over all the events as well.
So if you clone the element into a variable you can bring back the old click events by simply replacing your target element with its older clone (which has the old events)
So it goes as follows:
step 1:
clone the target element using jQuery.clone(true) into a variable
step 2:
remove all click events from the target element using jQuery.off('click')
step 3:
bind your event to the target element with jQuery.on('click' function etc...)
step 4:
when you're done replace the target element with its clone (which has the old events)
Here is a JSFiddle for your viewing pleasure
(Sorry for the simpleness of the JSFiddle I mocked it up quickly and I have no example situation where I would use this.)
EDIT: I forgot to explain jQuery.clone(true)
You may catch the click before it can bubble by using
element.addEventListener(type, listener[, useCapture]);
This way you can 'catch' the click before triggering the jQuery click handler, like this (which I took from this stackoverflow question:
document.addEventListener('click', function(e) {
e.stopPropagation();
}, true);
For more information (and some IE < 9 support), see developer.mozilla
Edit: details about useCapture from Mozilla:
If true, useCapture indicates that the user wishes to initiate capture. After initiating capture, all events of the specified type will be dispatched to the registered listener before being dispatched to any EventTarget beneath it in the DOM tree. Events which are bubbling upward through the tree will not trigger a listener designated to use capture. See DOM Level 3 Events for a detailed explanation. If not specified, useCapture defaults to false.
If you have control over all of the JS code and can bind your own handler first and all other event handlers are bound with jQuery then you can do this:
var overrideClick = false;
$("#yourElementId").click(function(e) {
if (overrideClick) {
e.stopImmediatePropagation();
// e.preventDefault(); uncomment this if you want to prevent default action too
}
});
Where some other part of your code would set overrideClick = true when needed.
Demo: http://jsfiddle.net/NCa5X/
jQuery calls handlers in the order they are bound, so you can then use event.stopImmediatePropagation() to prevent the other handlers from being called.
Assuming that there are a large number of elements throughout the site that have an unknown number and type of events bound to them.
If I need to override all of these events with one single bound event, and only that event will fire, what are some recommendations?
I would be binding the event to a click event handler, and I am using jQuery.
Thanks in advance.
You’re looking for jQuery#unbind.
To remove all event handlers on an element or a set of elements, just do:
$('.some-selector').unbind();
To unbind only click handlers, use unbind('click'):
$('.some-selector').unbind('click');
To unbind all click handlers and immediately bind your own handler after that, you can do something like this:
$('.some-selector').unbind('click').click(function(event) {
// Your code goes here
});
Note that this will only work for events bound using jQuery (using .bind or any jQuery method that uses .bind internally). If you want to remove all possible onclick events from a given set of elements, you could use:
$('.some-selector')
.unbind('click') // takes care of jQuery-bound click events
.attr('onclick', '') // clears `onclick` attributes in the HTML
.each(function() { // reset `onclick` event handlers
this.onclick = null;
});
I would like to provide a thought without removing all events all together (just override them).
If your new one single bound event (we call it "click" here) is specific to the element it binds to, then I believe you can ignore any other events simply by stopPropagation() function. Like this
$("specific-selector").on("click", ".specific-class", function (e) {
e.stopPropagation()
// e.stopImmediatePropagation()
/* your code continues ... */
});
It will stop events bubbles up, so your other events won't fire. use stopImmediatePropagation() to prevent other events attached onto the same elements as "click" does.
For example, if "mouseleave" event is also bind to $("specific-selector .specific-class") element, it won't fire, too.
At last, all other events won't fire on this element but your new "click" element.
The unsolved question is, what if other events also use stopPropagation()? ... Then I think the one with best specification wins, so try to avoid complex, too many events is final suggestion.
You can see "Direct and delegated events" on jQuery site for more information.
Looks like this is pretty simple actually:
$('#foo').unbind('click');
$('#foo').bind('click', myNewFunction);
Thanks for your responses though.
Try to use live instead of bind. Then you can easily remove live binding with die from selector which is fast operation and set another live equally fast.
$('selection here').live('..', .....); // multiple invocations
$('selection here').die();
$('selection here').live('click',.....);
DOM is not touched at all. Event condition is evaluated on event occurrence.
But generally if you just want to swap handler functions why not to do it this way:
var ahandler = function(evt) { /* first implementation */ }
$('.selector').bind('click', function(evt) { ahandler(evt); });
//and then if you want to change handlers
ahandler = function(evt) { /* new implementation */ };
This gives absolutely no cost of any changes, rebinding etc.
What's the best way to execute a function exactly once every time a button is clicked, regardless of click speed and browser?
Simply binding a "click" handler works perfectly in all browsers except IE.
In IE, when the user clicks too fast, only "dblclick" fires, so the "click" handler is never executed. Other browsers trigger both events so it's not a problem for them.
The obvious solution/hack (to me at least) is to attach a dblclick handler in IE that triggers my click handler twice. Another idea is to track clicks myself with mousedown/mouseup, which seems pretty primitive and probably belongs in a framework rather than my application.
So, what's the best/usual/right way of handling this? (pure Javascript or jQuery preferred)
Depending on your situation you can use different approaches, but I would suggest using namespaced event handlers with jQuery like this:
function eventHandler(event) {
// your handler code here
doSomeMagic();
}
var element = $('#element');
element.one('click.someNameSpace', function(event){
// first we unbind all other event handlers with this namespace
element.unbind('.someNameSpace');
// then we execute our eventHandler
eventHandler();
}).one('dblclick.someNameSpace', function(event){
// If this fires first, we also unbind all event handlers
element.unbind('.someNameSpace');
// and then execute our eventHandler
eventHandler();
});
I'm not sure this will work the way you want it, but it's a start, I guess.
Mousedown and mouseup works just like the click functions, unfortunately so much that when IE omits a click because of a doubleclick it will also omit the mousedown and mouseup. In any case, you can add both click and dblclick to the same object and feed the clicks through a function that sort out any click happening too close to the last.
<div onclick="clk()" ondblclick="clk()"></div>
lastclicktime=0
function clk(){
var time=new Date().getTime()
if(time>lastclicktime+50){
lastclicktime=time
//Handle click
}
}
I by the way just found out that, at least in Firefox the dblclick event is not given an event time, therefore I had to resolve to the Date method.