Skip to content

Instantly share code, notes, and snippets.

@maettig
Forked from 140bytes/LICENSE.txt
Created November 21, 2011 16:13
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save maettig/1383091 to your computer and use it in GitHub Desktop.
Save maettig/1383091 to your computer and use it in GitHub Desktop.
getElementsByClassName in 140byt.es

Several getElementsByClassName() prototype methods in about 140 bytes (more or less). Useful for Internet Explorer <9.0 and (few) other old web browsers that do not support HTML5. Done for 140byt.es.

All "full" versions perfectly match the W3C specification, as far as I know. They support multiple class names in every order and class names that start with or contain dashes or nonascii characters.

The short version (138 bytes) does not support searching for multiple class names and fails when the query string contains any whitespace character.

The "annotated" version is a compromise with a few restrictions (see the comment below). It supports searching for multiple class names and should work in most web browsers.

/**
* This is the long version with support for multiple classes, but it's simplified to make it as
* short as possible (137 bytes). It uses .all (should be supported by most modern web browsers for
* compatibility reasons) instead of .getElementsByTagName('*'). It does not support tab characters
* (neither in the query string nor in the class attributes) and fails if the query string starts
* with whitespace characters.
*/
function(a, b, c, d) //class names and three dummy arguments
{
c = []; //can't build a NodeList, use an Array
for (d in b = //iterate all properties in the NodeList
this. //to be used in a prototype
all //shortcut for getElementsByTagName('*')
)
(b[d].className || '' //for-in also returns "length", skip this
).match( //if the elements class attribute matches
a.replace(/(\S+) */g, //match names in query string, remove spaces
'(?=(^|.* )$1( |$))')) //all names become positive lookaheads
&& c.push(b[d]); //then append to the Array
return c //return the Array
}
// 167 bytes.
function(a,b,c,d){c=[];for(d in b=this.getElementsByTagName('*'))(b[d].className||'').match(a.replace(/\s*(\S+)\s*/g,'(?=(^|.*\\s)$1(\\s|$))'))&&c.push(b[d]);return c}
// 155 bytes. Works in IE9+ only.
function(a){return[].filter.call(this.getElementsByTagName('*'),function(b){return b.className.match(a.replace(/\s*(\S+)\s*/g,'(?=(^|.*\\s)$1(\\s|$))'))})}
// 72 bytes. Works in IE8+ only.
function(a){return this.querySelectorAll(a.replace(/\s+(?=\S)|^/g,'.'))}
function(a,b,c,d){c=[];for(d in b=this.getElementsByTagName('*'))(b[d].className||'').match('(^|\\s)'+a+'(\\s|$)')&&c.push(b[d]);return c}
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2011 Thiemo Mättig <http://maettig.com/>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
{
"name": "getElementsByClassName",
"description": "Prototype methods for Internet Explorer <9.0 and other old web browsers that do not support HTML5.",
"keywords": [
"class",
"dom",
"ie",
"prototype"
]
}
<!DOCTYPE HTML>
<div id="fragment">
<h1 class="findMe-not">Should not match</h1>
<h1 class="test findMe">Should match first</h1>
<p class="findmE">Should not match</p>
<p class="findMe" id="forInFailsInIE">Should match second</p>
<blockquote class="x-findMe">
<p class="findMeNot not">Should not match</p>
<p class="do findMe too">Should be red and third</p>
</blockquote>
</div>
<script type="text/javascript">
function highlight(node)
{
if (!node.getElementsByClassName) return;
var elements = node.getElementsByClassName('findMe');
for (var i = 0; i < elements.length; i++)
{
elements[i].style.background = 'yellow';
elements[i].firstChild.data += ' [' + (i + 1) + ']';
}
elements = node.getElementsByClassName('too findMe');
for (var i = 0; i < elements.length; i++)
{
elements[i].style.background = 'red';
}
}
var fragment = document.getElementById('fragment');
var clone = fragment.cloneNode(true);
fragment.parentNode.appendChild(clone);
highlight(fragment);
clone.constructor.prototype.getElementsByClassName = function(a, b, c, d)
{
c = [];
for (d in b = this.all)
(b[d].className || '').match(a.replace(/(\S+) */g, '(?=(^|.* )$1( |$))')) &&
c.push(b[d]);
return c
}
highlight(clone);
</script>
@maettig
Copy link
Author

