14

I'm writing jQuery for an app that's being build in JSF. The JSF components are using a lot of their own JS for doing whatever JSF does and they use a lot of onclick attributes to handle it all.

Is there valid/proper way to bind your own click event to an element and ensure that your event fires prior to the default onclick event? It appears that by default a jQuery click function is fired after any inline onclick function calls.

DefyGravity
  • 5,681
  • 5
  • 32
  • 47
DA.
  • 39,848
  • 49
  • 150
  • 213
  • You cannot do this unless the inline onclick attributes are removed from the element. – ShankarSangoli Jul 14 '11 at 14:09
  • If you use the jquery function .click() on $(document).ready() you can call your own function. I've wondered often, how can jquery that do. – Reporter Jul 14 '11 at 14:10
  • i was under the impression that jQuery events fired before the default event, otherwise event.preventDefault would never work. i would try, on document ready, copying the jsf .click events present, unbind them, bind your click event, and add the jsf .click events back in through jQuery. I have no way to test that for you though. – DefyGravity Jul 14 '11 at 14:10

4 Answers4

17

In jQuery events are triggered strictly in the order in which they were registered.

You can circumvent any inline DOM0 onclick style handlers by iterating over the whole DOM, removing those onclick properties, and then registering your own handler which then invokes the originally defined function.

Something like:

$('[onclick]').each(function() {
    var handler = $(this).prop('onclick');
    $(this).removeProp('onclick');
    $(this).click(handler);
});

See http://jsfiddle.net/alnitak/2SCTK/

So, register your own handlers first with jQuery, then invoke the code above to remove the original inline onclick handlers and re-register them as if jQuery had added them.

EDIT code simplified to just use the original function - jQuery will ensure that the function is invoked with the right context. Previous code which explicitly set the context to window was incorrect.

Alnitak
  • 334,560
  • 70
  • 407
  • 495
  • `if (!r) {ev.preventDefault();}`. This will prevent the default action if the original returns a "falsy" value (0, undefined, false, etc.). I suggest you just `return orig.call(window);` instead. I did a few tests and it seems returning false from a DOM0 event and from an `addEventListener` act the same. – gen_Eric Jul 14 '11 at 14:20
  • 1
    We overlapped edits - I already changed it to `if (r === false)`. Just returning `false` does appear to work in Chrome, I didn't try other browsers yet. I'm sure I've had problems in the past where returning false didn't stop _all_ default behaviour. – Alnitak Jul 14 '11 at 14:24
  • 1
    @Rocket also it appears that `return false` in `addEventListener()` isn't the same as doing it in a jQuery `.click()` handler - apparently jQuery maps the `return false` into an event cancellation itself. http://www.mail-archive.com/jquery-en@googlegroups.com/msg71371.html, so I've updated the code. – Alnitak Jul 14 '11 at 14:34
  • I did not know that, thanks. I know how to bind events, but not too much about their internals. – gen_Eric Jul 14 '11 at 14:37
  • I really like this as this can be useful for everything we're doing where we're mixing back-end generated inline js calls with jQuery. – DA. Jul 14 '11 at 16:02
  • @DA check it again - I simplified it somewhat after realising that I didn't need to use `Function.call()` to set the context. – Alnitak Jul 14 '11 at 16:44
7

You can remove the onClick event from the DOM element, and then rebind it using jQuery.

<div id="click" onclick="alert('DOM')">CLICK ME</div>

$('#click').click(function(){
    alert('jQuery');
});

var clickFunc = $('#click').prop('onclick');
$('#click').removeProp('onclick');
$('#click').click(clickFunc);

Events are called in the order they are bound, so the only way to call your function first, is to unbind, and then rebind the original function.

Demo: http://jsfiddle.net/wYd5t/2/

gen_Eric
  • 223,194
  • 41
  • 299
  • 337
  • yes, that's kinda what I was suggesting. Just wondering - isn't there a difference between how an `onclick` DOM0 style handler prevents bubbling or propagation (i.e. `return false`) compared to `addEventListener()` style handlers? – Alnitak Jul 14 '11 at 14:11
  • @Alnitak: They seem to act the same way (try to change the location of the `return false`). http://jsfiddle.net/wYd5t/1/ – gen_Eric Jul 14 '11 at 14:21
  • 1
    see http://stackoverflow.com/questions/1357118/javascript-event-preventdefault-vs-return-false - the answer appears to be unclear. – Alnitak Jul 14 '11 at 14:31
  • @Alnitak: I didn't realize there was a difference between those. – gen_Eric Jul 14 '11 at 14:37
  • 1
    I realised I don't need to `return cb.call(...)` at all, just pass the function to `.click` like you did in your answer. – Alnitak Jul 14 '11 at 16:46
1

There is no way to mandate event firing order for the same listener on the same element. Every browser is free to fire the events in any order it chooses.

Variant
  • 17,279
  • 4
  • 40
  • 65
  • Is this a theoretical problem or a real problem? In other words, are there known browsers that fire them in reverse order? – DA. Jul 14 '11 at 14:19
  • I remember reading that somewhere, can't find any reference for that now... Can't find any credible reference for the opposite either though. – Variant Jul 14 '11 at 14:24
  • According to the jQuery docs for `.bind`: "If there are multiple handlers registered, they will always execute in the order in which they were bound." http://api.jquery.com/bind/ – gen_Eric Jul 14 '11 at 14:32
  • jQuery is probably credible enough... :) – Variant Jul 14 '11 at 14:43
1

You could use onmousedown on the element.

radu florescu
  • 4,315
  • 10
  • 60
  • 92
powtac
  • 40,542
  • 28
  • 115
  • 170