davy
davy

Reputation: 4562

Override (wrap) an existing jQuery click event with another in javascript

Say I have an existing button and attach a click to it via jQuery:

var $button = $('#test').click(function () { console.log('original function') });

Now, say I want to override that click so that I can add some logic to the function before and after it. I have tried binding and wrapping using the functions below.

Function.prototype.bind = function () {
   var fn = this;
   var args = Array.prototype.slice.call(arguments);
   var object = args.shift();

   return function () {
       return fn.apply(object, args.concat(Array.prototype.slice.call(arguments)));
   }
}

function wrap(object, method, wrapper) {
   var fn = object[method];

   return object[method] = function() {
       return wrapper.apply(this, [fn.bind(this)].concat(
        Array.prototype.slice.call(arguments)));
   }
}

so I call wrap with the object that the method is a property of, the method and an anonymous function that I want to execute instead. I thought:

wrap($button 'click', function (click) {
     console.log('do stuff before original function');
     click();
     console.log('do stuff after original function');
 });

This only calls the original function. I have used this approach on a method of an object before with success. Something like: See this Plunker

Can anyone help me do this with my specific example please?

Thanks

Upvotes: 4

Views: 2273

Answers (2)

juvian
juvian

Reputation: 16068

After a long search I reached the same answer as @Corey, here is a similar way of doing it considering multiple events:

function wrap(object, method, wrapper) {
          
  var arr = []
  var events = $._data(object[0], 'events')
  
  if(events[method] && events[method].length > 0){ // add all functions to array
    events[method].forEach(function(obj){
      arr.push(obj.handler)
    })
  }
  
  
  
  if(arr.length){
      function processAll(){ // process all original functions in the right order
        arr.forEach(function(func){
          func.call(object)
        })
      }
  
      object.off(method).on(method, function(e){wrapper.call(object,processAll)}) //unregister previous events and call new method passing old methods
  }
   
    
}


$(function(){
  $('#test').click(function () { console.log('original function 1') });
  var $button = $('#test').click(function () { console.log('original function 2') });
  wrap($button, 'click', function (click,e) {
     console.log('do stuff before original functions');
     click()
     console.log('do stuff after original functions');
 });
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>


<div id='test'>click me</div>

Upvotes: 3

Corey
Corey

Reputation: 5818

You could create a jQuery function that gets the original event handler function from data, removes the click event, then adds a new event handler. This function would have two parameters (each functions) of before and after handlers.

$(function() {

    jQuery.fn.wrapClick = function(before, after) {
        // Get and store the original click handler.
        // TODO: add a conditional to check if click event exists.
        var _orgClick = $._data(this[0], 'events').click[0].handler,
            _self = this;

        // Remove click event from object.
        _self.off('click');

        // Add new click event with before and after functions.
        return _self.click(function() {
            before.call(_self);
            _orgClick.call(_self);
            after.call(_self);
        });
    };

    var $btn = $('.btn').click(function() {
        console.log('original click');
    });

    $btn.wrapClick(function() {
        console.log('before click');
    }, function() {
        console.log('after click');
    });

});

Here is a Codepen

Upvotes: 4

Related Questions