maettig commented Nov 24, 2011

@tkissing, I'm sorry, but I don't think it's helpful to remove characters from a regular expression if you don't really understand what it does. What you did (amongst other side effects) is basically what I did now: I removed support for multiple classes. Now the short version searches for a single class only (spaces are forbidden in the query string). In return I removed all restrictions from the long version. Now it should perfectly match the specification.

@tkissing
Copy link

@maettig: Yeah, I was a bit too aggressive in removing characters and didn't think enough, blindly relying on the test.
I added <p class="DO findMeNot either">Should not match</p> to my local test.html as another negative test - just in case. Certainly learned something from it though: Better tests help, getting too excited doesn't :)

@peterjaric
Copy link

Running IE9 in 'Browser Mode: IE7' and 'Document Mode: IE7 standards' and doing this (adding the version in index-full.js) on this page:

document.getElementsByClassName = function(a,b,c,d){c=[];for(d in b=this.getElementsByTagName('*'))d>=0&&b[d].className.match(a.replace(/\s*(\S+)\s*/g,'(?=(^|.*\\s)$1(\\s|$))'))&&c.push(b[d]);return c}
document.getElementsByClassName('comment gist-comment').length

results in 0.

Running this in Chrome 15:

document.getElementsByClassName('comment gist-comment').length

Results in 23.

Should I have added it to something else than document? It works for single class names, though.

The mistake probably lies with me, but you have another test case, now :)

@maettig
Copy link
Author

maettig commented Nov 24, 2011

Wow, this is strange. The reason is the for-in loop I'm using. This approach fails in IE7 and IE8 for elements with an ID. Here is a simple test:

<ul>
  <li>Returns "0"
  <li id="identifier">Returns "1" in Firefox/Opera but "identifier" in IE7/IE8
</ul>
<script>
  var d, b = document.getElementsByTagName('UL')[0].getElementsByTagName('*');
  for (d in b)
    document.write('<li>"' + d + '" (' + (d >= 0 ? 'ok' : 'non-numeric') + ')');
</script>

I solved the problem by replacing my d>=0 (something like this is needed because the for-in loop also returns the "length" property) with the ||'' mentioned before. Same problem in @eliperelman's Array.filter.

@ybop
Copy link

ybop commented Nov 30, 2011

Given that the only browsers that don't natively implement getElementByClassName are old versions of IE I've used "all" instead of getElementsByTagName using only 132 bytes.

function(t,c)
{
  var e,Z=[],L=(e=t.all).length;
  while(L--)e[L].className.match(c.replace(/\s+(?=\S)|^/g,'.'))&&Z.push(e[L]);
  return Z;
}

But in the real world where speed counts I would use

function(t,c)
{
  if(t.getElementsByClassName)return t.getElementsByClassName(c);
  var e,Z=[],L=(e=t.all).length,R=new RegExp(c.replace(/\s+(?=\S)|^/g,'.'));
  while(L--)R.test(e[L].className)&&Z.push(e[L]);
  return Z;
}

...which blows out to 210 bytes but given that native implementations are typically around 500 times faster, it's well worth including the test. Also, moving the regex out of the loop improves the speed by at least 60%.

@maettig
Copy link
Author

maettig commented Dec 1, 2011

Nice, thanks. all also works in Opera. And Firefox. Really? Seems so. Now my annotated.js is 137 bytes. So, mission accomplished. An (almost) full replacement for getElementByClassName that works in IE6 and IE7 without depending on other code.

function(a,b,c,d){c=[];for(d in b=this.all)(b[d].className||'').match(a.replace(/(\S+) */g,'(?=(^|.* )$1( |$))'))&&c.push(b[d]);return c}

Please note you are using the wrong regular expression. That's the one designed for querySelectorAll. You are right about the speed but that's not what this challenge is about.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment