42

I've got two almost identical simple JS fiddles calling a function on select change. Function name is the same as select ID in both cases, but for some reason the first fiddle works just fine, and the second one fails with a JavaScript error is not a function:

http://jsfiddle.net/AZkfy/7/ - works fine in FF9 (Linux), Chromium 16 (Linux), IE8 (Windows):

<script>
    function border(border) { alert(border); }
</script>

<select id='border' name='border' onchange='border(this.value)'>
    <option value='foo'>foo</option>
    <option value='bar'>bar</option>
</select>

and

http://jsfiddle.net/cYVzk/ - fails in FF9 (Linux), Chromium 16 (Linux), IE8 (Windows):

<script>
    function border(border) { alert(border); }
</script>

<form>
<select id='border' name='border' onchange='border(this.value)'>
    <option value='foo'>foo</option>
    <option value='bar'>bar</option>
</select>
</form>

First of all I fail to understand why the first one works fine, and the second one fails.

Second - are there any JS specifications or restrictions regarding the conflicting JS function names and element ID?

Oleg Mikheev
  • 17,186
  • 14
  • 73
  • 95

3 Answers3

63

This is a legacy scope chain issue originating from JavaScript 1.0 to 1.3 when there was no distinction between the programming language and what we now call a DOM API ("Dynamic HTML" back then).

If your form control (here: a select element) is part of a form (descendant of a form element), then the Form object that represents the form element is third-next in the scope chain of code in the control's event-handler attribute values (second-next is the form control object itself, next is the Variable Object of that code).

