17

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="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?

sennett
  • 8,014
  • 9
  • 46
  • 69

7 Answers7

24

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"
Jonathan
  • 32,202
  • 38
  • 137
  • 208
18

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;
};
Jonathan
  • 32,202
  • 38
  • 137
  • 208
Patrick
  • 1,019
  • 13
  • 16
7

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.
});
braks
  • 1,505
  • 15
  • 23
6

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...

algorhythm
  • 8,530
  • 3
  • 35
  • 47
2

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;
};
1

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

  • 1
    In 1.8.3 I had to use the following: ```$._data(this, "events")[type];```. Note 'this' is the actual html element and not a jQuery object. – Gowiem Oct 17 '13 at 21:13
-1

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

Amareswar
  • 2,048
  • 1
  • 20
  • 36
  • Thanks for the reply. How would I go about selecting an event to unbind? Are they named somehow? In prettyPhoto the listener is bound as an anonymous function. – sennett May 17 '11 at 10:30
  • 1
    how prettyphoto is stopping the event? If both you and plugin are listening to some type of event on the same item there is no way that the plugin can stop firing you bind. Listen to the same type of event the plugin listens to and also listen exactly on the same element. – Amareswar May 17 '11 at 11:48
  • Thanks once again. It seems to be working now... I must have made another mistake somewhere (possibly not encoding the product name when passing to javascript from PHP). – sennett May 17 '11 at 13:18