2

What's going on should be pretty basic, I've "dumbed down" everything to a simple example to hopefully be better understandable here.

I declare a "global" function in one of my scripts:

function byClass(cl)
{
 return arguments[1]?
  arguments[1].GetElementsByClassName(cl):
  document.getElementsByClassName(cl);
}

What it does is: you call byClass() with a string argument (=cl) and it returns a document.getElementsByClassName result. If you also specify an Element of the page as the optional 2nd argument, then the function will perform the .getElementsByClassName only "inside" of the specified element.

Example:

<span class="inner">Outside</span>
<p id="outer">
 <span class="inner">Inside</span>
</p>

var both=byClass('inner'); ==> [both 1st and 2nd span]

var out=document.getElementById('outer');
var in =byClass('inner',out); ==> [only 2nd span from inside P element]

What I want to happen now is "attach" that function to the HTMLElement Prototype like so:

HTMLElement.prototype.byClass=function(cl){byClass(cl,this);}

So when .byClass() is added to an element in the code, it will perform the byClass function just "inside" the element it was attached to. Applied as code, this will work something like so:

var out=document.getElementById('outer');
var in =out.byClass('inner'); ==> [only 2nd span from inside P]

This all works fine, having no trouble so far.

However, when adding a simple byClass() call to an element's "onClick" event, it doesn't perform the global byClass() but basically a this.byClass() call "inside" the element that triggered it.

var out=document.getElementById('outer');
var in1=byClass('inner'); ==> [works as expected]
var in2=out.byClass('inner'); ==> [works as expected]

but

<input type="button" onclick="console.debug(byClass('inner'));">

will perform an element-specific this.byClass() instead of the global byClass() when clicked.

How can I avoid that...? I'm lost.

I know I could call it as window.byClass() from the onClick event, but I want to keep things simple and leave nothing up to my luck with "maybe I won't forget adding window. before it"...

Grateful for any help or ideas!

No jQuery please. No "don't extend the DOM" comments please.

Thank you. :)

Rob
  • 41
  • 1
  • 5
  • this interesting question. Inline handler first try find function in `HTMLElement.prototype` and then in global scope, i create [fiddle](http://jsfiddle.net/nnqz3Lc0/1/) with same behavior for standard `onblur`. So as workaround you can or rename your function in prototype, or pass some additional parameter and check it – Grundy Aug 19 '15 at 07:16
  • I tried to pass an additional parameter already, also tried to check the source with Function.caller. But the issue isn't the evaluation, it's the triggering, as you say - the inline handler looks through its Element's prototype first before reverting to global. The trouble is: if I add an additional optional argument to the Element.prototype.byClass() function (like byClass(true) or so), then that additional argument will not only be triggered if the function is called from an inline Event, but also when you call it from a line of code, like var x=y.byClass(), breaking that functionality... – Rob Aug 19 '15 at 10:23
  • 1
    Why not `elem.addEventListener` in _script_ instead of inline with the _HTML_? – Paul S. Aug 19 '15 at 12:07
  • Don't use inline attribute event handlers. Just don't. – Bergi Aug 19 '15 at 12:22
  • Btw: Don't extend the DOM :-) You could have made your example much simpler by using a function that already exists on the `Element` prototype, so that you don't need to extend the DOM to demonstrate your problem. – Bergi Aug 19 '15 at 12:23
  • @PaulS. I'm not doing this just for me, so I (as in: the script) need to be prepared for non-"pro" approaches. – Rob Aug 19 '15 at 12:30
  • @Bergi What built-in function would you be referring to? If you mean the standard Element.getElementsByClassName() function, then no, that's not an option because the whole idea is to avoid that. (No, you don't need to understand.) :) – Rob Aug 19 '15 at 12:33
  • 1
    @Rob: Yes, for example a global `getElementsByClassName` function would have demonstrated your problem just as well (like `clear` in [this question](http://stackoverflow.com/q/7165570/1048572)) – Bergi Aug 19 '15 at 12:36
  • @Rob but you anyway call it inside, how you avoid it? – Grundy Aug 19 '15 at 12:36
  • @Rob if you teach them the best way to start with then they'll never be so _non-"pro"_ and everyone is better for it, they are already _"pro"_ enough to extend the _DOM_. – Paul S. Aug 19 '15 at 13:26

2 Answers2

1

How can I avoid that...?

By not using event handler attributes in HTML. There's not really a way around that.

Of course, you can also avoid putting .byClass properties on your elements in the first place, but the problem persists for all names of the elements and documents properties.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

Inline handler first try find function in HTMLElement.prototype and then in global scope.
You can use an ugly workaround with non-standard Function.caller function, something like in snippet below

function bl() {
  console.log('blur');
}

function byClass(cl) {
  console.log(arguments.length > 1 ? 'proto' : 'global', this, arguments, arguments.length);

  return arguments[1] ?
    arguments[1].getElementsByClassName(cl) :
    document.getElementsByClassName(cl);
}
HTMLElement.prototype.byClass = function byClassEl(cl) {
  console.log('proto', this, arguments, byClassEl.caller);
  if (!this.onclick || byClassEl.caller !== this.onclick) { //if called not from onclick -> run with two parameters
    byClass(cl, this);
  } else { //otherwise run with one
    byClass(cl);
  }
}
console.log('first, should be global'), byClass('inner');
var x = document.getElementById('outer');
console.log('second, should be proto'), x.byClass('inner');
<span class="inner">Outside</span>
<p id="outer">
  <span class="inner">Inside</span>
</p>
<input type="button" onclick="byClass('inner');" value="button" />
<br/>
<br/>Sample for standart onblur:
<input type="button" onclick="onblur()" onblur="bl();" value="button2" />

UPDATE: a bit more generic solution

function bl() {
  console.log('blur');
}

function byClass(cl) {
  console.log(arguments.length > 1 ? 'proto' : 'global', this, arguments, arguments.length);

  return arguments[1] ?
    arguments[1].getElementsByClassName(cl) :
    document.getElementsByClassName(cl);
}
HTMLElement.prototype.byClass = function byClassEl(cl) {
  console.log('proto', this, arguments, byClassEl.caller);
  if (checkProp(this, byClassEl.caller)) { //if called not from onclick -> run with two parameters
    byClass(cl, this);
  } else { //otherwise run with one
    byClass(cl);
  }
}

function checkProp(source, caller) {
  for (var i in source) {
    if (i.startsWith('on')) {
      var prop = source[i];
      if (prop && typeof prop === "function" && prop === caller) {
        return false;
      }
    }
  }
  return true;
}
console.log('first, should be global'), byClass('inner');
var x = document.getElementById('outer');
console.log('second, should be proto'), x.byClass('inner');
<span class="inner">Outside</span>
<p id="outer">
  <span class="inner">Inside</span>
</p>
<input type="button" onclick="byClass('inner');" value="button" />
Grundy
  • 13,356
  • 3
  • 35
  • 55
  • Hm, yes, that would solve the problem for onclick Events, but then I'd have to apply this to all other possible/sensible Event types as well... just checking for onclick will still not make this work with onmousedown Events etc. But I will try giving the prototype's assigned function a different name, never occured to me to do that (behind the equals sign), maybe I can work something out with that. – Rob Aug 19 '15 at 11:27
  • @Rob, more generic possibly add function that check all function property from `this` started with `'on'` – Grundy Aug 19 '15 at 11:33