jQuery.on('click') before jQuery.click?

2020-07-10 10:40发布

问题:

I have an external script, wich i can't modify. This script load a button, and add a jQuery .click on it... and it finish with "return false".

I need to trigger my own code on this click. When i load the page the doesn't exists, so i need to use .on('click') to bind "live". But it looks like the .on('click') is loaded "after" the .click and as he use "return false", my .on('click') is not loaded.

So the question is... How can i trigger my on click on this dynamically loaded a#btn wich already has a .click function returning false ?

Here is the fiddle : http://jsfiddle.net/PLpqU/

And here a code example :

<div id="container"></div>
// I want this action to be executed on click, and the other too
// I can't use .click because on the "real" code, the a#btn is loaded after the page by another script
jQuery(document).on('click','a#btn',function(){
        ga('send', 'event', { eventCategory: 'xxxx', eventAction: 'yyyy' });
}) ;

// http://www.xxxxxx.com/distant-script.js
// This one is binded in a script that i cannot edit :
// Just before it load a#btn on the page, and then bind .click on it
// as he return false, the other on('click') are not executed
jQuery('#container').append('<a id="btn" />') ;
jQuery('a#btn').click(function(){
    // Some code stuff i need to be executed, but i can't manage
    return false ;
}) ;

As you can the, the objective is to trigger a Google Analytics event on a link button loaded by a distant script.

回答1:

It seems that you are facing multiple issues, but the more important one would be knowing when the element is rendered in the DOM so that you can manipulate it's events collection.

Once the element is accessible, it's quite simple to unbind the plugin's handlers, bind yours and rebind the plugin's one knowing that the jQuery's event collection can be accessed like: $._data(domEl, 'events');

Here's an example:

var $div = $('<div>').click(function () { console.log('plugin'); return false; }),
    clickListener = jQuery._data($div[0], 'events').click[0];

//unbind all
$div.off('click');

//bind yours
$div.click(function () { console.log('yours'); });

//rebind the plugin's one
//note that I do not register the listener by honoring the same configs, 
//but you could since you can see them in the clickListener object
$div.click(clickListener.handler);

//test out everyting
$div.triggerHandler('click');

If you do not want to unbind, I believe that you can also use the DOM level 0 event model and do:

element.onclick = yourHandler;

Know, to know when the element is available is much more of an issue if the plugin renders this element asynchronously and doesn't provide a way to know when the process is complete.

If you want to support old browsers, you will have no other choice than either override the plugin's code (assuming the concerned methods are public) or poll the DOM with setInterval until the element you were looking for is part of the DOM and then you can do what we discussed above.

If you are targetting modern browsers, you can use the MutationObserver object to listen to DOM changes.



回答2:

You can unbind an event and then add your function as the event handler. http://api.jquery.com/unbind/

Check this jsfiddle http://jsfiddle.net/Xm5MB/

$(function(){

    $("#testDiv").append("<a id='btn'>link</a>");
    $("a#btn").click(function(){
        alert("first bind");
    });
    $("a#btn").unbind("click");
    $("a#btn").on("click", function(){
        alert("second bind");
    });
});


回答3:

One thing that might work is to bind the click event that you want to fire first to a child of the element that the other click event is bound to. Since the click event will bubble up from the lowest level of the DOM to the highest, it should fire any handlers bound to children before the ones bound to their parents.

If you want to stop the other event from firing, you can either manually use event.stopPropagation() or else return false from the function you have bound to the child, which calls both event.stopPropagation() and event.preventDefault().



回答4:

Here is a solution provided by @plalx :

var os = jQuery('#container') ; // the #container is written by me on the code. He's empty on load and is filled by the distant script wich add the a#btn on it.
if ( os.length > 0 ) // I just check if he is on my page, just in case.
{
    var timer = setInterval(function () 
    {
        var btn_os = jQuery('a#btn') ;
        if (btn_os.length) // Button is loaded on page : we can do the trick
        {
            clickListener = jQuery._data(btn_os[0], 'events').click[0]; // caching the first .click bind

            btn_os.off('click'); // Removing all binds

            // Adding my own bind
            btn_os.click(function () {
                console.log('test') ;
                ga('send', 'event', { eventCategory: 'xxxx', eventAction: 'yyyy', eventLabel : 'zzzz' });
                // You can either trigger here the previous handler or rebind it out of here, 6 lines bottom
                // In my case i prefer to trigger it myself, with a little delay to make sure that my ga('send') has time to register
                setTimeout(function(){clickListener.handler() ;}, 150);
            });

            // And rebinding the old one if needed
            // In my particular case i don't want to rebind it, i prefer triggering it manually on my .click function with a setTimeout delay
            // btn_os.click(clickListener.handler);
            clearInterval(timer) ; // Removing the interval timer
        }
    }
    , 100);
}