View Full Version : document.getElementsByClassName()
FF3, Chrome and Opera 9+ have already implemented this method : document.getElementsByClassName() as javascript native, but for a crossbrowser approach, you may use this workaround:
<script type="text/javascript">
onload=function(){
if(!document.getElementsByClassName){
document.getElementsByClassName=function(cn){
var allT=document.getElementsByTagName('*'), allCN=[], i=0, a;
while(a=allT[i++]){
a.className==cn?allCN[allCN.length]=a:null;
}
return allCN
}
}
}
</script>
rangana
12-22-2008, 02:52 PM
Hi Kor,
It would fail on multiple class names like:
<span class="class1 class2">Dummy</span>
...how about:
onload=function(){
if(!document.getElementsByClassName){
document.getElementsByClassName=function(cn){
var rx=new RegExp('\\b'+cn+'\\b');
var allT=document.getElementsByTagName('*'), allCN=[], i=0, a;
while(a=allT[i++]){
rx.test(a.className)?allCN[allCN.length]=a:null;
}
return allCN
}
}
}
rnd me
12-22-2008, 04:16 PM
i liked rangana's take on kor's slim code.
i've seen many versions of this floating around, and this one is pretty good.
i had some time on my hands, so i thought i could tweak it a bit.
i think that since native code performs about 100X faster than js replacements, we should tweak native replacements as much as possible.
The direct string compare is much faster than a regexp, but as we see from kor's code, limited to single class attribs.
Second best to a direct compare, indexOf performs 3-50X faster than a comparable regExp.
This is of limited use however, because it could match partial substring:
ex: searching for "sample" would match "sampleBold".
what we want is the speed of string methods, and the precision of a regexp.
the solution: have the matching code to only work as hard as needed:
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (cn) {
var rx = new RegExp("\\b" + cn + "\\b"), allT = document.getElementsByTagName("*"), allCN = [], i = 0, a;
while (a = allT[i++]) {
if (a.className && a.className.indexOf(cn) + 1) {
if(a.className===cn){ allCN[allCN.length] = a; continue; }
rx.test(a.className) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
}
this performs about 10 - 50% (avg ~20) faster than rangana's version, and it finds the same things.
after checking for any className at all,
it uses the medium speed indexOf to determine if it's worth further examination.
if there is some kind of match, a super fast direct compare is tried.
failing the direct compare, sub-matches are eliminated by rangana's regexp.
perhaps someone else can tweak it even further?
Pretty good development, good job also rnd me :)
oesxyl
12-22-2008, 11:00 PM
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (cn) {
var rx = new RegExp("\\b" + cn + "\\b"), allT = document.getElementsByTagName("*"), allCN = [], i = 0, a;
while (a = allT[i++]) {
if (a.className && a.className.indexOf(cn) + 1) {
if(a.className===cn){ allCN[allCN.length] = a; continue; }
rx.test(a.className) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
}
would be any problem of speed and portability with this?
...
if(a.className && a.className.split(' ').indexOf(cn) > -1){
...
best regards
rnd me
12-22-2008, 11:04 PM
would be any problem of speed and portability with this?
yes.
Array.indexOf is not supported in ie or any browser without 1.6 array methods...
while it could be added in with 1.5 code, that robs peter to pay paul, and i suspect it would be slower in the end.
oesxyl
12-22-2008, 11:18 PM
yes.
Array.indexOf is not supported in ie or any browser without 1.6 array methods...
while it could be added in with 1.5 code, that robs peter to pay paul, and i suspect it would be slower in the end.
good to know, :). that means Opera, Safari and IE?
http://aptana.com/reference/html/api/Array.html
best regards
rnd me
12-22-2008, 11:22 PM
good to know, :). that means Opera, Safari and IE?
best regards
basically anything besides firefox, which already supports document.getElementsByClassName()...
good link btw...
oesxyl
12-22-2008, 11:29 PM
basically anything besides firefox, which already supports document.getElementsByClassName()...
good link btw...
yes, is good, :)
I discover also problems with split in safari, :)
best regards
rangana
12-23-2008, 02:36 AM
Okay, now I understand. You are filtering the element for a match (single class name), otherwise (for multiple classname), it'll go to the RegEx.
Saves resources indeed.
rnd me, I'm confused, on why go all the hassle of those if's statement.
Why not just:
if (!document.getElementsByClassName)
{
document.getElementsByClassName = function (cn)
{
var rx = new RegExp("\\b" + cn + "\\b"), allT = document.getElementsByTagName("*"), allCN = [], i = 0, a;
while (a = allT[i++])
if (rx.test(a.className))
allCN.push(a);
return allCN;
}
}
itsallkizza
12-23-2008, 03:03 AM
He's saying that
if (rx.test(a.className))
is significantly slower than
if (cn===a.className)
So he's avoiding RegExp.test() for elements with a single class name.
EDIT: I missed your edit ;) Ignore my post.
basically anything besides firefox, which already supports document.getElementsByClassName()...
good link btw...
Basically all the new browsers' versions, except, of course, IE :rolleyes: support now the native getElementsByClassName() method. At least Opera 9+ and Chrome do so.
rnd me
12-23-2008, 06:01 AM
lots of discussion calls for further explanation.
i throw in a little general performance tuning strategy to provide a context for what's going on here, and why it matters.
loops are slow.
thus, less work at each iteration equals faster execution.
simple and true.
also, functions that will be commonly used should be as fast as possible. you'll have plenty of opportunity to slow things down later on...
a common task like harvesting tags should be as quick and compatible with existing native interfaces.
consider the four possible outcomes of matching a className to an element:
(in order of likelihood)
1. the tag has no className
2. the tag has a className, but the search term is not contained in the element's className
3. the tag has a className, and the search term is equal to the element's className
4. the tag has a className, and the search term is contained in the element's className ( it's one of many classes represented in the attribute)
we provide the code inside the loop four opportunities to avoid the regexp and bail out early.
these opportunities coincide with the four possible outcomes.
line by line walkthrough:
while (a = allT[i++]) {
continue advancing to the next one in our stack of all tags, and assign it to "a".
if (a.className && a.className.indexOf(cn) + 1) {
this one is critical. most tags do not have classes. the first part of the if statement "if a.className" means that any element without a class will fail, and goto the end of the if block, and thus the end of the loop. right away, most tags fail. bailing early lets us avoid a slow string (.indexOf) or regexp (.test) method execution on something with no class at all.
the second part of the if statement (a.className.indexOf(cn) + 1) performs a similar role, acting as a gate keeper to the slow regexp. if no match if found, indexOf will return -1. adding 1 to that = 0, turning a no-match evaluation into a "falsey" value, and failing the if statement.
if we got to this point we know the element has a class, and that the search term at least partially matches the element's class.
if(a.className===cn){ allCN[allCN.length] = a; continue; }
most of the time there will only be one class used, and a direct compare is super fast.
therefore, it is fastest to try to get away with a direct compare.
if it works, which it usually will since most class attributes mean one class, we add the result to the array of matches, and continue the loop from the top.
this skips over the regexp if the fast direct match is successful.
if the direct match fails we know that one of two things is true:
1. we found a match substring, like "tree" in "subtree" for instance.
2. we found the proper class among more than one classes represented in one class attribute, typically in a space-separated list.
this line:
rx.test(a.className) ? (allCN[allCN.length] = a) : 0;
runs the regexp that can determine precisely the difference between a substring and a "one of many" match.
if it's "one of many", it adds the element to the array of matches and proceed with the next loop iteration.
-------
in general, there is a point of diminishing returns using ifs to avoid work.
you could get to the point that the filtering is more intensive than the workload to be avoided.
in this case, the regexp is sufficiently slow to provide ample time to trade in exchange for a couple of if statements.
in other situations, that's not the case, and it pays to runs a few tests and find the proper balance.
rnd me
12-23-2008, 06:11 AM
Basically all the new browsers' versions, except, of course, IE :rolleyes: support now the native getElementsByClassName() method. At least Opera 9+ and Chrome do so.
i was referring to Array.indexOf, not getElementsByClassName, but it's probably the same either way. i know chrome has Array.indexOf...
itsallkizza
12-23-2008, 08:14 AM
This is trivial I know, but since we're going for fine-tuning...
Don't you suppose
if (some_string.indexOf(str)!=-1)
would be the tiniest bit faster than running an addition statement?
i.e.
if (some_string.indexOf(str)+1)
I don't have time to benchmark it right now and I can see how it might actually take longer for the browser's JS interpreter to compare two integers than to add and see if it's 0 or not, but I'll run some benchmarking tomorrow at lunch if I have any spare time.
itsallkizza
12-23-2008, 04:06 PM
I built this speed test to see which if test is faster. I'm sure there are better/more accurate ways of testing but these are the results I got:
Test
func1 tested if (some_text.indexOf(str_to_search_for)!=-1)
func2 tested if (some_text.indexOf(str_to_search_for)+1)
250000 iterations for each function.
IE6 and IE7
Average func ms: 1150 (IE7)
func1 and func2 averaged almost exactly the same time.
Result: no difference
FF3 3.0.4
Average func ms: 235
func1 averaged approx 4 milliseconds faster than func2. Negligible at best.
Result: func1 ran ~1-2% faster
Opera 9.62
Average func ms: 405
The difference here was slightly more noticeable with the average difference being about 15 ms. This one was odd in that it would time exactly the same some of the time, and a difference of almost exactly 32 other times.
Result: func1 ran ~3-4% faster
Safari (3.2.1 for Windows)
Average func ms: 335
func1 ran about 5 ms faster than func2. The results were fairly inconsistent from run to run though.
Result: func1 ran ~1.5% faster
Conclusion
The difference in speed between
if (some_text.indexOf(str_to_search_for)!=-1)and
if (some_text.indexOf(str_to_search_for)+1)
is too negligible to warrant a change in coding.
Speed Test Used:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<title>Speed Test</title>
<style type="text/css">
body
{
font-size: 12px;
font-family: arial,sans-serif;
}
form
{
margin: 0;
padding: 0;
}
h2
{
margin: 0;
padding: 12px 0 0 0;
font-size: 18px;
}
.tab
{
margin-right: 20px;
}
</style>
<script type="text/javascript">
// <![CDATA[
/*Author: itsallkizza*/
function func1()
{
var some_text = "here\'s some text to search through. hope you enjoy :)";
var str_to_search_for = "search ";
var flag = 0;
if (some_text.indexOf(str_to_search_for)!=-1) flag = 1;
}
function func2()
{
var some_text = "here\'s some text to search through. hope you enjoy :)";
var str_to_search_for = "search ";
var flag = 0;
if (some_text.indexOf(str_to_search_for)+1) flag = 1;
}
function speedTest(iterations)
{
var f1_results = document.getElementById("f1_results"),f2_results = document.getElementById("f2_results"),status = document.getElementById("status");
var t1,t2;
status.innerHTML = "running";
t1 = (new Date()).getTime();
for (var i=0;i<iterations;i++) func1();
t1 = (new Date()).getTime()-t1;
t2 = (new Date()).getTime();
for (var i=0;i<iterations;i++) func2();
t2 = (new Date()).getTime()-t2;
status.innerHTML = "test complete";
f1_results.innerHTML = t1;
f2_results.innerHTML = t2;
}
window.onload = function()
{
document.getElementById("function1").innerHTML = ((func1+"").replace(/\n/g,"<br />")).replace(/\t/g,'<span class="tab"></span>');
document.getElementById("function2").innerHTML = ((func2+"").replace(/\n/g,"<br />")).replace(/\t/g,'<span class="tab"></span>');
}
var dat = new Date();
function onFormSubmit(form_element)
{
var i = form_element.iterations.value*1;
if (i && !isNaN(i)) speedTest(i);
return false;
}
// ]]>
</script>
</head>
<body>
<form onsubmit="return onFormSubmit(this)">
<input type="text" name="iterations" value="250000" /><br />
<input type="submit" value="start" /><br />
</form>
<h2>Function 1</h2>
<div id="function1"></div>
<h2>Function 2</h2>
<div id="function2"></div>
<h2>Results</h2>
<div id="results">
<div id="status"></div>
Function 1 Time: <span id="f1_results"></span><br />
Function 2 Time: <span id="f2_results"></span>
</div>
</body>
</html>
Notes:
The variation in times could be attributed not only the the functions being tested, but also to the creation of the Date object and the calling of getTime(). Evidence suggesting this is not the case is Opera and IE7's relative consistency in results. An increase in iterations would reduce the effect of (new Date()).getTime().
I also switched the order of the functions being tested to preclude any unforeseen algorithm caching (or whatever) with the same results.
rnd me
12-24-2008, 12:54 AM
Conclusion
The difference in speed between
if (some_text.indexOf(str_to_search_for)!=-1)and
if (some_text.indexOf(str_to_search_for)+1)
is too negligible to warrant a change in coding.
Notes:
The variation in times could be attributed not only the the functions being tested, but also to the creation of the Date object and the calling of getTime(). Evidence suggesting this is not the case is Opera and IE7's relative consistency in results. An increase in iterations would reduce the effect of (new Date()).getTime().
I also switched the order of the functions being tested to preclude any unforeseen algorithm caching (or whatever) with the same results.
that's good work, i will switch to !==-1 from now on. i think this function only has to work in ie and opera, but it's certainly something to add to my arsenal.
thanks for taking the time to run the numbers. just because it didn't really flush out this time, should not discourage trying again. nothing is perfect, and this is one of the slowest functions i have timed in a while.
It's too bad we can't fish out a lot more performance somehow.
i like improvement a lot better than finality. the permutations i tried either resulted in a wash across different documents, or hit close to the limit of benchable precision.
rnd me
12-24-2008, 07:06 AM
ok, after much input from no fewer than 5 people, and with some of my own revisons, i have manged to shave another 25% off.
also there is a minor issue with the regexp we were using, as it would match "tree" in "sub-tree bold highlight" for instance. thanks to jeff mott for pointing that out, and providing a solution. i added itsallkizza's indexOf gateway as well, seems to be faster.
the current version with all tweaks and fixes:
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (cn) { //fast and lean
var rx = new RegExp("(?:^|\\s)" + cn+ "(?:$|\\s)");
var allT = document.getElementsByTagName("*"), allCN = [], ac="", i = 0, a;
while (a = allT[i=i+1]) {
ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
}
i expected to be able to make a few single digit gains, but i wasn't expecting this much of a gain. turns out caching the dom property lookups (a.className) helps a lot.
for speed freaks, here is a more elaborate version that beats the above by about 10%:
function (cn) { // the speed king by ~11%
var rx = new RegExp("(?:^|\\s)" + cn+ "(?:$|\\s)");
var allT =document.getElementsByTagName("*"), allCN = [],i = 0, ac="", a;
for (mx=allT.length;i<mx;i=i+2){
a = allT[i]; ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
a = allT[i+1]; if(!a){ break ;}
ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
it's quite a bit lengthier, but is a bit faster, especially in IE where it will mainly be used. it's folded twice, and perhaps another fold or two could coax out a bit more performance.
i didn't bother because the regular version seems fast enough, now that it's 20-30% faster than what i thought was fast just yesterday.
ain't collaboration great?
itsallkizza
12-24-2008, 07:26 AM
very nice work
admirable the rnd me efforts to improve the code :) Merry Christmas everybody!
rangana
12-24-2008, 10:04 AM
Well done!
Thanks for rectifying the RegEx for me.
rnd me
12-26-2008, 03:32 AM
It dawned on me that nobody wants to type out document.getElementsByClassName very often when coding.
since i started using this function, now that i have one i like, i realized the finger-killer that is the function's name.
what a pain.
here is a simple shortcut/wrapper function that adds a slight enhancement as well.
it returns an array/collection of elements match the class specified in the first argument: className.
you can use classes("sample"), where you would normally use document.getElementsByClassName("sample").
It also has an optional second argument mom that is the parent element.
passing an element here restricts the className search to tags under mom only.
If you just wanted to grab highlighted rows in a certain table, you could use
classes("hilite", resultTable);
For convenience, you can use either an actual element object, or the id of the element as a string, to specify this optional parent element.
function classes(className, mom){
mom=mom||document;
if(mom.charAt){mom= document.getElementById(mom);}
return mom.getElementsByClassName(className);
}
anecdotally i can say that i never really bothered with getElementsByClassName before, and i was missing out.
it's a great way to specify groups without forms.
with decent addClass and removeClass functions, you can still keep you style directions intact, something else that used to discourage me.
if you need the class functions,check the snippets link on my sig @ "JavaScript\DOM\class handlers"
getElementsByClassName is native to most good browsers, and i don't mind making IE users wait a little longer, now that i know it will still work reliably. my life just got a little easier.
---
Thanks to everyone who helped make this the best darn getElementsByClassName ever!
merry xmas!
abduraooft
01-05-2009, 09:43 AM
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (cn) { How do I change it so that, I can call this getElementsByClassName() from other elements too?
Say, most of the time, I need to get the input elements of a form. I think,
formObj.getElementsByClassName() would be much faster than
document.getElementsByTagName("*"). Is it possible?
rnd me
01-05-2009, 10:26 AM
How do I change it so that, I can call this getElementsByClassName() from other elements too?
IE won't take the prototypes, and other browsers already have element.getElementsByClassName,
so it would be hard to make that usage cross browser compatible.
you could add the method manually to all tags (slow on boot), or as you fetch them (slower to fetch).
however, i posted a shortcut function in my last post which does about the same thing, only quicker:
function classes(className, mom){
mom=mom||document;
if(mom.charAt){mom= document.getElementById(mom);}
return mom.getElementsByClassName(className);
}
mom is the parent element to look "under".
it can be passed or identified by its' id attrib in a string.
makes it quicker to code, and you can even nest calls to classes function to get really picky.
abduraooft
01-05-2009, 10:36 AM
Oh.. I missed that last update.
Now, when I use it, I get mom.getElementsByClassName is not a function
return mom.getElementsByClassName(className);
I called it like
var elmts=classes('must','form1');
I'm trying to make a form validation procedure for all elements having a class="must"
rnd me
01-05-2009, 10:49 AM
Oh.. I missed that last update.
Now, when I use it, I get
I called it like
var elmts=classes('must','form1');
I'm trying to make a form validation procedure for all elements having a class="must"
DOH!
i really hate to post fresh code here, and that's exactly why. grrrr.
shame on me.
i grit as i hit post on this workaround i came up with, because i can't test it at the moment:
function classes(className, mom){
mom=mom||document;
if(mom.charAt){mom= document.getElementById(mom);}
if(mom.getElementsByClassName){
return mom.getElementsByClassName(className);
}
return document.getElementsByClassName(className, mom);
}
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (cn, mom) { //fast and lean
if(!mom){mom=document;}
var rx = new RegExp("(?:^|\\s)" + cn+ "(?:$|\\s)");
var allT = mom.getElementsByTagName("*"), allCN = [], ac="", i = 0, a;
while (a = allT[i=i+1]) {
ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
}
my theory is that where native, element.getElementsByClassName will be used, and the new version of document.getElementsByClassName can be used otherwise via the newly added 2nd argument.
there may be a browser that supports document.getElementsByClassName but not element.getElementsByClassName, in which case it would fail.
i will update this post (if need be) when i get a chance to test it.
abduraooft
01-05-2009, 11:49 AM
Thanks a lot. It works now :)
I like your way of referring the DOM elements, mom.dad..... :D
Basscyst
01-06-2009, 08:28 PM
It could be suggested that optionally passing the tag name could speed up the process as well. That way if you only want all <input> tags with a certain class you aren't iterating through every element.
rnd me
01-06-2009, 09:18 PM
It could be suggested that optionally passing the tag name could speed up the process as well. That way if you only want all <input> tags with a certain class you aren't iterating through every element.
thanks for the suggestion.
you are right, and we were working toward something like that.
the complication was with the replacement document.getElementsByClassName function, and keeping a native interface intact.
in light of forum input and my own usage, i think abandoning native document.getElementsByClassName compatibility and using a custom search function is probably better, though it wont provide much support to other scripts that are expecting document.getElementsByClassName.
It's probably more useful to more people as a standalone product.
This allows us to still use the native methods if available, guaranteeing the fastest results.
function classes(cn, mom) { //gets elements by sub-tag classes in mom or document
if(!mom){mom=document;}
if(mom.getElementsByClassName){ return mom.getElementsByClassName(cn); }
var rx = new RegExp("(?:^|\\s)" + cn+ "(?:$|\\s)");
var allT = mom.getElementsByTagName("*"), allCN = [], ac="", i = 0, a;
while (a = allT[i=i+1]) {
ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
abduraooft
01-07-2009, 05:45 AM
That way if you only want all <input> tags with a certain class you aren't iterating through every element. Yes, but we need to consider <select> and <textarea> too.
itsallkizza
01-07-2009, 06:32 AM
You could have it optionally take an array (or space delimited string) with multiple tags - or maybe even mother tags (like if you wanted to search through all child elements of <form>s only).
Basscyst
01-07-2009, 06:25 PM
Oh I know, it wouldn't be practicle for form validation, input was just an example. It's just one more way to speed the process if the native interface isn't available. If I'm targeting a group of elements, be it to add event handlers or what have you, I generally know what type of tag they are.
I guess the real question is whether or not looping through the results of the native function when a tag name is specified becomes counter productive when compared to the time it saves on the custom function.
Something like:
function classes(cn, mom, tag) { //gets elements by sub-tag classes in mom or document
if(!mom){mom=document;}
if(!tag){tag="*";}
if(mom.getElementsByClassName){
var arr=mom.getElementsByClassName(cn);
if(tag){
var len=mom.length;
var tag_filter=new Array();
for(var i=0;i<len;i++){
if(arr[i].tagName.toLowerCase()==tag){tag_filter[tag_filter.length]=arr[i];}
}
return tag_filter;
}else{
return arr;
}
}
var rx = new RegExp("(?:^|\\s)" + cn+ "(?:$|\\s)");
var allT = mom.getElementsByTagName(tag), allCN = [], ac="", i = 0, a;
while (a = allT[i=i+1]) {
ac=a.className;
if ( ac && ac.indexOf(cn) !==-1) {
if(ac===cn){ allCN[allCN.length] = a; continue; }
rx.test(ac) ? (allCN[allCN.length] = a) : 0;
}
}
return allCN;
}
abduraooft
01-07-2009, 06:38 PM
When validate all the required fields in one go and show the messages to the user, then it's OK. But, when we need to validate the elements in the order in which they are displayed, one at a time, then the above would not be enough, I think.
(Personally, I prefer to validate one field at a time from the client side)
Basscyst
01-07-2009, 10:41 PM
When validate all the required fields in one go and show the messages to the user, then it's OK. But, when we need to validate the elements in the order in which they are displayed, one at a time, then the above would not be enough, I think.
(Personally, I prefer to validate one field at a time from the client side)
I was thinking more for say, adding a click event to all anchor tags with a certain class. If all the anchor tags are placed sporadically throughout the document, we'd have to iterate through every element within the document. For form validation, one could keep the tag specification null and simply traverse from the form itself by specifying the parent.
vBulletin® v3.8.2, Copyright ©2000-2012, Jelsoft Enterprises Ltd.