JavaScript™ was designed by Brendan Eich (then at Netscape) as a programming language that is easy to use for beginners and that works well with HTML documents (as complement to Sun's Java; hence the ever-confusing name). Because in those early days language and (Netscape) DOM API were one, this (over)simplification applied to the DOM API as well: A Form object has the names of the controls contained in the form that it represents as the names of its properties that refer to the corresponding form control objects. IOW, you can write

myForm.border

which is the proprietary shorthand of the standards-compliant (W3C DOM Level 2 HTML), but equally backwards-compatible

document.forms["myForm"].elements["border"]

Now, if you use a form control's name in an event-handler attribute value of a form control in a form, like

<form …>
  <… name="border" onchange='border(this.value)' …>
</form>

that is the same as if you had written the half-proprietary

<form …>
  <… name="border" onchange='this.form.border(this.value)' …>
</form>

or the standards-compliant

<form …>
  <… name="border" onchange='this.form.elements["border"](this.value)' …>
</form>

because a potential global border() function is a property of the ECMAScript Global Object which comes last, after the Form object (an object implementing the HTMLFormElement interface in the W3C DOM), in the scope chain.

However, the form control object referred here by border is not callable (does not implement the ECMAScript-internal [[Call]] method or implements it so that it throws an exception when called). So if you try to call the object with border(this.value), a TypeError exception is thrown, which you should see in the script consoles (like "TypeError: border is not a function" in the Developer Tools of Chromium 16.0.912.77 [Developer Build 118311 Linux]).

Microsoft, Netscape's competitor in the 1990s, had to copy that feature for the MSHTML DOM so that code written for Netscape would also run in Internet Explorer (3.0), with JScript (1.0). And Microsoft's competitors copied it to their DOM implementations for exactly the same reason. It became part of a quasi-standard (now called "DOM Level 0").

Then came the DOM Level 2 HTML Specification, a continuing effort to standardize and extend common features of existing DOM implementations at the time. A W3C Recommendation since 2003-01-09, its ECMAScript Language Binding specifies that items of HTMLCollections can be accessed by their name or ID with the bracket property accessor syntax [], equivalent to calling the namedItem() method of the object implementing the HTMLCollection interface.

form element objects and element objects for form controls in forms are items of HTMLCollections in the W3C DOM, HTMLDocument::forms and HTMLFormElement::elements, respectively. But for backwards compatibility in browsers,

document.forms["myForm"].elements["myControl"]

needs to be equivalent to

document.myForm.myControl

So, with the implementations of W3C DOM Level 2 HTML interfaces at the latest, this feature started to apply to elements with ID (id attribute value) as well (which can be seen in Chromium, for example).

As a result, the convenience feature introduced in JavaScript™ 16 years ago still bites you like a bug in client-side DOM scripting today.

If you avoid using the same name or ID for form controls and forms that you use as identifier of user-defined functions, and that are already used for built-in form properties (like action, submit, and reset), then this becomes less of an issue. Also, it is a bad idea to use the same identifier for the function and one of its arguments as (confusing code aside) that makes the function object inaccessible from within the function (the Variable Object of the function context comes first in its scope chain).

PointedEars
  • 14,752
  • 4
  • 34
  • 33
  • @Christoph You're welcome :) Since the question was about IDs, I have looked into this further and added an explanation how W3C DOM 2 HTML relates this issue to element IDs. It should also be noted that the form object comes *third*-next in the scope chain. If it wasn't for disambiguity, we wouldn't need to write `this.value`, only `value`(!). – PointedEars Feb 07 '12 at 00:32
  • 1
    I just ran into this problem using `Chrome Version 94.0.4606.81 (Official Build) (64-bit)` in Oct 2021!!! - it was driving me nuts for a good hour. In my case: ` ` – rolinger Oct 27 '21 at 14:48
  • @rolinger: This markup leaves a lot to be improved. And you should not have a global custom `showPass` function; it should be a method of a user-defined object instead. But if you cannot help the naming of the button (“btn-showPass” would be an alternative) or the definition of the `showPass` function, then you can use `onclick='(function () { return this; }()).showPass()'`, where the function returns a reference to the ECMAScript global object. If needed in several places, you could define `var _global = this;` in the global execution context, and then use `_global.showPass();` instead. – PointedEars Nov 07 '21 at 03:35
  • The inline call to obtain a reference to the global object only works if the global context is not declared to be executed in strict mode. I am actually not sure if a global strict mode declaration (`'use strict';`) is per-file or per-element, or carries over to the global HTML context when used in an included script resource or `script` element content, respectively. – PointedEars Nov 07 '21 at 03:41
5

IE automatically reserves a var ID = domElement; in the global space for each DOM-Element with an ID. Some other browsers adopted this behaviour.

Always try to avoid using same IDs and varnames! Alternatively, use your own namespace in JS to avoid the collisions.

EDIT:

I don't know why one of your examples fails, while the other one works. It might be a simple timing/order of execution -issue caused by the wrapping <form>.

Christoph
  • 50,121
  • 21
  • 99
  • 128
  • as I mentioned in my answer, I assume the form might delay the occupation of the var by the dom-element. So the function is assigned to the var first and later it gets overridden by the dom-element. – Christoph Feb 06 '12 at 10:38
  • For a change, this has nothing to do with MSHTML's making objects for named/ID'd elements accessible through properties of a global host object in the scope chain, and it has nothing to do with timing. It is a legacy scope chain issue and occurs in Standards Compliance Mode as well (while the name/ID problem does not in several browsers except IE). – PointedEars Feb 06 '12 at 12:56
  • 2
    @Christoph What you’re describing is mentioned in the HTML5 spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/browsers.html#named-access-on-the-window-object But it’s [likely to be removed in the future](https://www.w3.org/Bugs/Public/show_bug.cgi?id=11960). – Mathias Bynens Feb 06 '12 at 14:03
  • @Mathias I didn't know, that this applies to the name attribute of some elements as well. So the problem indeed is, that the creation of a variable for the option element is shadowing the function attached to the global object. – Christoph Feb 06 '12 at 14:53
  • @Christoph Creation of a _property_ for the _select_ element. – PointedEars Feb 06 '12 at 17:13
1

http://jsfiddle.net/x79ey/1/

It looks to me like the form tag creates an additional scope around inline event handlers, and form elements are defined as variables in this local scope:

<form>
    <element id="foo"....
    <element onclick="foo is a local variable here"

No variables are auto-defined globally in my tests, but this may vary across browsers/modes.

georg
  • 211,518
  • 52
  • 313
  • 390