6

I want to wrap an existing click event in some extra code.

Basically I have a multi part form in an accordion and I want to trigger validation on the accordion header click. The accordion code is used elsewhere and I don't want to change it.

Here's what I've tried:

   //Take the click events off the accordion elements and wrap them to trigger validation
    $('.accordion h1').each(function (index, value) {
        var currentAccordion = $(value);
        //Get reference to original click
        var originalClick = currentAccordion.click;

        //unbind original click
        currentAccordion.unbind('click');

        //bind new event           
        currentAccordion.click(function () {
            //Trigger validation
            if ($('#aspnetForm').valid()) {
                current = parseInt($(this).next().find('.calculate-step').attr('data-step'));             
                //Call original click.
                originalClick();
            }
        });
    });

jQuery throws an error because it's trying to do this.trigger inside the originalClick function and I don't think this is what jQuery expects it to be.

EDIT: Updated code. This works but it is a bit ugly!

   //Take the click events off the accordion elements and wrap them to trigger validation
    $('.accordion h1').each(function (index, value) {
        var currentAccordion = $(value);
        var originalClick = currentAccordion.data("events")['click'][0].handler;
        currentAccordion.unbind('click');
        currentAccordion.click(function (e) {
            if ($('#aspnetForm').valid()) {
                current = parseInt($(this).next().find('.calculate-step').attr('data-step'));
                $.proxy(originalClick, currentAccordion)(e);
            }
        });
    });
Rob Stevenson-Leggett
  • 35,279
  • 21
  • 87
  • 141

4 Answers4

4

I finally came up with something reliable:

$(".remove").each(function(){
    // get all our click events and store them
    var x = $._data($(this)[0], "events");
    var y = {}
    for(i in x.click)
    {
    if(x.click[i].handler)
    {
    y[i] = x.click[i].handler;
    }
    }

    // stop our click event from running
    $(this).off("click")

    // re-add our click event with a confirmation
    $(this).click(function(){
    if(confirm("Are you sure?"))
    {
        // if they click yes, run click events!
        for(i in y)
        {
        y[i]()
        }
        return true;
    }
    // if they click cancel, return false
    return false;
    })
})

This may seem a bit weird (why do we store the click events in the variable "y"?)

Originally I tried to run the handlers in x.click, but they seem to be destroyed when we call .off("click"). Creating a copy of the handlers in a separate variable "y" worked. Sorry I don't have an in depth explanation, but I believe the .off("click") method removes the click event from our document, along with the handlers.

http://www.frankforte.ca/blog/32/unbind-a-click-event-store-it-and-re-add-the-event-later-with-jquery/

Frank Forte
  • 2,031
  • 20
  • 19
  • You may need to handle click events added inline, e.g. `do something` separately by grabbing `var inline_click = this.onclick` then checking for inline_click after the for loop and running it `if(inline_click){ inline_click(); }` – Frank Forte Nov 24 '13 at 05:22
4

I think this:

var originalClick = currentAccordion.click;

Isn't actually doing what you think it is - you're capturing a reference to the jQuery click function, rather than event handler you added, so when you call originalClick() it's equivalent to: $(value).click()

gb.
  • 4,629
  • 1
  • 20
  • 19
  • You're right, so I need to find out how to get the original handler. – Rob Stevenson-Leggett Oct 13 '11 at 10:24
  • You can perhaps do that using currentAccordian.data("events") - it gives you back the list of events that there are handlers for and the handlers, but I don't recall the exact way that works. – gb. Oct 13 '11 at 10:31
  • Try this: [How to find event listeners on a DOM node?](http://stackoverflow.com/questions/446892/how-to-find-event-listeners-on-a-dom-node/447106#447106) – gb. Oct 13 '11 at 10:34
  • Cheers, between you and Steve there's a working solution but it's a bit ugly :-) – Rob Stevenson-Leggett Oct 13 '11 at 10:39
2

I'm not a jQuery user, but in Javascript, you can set the context of the this keyword.

In jQuery, you use the $.proxy() method to do this.

$.proxy(originalClick, value);
originalClick();

Personally, I'd look at creating callback hooks in your Accordion, or making use of existing callbacks (if they exist) that trigger when opening or closing an accordion pane.

Hope that helps :)

Steven Bakhtiari
  • 3,227
  • 2
  • 20
  • 24
1

currentAccordion.click is a jQuery function, not the actual event.

Starting with a brute-force approach, what you'd need to do is:

  • Save references to all the currently bound handlers
  • Unbind them
  • Add your own handler, and fire the saved ones when needed
  • Make sure new handlers bound to click are catched too

This looks like a job for an event filter plugin, but I couldn't find one. If the last point is not required in your application, then it's a bit simpler.

Edit: After some research, the bindIf function shown here looks to be what you'd need (or at least give a general direction)

abesto
  • 2,331
  • 16
  • 28