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. :)

10
  • this interesting question. Inline handler first try find function in HTMLElement.prototype and then in global scope, i create fiddle 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 Commented Aug 19, 2015 at 7: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... Commented Aug 19, 2015 at 10:23
  • 1
    Why not elem.addEventListener in script instead of inline with the HTML? Commented Aug 19, 2015 at 12:07
  • Don't use inline attribute event handlers. Just don't. Commented Aug 19, 2015 at 12:22
  • 1
    @Rob: Yes, for example a global getElementsByClassName function would have demonstrated your problem just as well (like clear in this question) Commented Aug 19, 2015 at 12:36

2 Answers 2

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.

Sign up to request clarification or add additional context in comments.

Comments

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" />

2 Comments

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, more generic possibly add function that check all function property from this started with 'on'

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.