There’s an important compatibility regression coming up with the releases of Firefox 3 and Safari 3.1 (and any other browser that will natively implement getElementsByClassName) concerning old releases of Prototype (pre 1.6).
In talking with Tobie Langel, of the Prototype team, he recommends the following:
We’ve already deprecated document.getElementsByClassName and Element#getElementsByClassName [in Prototype 1.6].
The easiest workaround, which does not require upgrading, is to replace calls to document.getElementsByClassName(‘foo’) with $$(‘.foo’) and calls to Element#getElementsByClassName(‘foo’) with Element#getElementsBySelector(‘.foo’) or Element#select(‘.foo’) (version >= 1.6.0) depending on which version of Prototype you are running.
A number of users noticed this problem during the Firefox 3 betas and after the release of Safari 3.1:
If you have a site that’s using an old version of Prototype, and getElementsByClassName, then it’s recommended that you either upgrade or perform the above steps (probably sooner, rather than later, so you don’t hit compatibility problems).
How This Issue Came About
It’s important to realize how a problem like this can come about so that you can prepare for it within your development. This core issue arises from the fact that Prototype was implementing a property on the global document
variable which was, eventually, codified into the HTML 5 standard and implemented in some browsers.
Looking at their code we can see something like this:
if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ // ... };
This code in, and of, itself isn’t necessarily the problem. If you implement a JavaScript library that completely, 100%, implements a specification then it’s probably safe to do the above in that manner (base2 is a good example of a library that does this well).
A problem occurred because, in their implementation of the method, they were adding new methods to the returned array (such as .each
). This meant that users began to use the method in this manner:
document.getElementsByClassName("monkey").each(Element.hide);
Which subsequently broke when an actual getElementsByClassName implementation came on the scene (since the result returned from the method wasn’t ‘enhanced’ with the each functionality – amongst other methods).
Attempting to implement any sort of future specification can be a real fools game. Since standards are often imperfect (such as not providing an .each
mechanism) or frequently change – attempting to match that functionality with a library can be incredibly difficult. More often than not it’s simply a better idea to implement brand new methods that can never conflict with upcoming functionality (as the Prototype team has done in the 1.6 release).
Tobie Langel (March 26, 2008 at 3:28 am)
As a matter of fact, Dean had to deprecate
document.getElementsByClassName
in Base2 for exactly the same reason.Ryan (March 26, 2008 at 3:39 am)
Globals and overriding DOM objects are a firestorm. just, don’t do it…
Rik (March 26, 2008 at 6:25 am)
Actually, Base2 got the same problem with the Selector API but it’s gonna be fixed soon. See http://groups.google.com/group/base2-js/browse_thread/thread/39afd9e4a34159ef/
Steve Laniel (March 26, 2008 at 7:45 am)
I’m not an experienced JavaScript developer, so pardon me if this question is naive, but: why was the Prototype code playing in the ‘document’ namespace to begin with? Why weren’t they using, say, prototype.getElementsByName()?
Chris Dary (March 26, 2008 at 8:03 am)
@Steve:
It was probably because they were attempting to leverage when browsers caught up to spec and started to use a native (and much faster) implementation of getElementsByClassName. Which is understandable.
The trouble came when they forgot that they were overloading their array with extra methods – this broke spec, and as a result caused errors when the native implementation existed.
Neil Anderson (March 26, 2008 at 8:41 am)
And was the prototype getElementsByClassName returning an array of nodes or a nodelist?
Ryan is right ‘just don’t do it’
John Resig (March 26, 2008 at 8:41 am)
@Tobie and Rik: Good catch, I totally forgot that base2 had the same problem!
Andi (March 26, 2008 at 9:54 am)
I totally agree: Just use the framework’s methods like $$() to append framework specific method calls.
I think it’s ok to implement W3C methods for older browsers. but Prototype should not extend the results of those methods with its own extensions. It misleads you to write buggy code.
Dean Edwards (March 26, 2008 at 12:25 pm)
Actually, I removed getElementsByClassName() from base2 because I could not return a live node list. There is an important distinction between the Selectors API and other DOM methods. The Selectors API returns a StaticNodeList object that is not affected by further changes to the DOM. Other DOM retrieval methods return live lists that are immediately updated whenever you make a change to the DOM. Obviously, this is impossible to do with JavaScript libraries.
Weston Ruter (March 26, 2008 at 12:35 pm)
Actually, if developers were conscious of the fact that document.getElementsByClassName is an implementation of an upcoming standard, they should have passed the results of the call through the $A utility method (since they would be conscious that the result should be a DOMNodeList and not an Array), so:
$A(document.getElementsByClassName("monkey")).each(Element.hide);
If they did this, then Firefox 3 and other browsers that implement document.getElementsByClassName will encounter no problems.
Tom (March 26, 2008 at 12:54 pm)
looking at prototype 1.5.1.1, they do not even check for the existence of document.getElementsByClassName before creating it, they just check to see if they can use XPATH or not to implement the function. wouldn’t this automatically override the native function?
Neil Anderson (March 26, 2008 at 6:35 pm)
What should have been done was to add a method to their own namespace. One that checked whether document.getElementsByClassName ran natively and then used that, for speed, or did their own stuff. One that always returned an array of nodes.
I may be off point here but the point of a library is to iron out the differences between browsers rather than to introduce, for me, rather sinister error chances.
We all knew that getElementsByClassName was going implemented sometime, so why would anyone pop it on the document?
Now you’ve all fixed it – great; but you shouldn’t have had to.
Andrew Dupont (March 27, 2008 at 12:04 am)
OK. First things first:
document.getElementsByClassName
had existed in several forms (including Prototype’s) for years, long before there was any hint of standardization. We deprecated our version once it became clear that HTML5/Firefox were doing their own thing.@Steve: The answer will get us made fun of, but we treat the API as a UI: why should you place a
getElementsByClassName
method in a different place than yourgetElementsByTagName
method? We aim for intuitive APIs.Ted Henry (March 27, 2008 at 2:42 am)
@Andrew: You may feel all warm and fuzzy with your “intuative API” but it has bit the Prototype crew many times (and will continue to do so.) Namespacing is a best practice in every area of programming…except for Prototype programmers. Odd situation you guys have over there.
Andrew Dupont (March 27, 2008 at 3:55 pm)
@Ted: Whoa, you busted out the sarcastic quotation marks. Harsh!
The Prototype crew is doing just fine, by the way. But thanks for your concern.
Dean Edwards (March 27, 2008 at 8:16 pm)
@Andrew – kid yourself if you like but Prototype has experienced many RECENT problems for exactly the reasons that “sarcastic” Ted points out.
Dean Edwards (March 27, 2008 at 8:18 pm)
Oops. Inadvertent caps-lock on “recent”. :-)
Andrew Dupont (March 28, 2008 at 5:49 pm)
@Dean: I do happily grant we’ve run into naming collisions recently, most notably with instance methods on HTML nodes. We’re trying to build a library with an expressive syntax. There are obviously trade-offs with our approach, but we’re trying to maximize our burden (Prototype Core’s, that is) and minimize the Prototype user’s burden.
In this case, we deprecated a method for exactly the same reason you deprecated your version of the same method — we couldn’t guarantee it would behave the same way as the standard. In another case,
Array.map
, we changed our implementation in order to ensure it would behave exactly the same way as the method of the same name implemented by Firefox.Ted didn’t seem like he wanted to have a meeting of the minds; more like he wanted to deliver some public snark in the comments of a third-party’s blog. I responded in kind.
Craig Buchek (March 31, 2008 at 10:22 pm)
I’m not sure I agree with the verdict against Prototype here. It seems like Prototype actually set the de facto standard for getElementsByClassName(). So it’s hard to blame them when someone else takes that standard and implements it in an incompatible way.
I suppose you could take the stance that no existing object should ever be enhanced by a library. But that’s not possible in JavaScript. Sure the jQuery library only adds a single object to the global namespace. But what if a browser decided to implement the jQuery de facto “standard” to speed up apps using jQuery? So while jQuery is less likely to run into the issue, I don’t think you can claim that it has “solved” the issue.
To me, the problem is more the assumption that if you have X (the getElementsByClassName method) then you also have Y (arrays that have an each method).
Bahr-El-Hawa (April 2, 2008 at 5:09 am)
Ted: Whoa, you busted out the sarcastic quotation marks. Harsh!
The Prototype crew is doing just fine, by the way. But thanks for your concern.
Scott Shattuck (April 12, 2008 at 12:59 pm)
Implementing an upcoming standard incompletely/inaccurately, or failing to test for previous definition of a compliant function, can be dealt with using the easy mantra “just don’t do it”. After all, replacing a C-based version with JS or worse, buggy/inconsistent JS, can’t possibly help the development community at large.
Of course, the fallacy of object testing is that existence === conformant. Just because a function exists doesn’t mean it works or performs well. What then? Does a library leave the buggy/slow version in place or repair it? Most tend toward repair to help stabilize the platform. This seems like “a good thing”, but it relies on something the community (incorrectly imho) has deprecated almost out of existence — browser/version detection.
The bigger looming problem is likely to be class and event names. Since these are now starting to be defined in various specs and libraries without any particular oversight it’s likely that we’ll see numerous namespace collisions over the coming years until the specifications stabilize in both the AJAX and XML-focused groups (WHAT, XForms, XML Events, ECMA, OpenAJAX, et. al.)
Ted Henry (April 17, 2008 at 3:58 pm)
@Andrew: I think you are missing the point. Prototype’s design inherently creates this situation where you need to deprecate APIs and have the developers using Prototype update their code. If a page using Prototype is left to fallow on the web then it could end up broken when these conflicts arise. Using namespacing strategies reduces the problems. I will never understand how the Prototype core and user developers can accept the lack of namespace design decisions in Prototype. It is breaking a cardinal rule for building maintainable software.