4

I'm trying to modify the behaviour or a JavaScript library, basically by monkeypatching it (no, there is no better way).

At a certain point in the code, I need to know whether Shift is pressed or not. If the event handler in this library were properly written, it'd receive the "event" as its first parameter, but unfortunately, it isn't (events are wired with onclick inline in the HTML)

So, I'm trying to see if jQuery "stores" the last event object somewhere, or if there is some other way to access it.

Essentially, what I want is "window.event", but ideally I'd like for it to work on Firefox.

Any ideas, besides adding a global onKeyDown handler to the document and keeping track of the state of Shift myself? That feels a bit overkill and a bit too global for my taste.

AstroCB
  • 12,337
  • 20
  • 57
  • 73
Daniel Magliola
  • 30,898
  • 61
  • 164
  • 243

3 Answers3

2

Can you wrap the function they are using as their event handler? Take this contrived example:

var someObject = {
    keyDownListener: function() {
        alert('something was pressed!');
    }
}

We could replace keyDownListener with our own method that accepts the event object.

var someObject = {
    keyDownListener: function() {
        alert('something was pressed!');
    }
}

var oldKeyDownListener = someObject.keyDownListener;
someObject.keyDownListener = function(event) {
    oldKeyDownListener(); // Call the original
    // Do anything we need to do here (or before we call the old one!)
}

If you can get inside the function, you can also inspect the arguments object. Your event object should be in there (the first item, I believe).

var f = function() {
    console.log(arguments);    
}

f(); //= Logs []
f(1); //= Logs [1]
f(1, 'something'); //= Logs [1, 'something']

EDIT (In response to the comment below).

If you can "hijack" the method, here's ONE way you could it. I'm not certain if this is a good approach but if you have all these constraints, it will get you what you want. Basically what this code does is it searches for any elements that have an onclick attribute on them and changes the method signature to include the event object.

We can then wrap the original listener to pull the event object out of arguments and then pass execution back to the original function.

Doing this will get you what you want (I think?). See this code:

HTML:

<a href="#" onclick="javascript:myFunction(1, 2, 3);">Click Me</a>​

JavaScript:

window.myFunction = function(one, two, three) {
    console.log("one: " + one + ", two: " + two + ", three: " + three);
}

var originalMyFunction = window.myFunction;
window.myFunction = function() {
    var event = arguments[arguments.length - 1]; // The last item in the arguments array is our event object.
    console.log(event);        
    originalMyFunction.apply(this, arguments);
}

$('[onclick]').each(function() {
    var s = $(this).attr('onclick');
    var lastIndex = s.lastIndexOf(')');

    var s2 = s.substring(0, lastIndex);
    var s3 = s.substring(lastIndex, s.length);

    var s4 = s2 + ', event' + s3;

    $(this).attr('onclick', s4);
});

You can see it working in this fiddle: http://jsfiddle.net/84KvV/

EDIT 2

If you wanna get really fancy with it, you could even automate the wrapping of the functions. See this fiddle: http://jsfiddle.net/84KvV/2/.

Please note that this is expecting the function strings to be in a certain format so that it can parse it (functionName(...)). If it's not that in that format, this exact code will not work :)

Jason L.
  • 2,464
  • 1
  • 23
  • 41
  • No, I can't. Your example is not contrived at all, that's what I'm doing everywhere already :-) ... The problem is that I have a ... I can wrap blah(), but blah still doesn't receive the event, it receives the parameters explicitly set in the inline onclick. Thank you for your answer though! – Daniel Magliola Jul 20 '12 at 20:10
  • 1
    I like your style :-) But yeah, this is what I was trying to avoid. In my highly subjective scale of things I'd rather not do, this one totally trumps just having a keyup/keydown handler, and a global variable to find the store of shift. It's *way* less code and much simpler. I was just hoping there'd be a magic way, or some equivalent to window.event in Firefox that I hadn't found. Thank you!! – Daniel Magliola Jul 20 '12 at 20:35
  • 1
    In my highly subjective scale of things I'd rather not do, this is one I'd try to avoid as well :) But this is also what makes JavaScript so much fun - we can hijack most anything to make it work the way we want.. or totally mess it up :P As for window.event in Firefox, I will post a second answer (this one is getting long) that can show you how to "mock" it. I'm in the middle of a project that involves making newer browsers work like IE5/6. Don't ask :) Not sure if it will work for inline event handlers, but maybe? – Jason L. Jul 20 '12 at 20:43
2

As mentioned in my comment, it IS possible to make window.event exist in browsers that are not IE. It requires you to wrap attachEvent/addEventListener. It would go something like this:

var oldAddEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function(type, listener, useCapture) {
    var wrapped = function(event) {
        window.event = event;

        listener(arguments);
    }

    oldAddEventListener.call(this, type, wrapped , useCapture);
}

As I said, I'm not sure if this will work for inline event listeners, but it might :) If not, at least it's here as a reference for those looking for something similar.

Jason L.
  • 2,464
  • 1
  • 23
  • 41
  • THAT is Hardcore! That's the kind of thing I was hoping jQuery would have :-D Thank you!! – Daniel Magliola Jul 20 '12 at 20:55
  • 3
    If you think that's nuts, you should see the library we have built here at work that shims Chrome, Firefox, Safari, and IE7+ to work like IE5/6. The app was original IE only, and there was just too much JavaScript to change (300k+ lines), so we're building a library to make all the newer browsers work like the older crappy ones. Reverse jQuery, if you will :) Doing element.attachEvent on Chrome feels weird, haha :D – Jason L. Jul 20 '12 at 20:59
0
function eventlessHandler(myVal) {
    var event = window.event || eventlessHandler.caller.arguments[0];
    // some work
}    

It may be needed traverse .caller several times depending on actual call chain.

Tested this on IE6, IE11 and latest Chrome. Should work on most other browsers too.

See also How do you find out the caller function in JavaScript?.

Vadzim
  • 24,954
  • 11
  • 143
  • 151