开发者

jQuery bind event listener before another

Using the prettyPhoto plugin to open modal-style content containers, and trying to integrate with Google Analytics' Event Tracking to track when videos get opened.

Trouble is, when I

$('a.videoClickListener').click(function(event){
    console.log('here');
    _gaq.push(['_trackEvent', 'Product Page', 'Video Open', '<?php echo $product->getNameWithManufacturer(); ?>']);
});

the event is never fired, as prettyPhoto stops the event from continuing (quite rightly, as otherwise the page would change if a hyperlink was clicked).

prettyPhoto does not seem to provide an 'open' callback function, but if it did I couldn't use it anyway, as the prettyPhoto listeners are set up somewhere in the layout (we use rel=开发者_JAVA百科"prettyPhoto" every time we want to use prettyPhoto, and pass params through the URL, which is the prettyPhoto-recommended way of doing things). I would also like to pass the product details to Analytics, ruling out a global video-opening listener across all prettyPhoto opening events.

How can I bind my listener before the prettyPhoto listener? If it turns out I have to use .unbind(), then bind my listener, and then rebind prettyPhoto, how can I unbind a handler specified in a plugin?


Ideally, you would be able to add your event binding before any others, so that it executes first.

Unfortunately, jQuery doesn't seem to include any such facility.

However, I've coded a preBind method, which seems to do the trick:

$.fn.preBind = function(type, data, fn) {
  this.bind(type, data, fn);

  var currentBindings = this.data('events')[type];
  var currentBindingsLastIndex = currentBindings.length - 1;

  var newBindings = [];

  newBindings.push(currentBindings[currentBindingsLastIndex]);

  $.each(currentBindings, function (index) {
    if (index < currentBindingsLastIndex)
      newBindings.push(this);
  });

  this.data('events')[type] = newBindings;

  return this;
};

Usage:

$('#button').bind('click', function() {
  console.log('world');
});

// 2nd apostrophe for the selector was missing
$('#button').preBind('click', function() {
  console.log('hello');
});

$('#button').click();

// Output:
//
// > "hello"
// > "world"


I'm using Jonathan's preBind method, slightly modified, and it does the trick nicely.

$.fn.preBind = function (type, data, fn) {
    this.each(function () {
        var $this = $(this);

        $this.bind(type, data, fn);

        var currentBindings = $this.data('events')[type];
        if ($.isArray(currentBindings)) {
            currentBindings.unshift(currentBindings.pop());
        }
    });
    return this;
};


Worked for me with older and newer jQuery versions:

/**
 * @link http://stackoverflow.com/a/26892146/655224
 */
jQuery.fn.getEvents = function() {
    if (typeof(jQuery._data) == 'function') {
        return jQuery._data(this.get(0), 'events') || {};
    } else if (typeof(this.data) == 'function') { // jQuery version < 1.7.?
        return this.data('events') || {};
    }
    return {};
};

jQuery.fn.preBind = function(type, data, fn) {
    this.each(function () {
        var $this = jQuery(this);

        $this.bind(type, data, fn);

        var currentBindings = $this.getEvents()[type];
        if (jQuery.isArray(currentBindings)) {
            currentBindings.unshift(currentBindings.pop());
        }
    });
    return this;
};

But beware, this functions can only return/prebind that events that was set with jQuery itself.

Special thanks to @jonathanconway and @Patrick...


Here is a variant of the solution using the more modern .On() approach.

// Same as .on() but moves the binding to the front of the queue.
$.fn.priorityOn = function (type, selector, data, fn) {
    this.each(function () {
        var $this = $(this);

        var types = type.split(" ");

        for (var t in types) {
            $this.on(types[t], selector, data, fn);

            var currentBindings = $._data(this, 'events')[types[t]];
            if ($.isArray(currentBindings)) {
                currentBindings.unshift(currentBindings.pop());
            }
        }


    });
    return this;
};

Usage is like

$(document).priorityOn("click blur change", ".some .selector input", function (e) {
    // Your code.
});


Here is a slightly modified version of braks answer that also supports namespaces and type as plain object:

$.fn.prioOn = function(type, selector, data, fn, /*INTERNAL*/ one) {
    // Types can be a map of types/handlers
    if (typeof types === 'object') {
        // ( types-Object, selector, data )
        if (typeof selector !== 'string') {
            // ( types-Object, data )
            data = data || selector;
            selector = undefined;
        }
        for (type in types) {
            if (!types.hasOwnProperty(type)) continue;
            this.prioOn(type, selector, data, types[type], one);
        }
        return this;
    }
    this.each(function() {
        var $this = $(this);

        var types = type.split(' ');

        for (var t in types) {
            if (!types.hasOwnProperty(t)) continue;
            $this.on(types[t], selector, data, fn, one);

            var currentBindings = $._data(this, 'events')[types[t].split('.')[0]];
            if ($.isArray(currentBindings)) {
                currentBindings.unshift(currentBindings.pop());
            }
        }
    });
    return this;
};


Had to change one line in the above code to make it work:

    var currentBindings = $this.data('events',type); // var currentBindings = $this.data('events')[type];

This could be because I am using jquery 2.0.3


if you use unbind() then even the bindings made by plugins will be unbinded.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