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

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