views:

264

answers:

4

In this code, Firefox sees 'this' as the element that was clicked and passes the href attribute through the right way.

IE seems to think that 'this' as [object window] instead. How would I get this to work the same way in both browsers?

Note: jQuery would be lovely, but is not an option for this project

var printElem = getElementsByClassName('print', 'a');
for(i in printElem){
 ObserveEvent(printElem[i], 'click', function(e){
  window.open(this.href, '', 'location=0,menubar=1,resizable=1,scrollbars=1,width='+810+',height='+700);
  cancelEvent(e);
 });
}
+4  A: 

You can use the event target, like this:

    ObserveEvent(printElem[i], 'click', function(e){
            var target = e.target || e.srcElement;
            window.open(target.href, '', 'location=0,menubar=1,resizable=1,scrollbars=1,width='+810+',height='+700);
            cancelEvent(e);
    });
Alexander Gyoshev
Damn... I was most of the way there, I had e.target I was missing the e.srcElement part. Thanks
SeanJA
Reminds us of the days before jQuery, heh
Alexander Gyoshev
This will fail if `printElem[i]` contains children and one of *those* is clicked.
Crescent Fresh
At the moment that wouldn't happen, but that could happen in the future...
SeanJA
Well, in the future, you could write a `contains` function to determine whether the target element is within the printElem[i]. But if it doesn't have any children, keep it simple ;)
Alexander Gyoshev
@SeanJA: good to know.
Crescent Fresh
@Alexander: how does a `contains` implementation help? You need to traverse *up* to the clicked anchor. All this is solved by an `ObserveEvent` implementation that **retains references to `this`**, like the question asked for in the first place.
Crescent Fresh
@crescentfresh: If only I could convince the boss that a switch to jQuery/any other full javascript framework would be beneficial
SeanJA
Well, yes, they have a great event handler implementation - and indeed, retaining the `this` reference is important - I am just proposing a solution to the problem, rather than answering the question directly.
Alexander Gyoshev
@SeanJA: OK I'm adding my own answer to help out here. I think you can use it.
Crescent Fresh
+3  A: 

The problem almost certainly stems from the ObserveEvent function using IE's attachEvent method, which (unlike addEventListener in other browsers) doesn't set this to the object being observed in the event handler.

Tim Down
+2  A: 

I'm adding my two cents here as I think you can use it.

There was a contest 4 years ago to see who could write the best addEvent implementation. One of the main problems it tried to address was retaining this inside the event handler.

Here is one contest entry written by the creator of jQuery:

function addEvent( obj, type, fn ) {
  if ( obj.attachEvent ) {
    obj['e'+type+fn] = fn;
    obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
    obj.attachEvent( 'on'+type, obj[type+fn] );
  } else
    obj.addEventListener( type, fn, false );
}

(And of course to be symmetrical):

function removeEvent( obj, type, fn ) {
  if ( obj.detachEvent ) {
    obj.detachEvent( 'on'+type, obj[type+fn] );
    obj[type+fn] = null;
  } else
    obj.removeEventListener( type, fn, false );
}

Rename it to "ObserveEvent" if you wish.

There you have it, an 8 line implementation you can drop into your codebase - No libraries, no frameworks. And your this reference inside your handler works just fine.

Crescent Fresh
Cool, thanks! I still would love to transition to a framework to add a bit more consistency to the code.
SeanJA
+1 from me :) doesn't prototype depend on the method, though? i.e. is the patching safe?
Alexander Gyoshev
@Alexander: very gracious of you :) I think I know what you mean. I don't condone patching the prototype library, no. However as I read the question I think they just ripped the code from the innards of prototype, in which case they can patch their own code to their heart's content.
Crescent Fresh
Yes @crescentfresh, you are right, it was ripped unceremoniously from it before I joined the company
SeanJA
No need to rely on function decompilation for something as simple as `addEvent`.
kangax
@kangax: I know. Aesthetically speaking I actually hate this implementation myself. Resig can get way too clever sometimes, with a high WTF factor. Anyway, as an answer it was meant to specifically address SeanJA's issue with `this`, and to mention there was a whole contest around this very problem.
Crescent Fresh
@kangax: Oh and also to show how something as simple as `addEvent` can be.
Crescent Fresh
@crescentfresh It's good to hear you agree :) This is of course not just an aesthetic issue. There are real implications from this "clever" approach (I wrote about why decompilation sucks some time ago - http://thinkweb2.com/projects/prototype/detecting-built-in-host-methods/, http://thinkweb2.com/projects/prototype/those-tricky-functions/). Ironically, we still use decompilation in Prototype.js (for compatibility reasons), but are removing it in future (back-incompatible) releases.
kangax
@kangax: nice writeups in both articles. Good work. Relying on "function decompilation" is indeed non-standard, akin to `navigator` sniffing and prone to suckage. (I never realized all the browser differences wrt to `Function.prototype.toString()` btw, thanks). I do just want to point out however that Resig is nowhere relying on any browser-specific string representation of the function. He uses the string representation, sure, but does not test against it (in any "conditional" sense).
Crescent Fresh
@crescentfresh No, of course, he doesn't. The problem here is that by specification, 1 single function object can return 2 different representations. In a similar manner, 2 different function objects can return 1 identical representation. This on its own already breaks the premise of `addEvent` being able to filter out identical submissions (which is what `addEventListener` does, for example). It also means that attaching 2 different event handlers can result in second one overwriting first one (if their representation is identical). You get the point :)
kangax
@kangax: if I had say two `Class` instances `a` and `b`, each with a `doClick` method (i.e. on the class `prototype`), and I go: `addEvent(el, 'click', a.doClick); addEvent(el, 'click', b.doClick);`, it fails. `addEventListener` gets it right and knows they are different functions (adds each as it should). Resig's `addEvent` sees them as the same and overwrites, effectively discarding the first `addEvent()`. I see know, yes. Thank you.
Crescent Fresh
+2  A: 

Another option: Create a closure to scope the element for the handler for each element:

var printElem = getElementsByClassName('print', 'a');
for(var i in printElem){
  (function(elem) { // elem === printElem[i] at time of execution later
    ObserveEvent(elem, 'click', function(e){
      window.open(elem.href, '', 'location=0,menubar=1,resizable=1,scrollbars=1,width='+810+',height='+700);
      cancelEvent(e);
    });
  })(printElem[i]); // immediately call the temp function with our element
}
gnarf