Page 1 of 2 12 LastLast
Results 1 to 10 of 17

Thread: getElementByClassname problem never figured out..

  1. #1
    Join Date
    Nov 2006
    Posts
    236
    Thanks
    4
    Thanked 1 Time in 1 Post

    Question getElementByClassname problem never figured out..

    I went looking back at some one my old post and then on the web. I haven't been able to fine a working getelementbyclassname function that doesn't use jqery.
    It seems like there should be some but most don't have demo pages or don't show you what to put in your html.

    here is what I have

    script above the body
    HTML Code:
    function getElsByClassName(classname){
    	var rv = []; 
    	var elems  = document.getElementsByTagName('div')
    	if (elems.length){
    		for (var x in elems ){
    			if (elems[x] && elems[x].className && elems[x].className == classname){
    				rv.push(elems[x]);
    			}
    		}
    	}
    	return rv; 
    }

    html
    HTML Code:
    <div id="tileM" onclick="getElsByClassName('switchM').classname='Tname'"
    I'm just trying to get this div to change the class of another div when clicked.
    it seems like it should be so easy but no code I've used has worked.
    I do have a better understanding when it's the div with the desired class that calls the function on it's self. scripts like that work. I'm not getting any errors but nothing is happening.
    I think I never figured out how to get a function like this to work. I though I had something on my hard drive where I had worked it out but I didn't find anything.

    I would appreciate it if some one can help me with this one issue. there are only certain things I frequently need solutions for. This is the last one.

    It needs to be compatible with IE6. And I can't use a script library cause it has to be totally self contained.

  2. #2
    Join Date
    Mar 2005
    Location
    SE PA USA
    Posts
    30,495
    Thanks
    82
    Thanked 3,449 Times in 3,410 Posts
    Blog Entries
    12

    Default

    That getElsByClassName function is primitive in that the elements it examines must have the exact class and only the exact class being looked for (but elements may have more than one class) and is limited to only examining div elements. This may or may not adequate for your purposes.

    In browsers that support it, the real getElementsByClassName returns a nodelist - an array like list of elements. The getElsByClassName function, and all of the other functions like it return a true array. Regardless of which you have (nodelist or true array) you cannot set anything on the entire list or array without looping through it. And you cannot set anything on a particular member of the list or array without having a numeric reference to it.

    There's no such thing as .classname, it's .className

    So, assuming the getElsByClassName function, though limited and primitive is adequate to the task of finding the elements you need it to find, you would still need to loop through its results and use the proper syntax on each of those (even if there's only one) for it to do anything. Something like:

    Code:
    <div id="tileM" onclick="var els = getElsByClassName('switchM'); for (var i = els.length - 1; i > -1; --i){els[i].className='Tname';}"
    If you want a better function for getting elements by their class name in browsers that don't have one, see:

    http://www.webdeveloper.com/forum/sh...d.php?t=198227

    for alternatives and a robust discussion upon their merits.
    - John
    ________________________

    Show Additional Thanks: International Rescue Committee - Donate or: The Ocean Conservancy - Donate or: PayPal - Donate

  3. #3
    Join Date
    Dec 2008
    Location
    Portsmouth, UK
    Posts
    1,891
    Thanks
    2
    Thanked 441 Times in 435 Posts

    Default

    I use

    Code:
     
    function bycls(nme,el){
      for (var reg=new RegExp('\\b'+nme+'\\b'),els=el.getElementsByTagName('*'),ary=[],z0=0; z0<els.length;z0++){
       if(reg.test(els[z0].className)){
        ary.push(els[z0]);
       }
      }
      return ary;
     }
    Vic
    God Loves You and will never love you less.
    http://www.vicsjavascripts.org/Home.htm
    If my post has been useful please donate to http://www.operationsmile.org.uk/

  4. #4
    Join Date
    Mar 2005
    Location
    SE PA USA
    Posts
    30,495
    Thanks
    82
    Thanked 3,449 Times in 3,410 Posts
    Blog Entries
    12

    Default

    Vic, I thought that was the way to go too, until I read the thread referenced in my previous post. Using that regex and searching for a class of 'title' will give a false positive on an element like:

    Code:
    <div class="title-div">
    If you change it to:

    Code:
    function bycls(nme,el){
      el = el || document; // assume document if no el argument is given
      for (var reg=new RegExp('\\W'+nme+'\\W'),els=el.getElementsByTagName('*'),ary=[],z0=0; z0<els.length;z0++){
       if(reg.test(els[z0].className)){
        ary.push(els[z0]);
       }
      }
      return ary;
     }
    It works correctly on that, but is less efficient than it could be. That's only important if it has to iterate over tons of elements, but that situation could arise on a page with a lot of HTML code on it. And it still doesn't exactly mimic getElementsByClassName(), which returns a nodelist, not an array and can take more than one class name as an argument (from the specification):

    The getElementsByClassName(classNames) method takes a string that contains a set of space-separated tokens representing classes. When called, the method must return a live NodeList object containing all the elements in the document, in tree order, that have all the classes specified in that argument.
    I don't think there's any way to create a nodelist, but accepting more than one class name is doable. Also, it might be good to fall forward to the browser's native getElementsByClassName if it has one.

    BTW:

    Code:
    getElementsByTagName('*')
    and:

    Code:
    ary.push(els[z0])
    will fail in IE less than 6. Or it might be IE less than 5.5, I forget which. It might be different for each. Push is less than 5.5, I'm not sure about the other, it's 5.5 or 6. I use a test for push:

    Code:
    if (![].push){
    	Array.prototype.push = function(){
    		var a = arguments;
    		for(var i = 0; i < a.length; ++i){
    			this[this.length] = a[i];
    		}
    		return this.length;
    	};
    }
    But the other one, the substitute is (some browsers that identify .getElementsByTagName as a function don't do .getElementsByTagName('*')) document.all, or el.all, so if I'm branching for .getElementsByTagName('*'), I test the availability of the .all method first and use it if available.

    Generally one can leave out these ancient browsers, they're incapable of rendering your average modern web page, but it depends upon the scope of script, what browsers it has to accommodate. There certainly are folks out there still using IE 5 and 5.5.
    Last edited by jscheuer1; 05-31-2012 at 03:40 PM. Reason: add 'fix', more info
    - John
    ________________________

    Show Additional Thanks: International Rescue Committee - Donate or: The Ocean Conservancy - Donate or: PayPal - Donate

  5. #5
    Join Date
    Dec 2008
    Location
    Portsmouth, UK
    Posts
    1,891
    Thanks
    2
    Thanked 441 Times in 435 Posts

    Default

    function bycls(nme,el){
    el = el || document; // assume document if no el argument is given
    for (var reg=new RegExp('\\W'+nme+'\\W'),els=el.getElementsByTagName('*'),ary=[],z0=0; z0<els.length;z0++){
    if(reg.test(els[z0].className)){
    ary.push(els[z0]);
    }
    }
    return ary;
    }
    interesting but needs(in red)

    Code:
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
    
    <html>
    
    <head>
      <title></title>
    </head>
    
    <body>
    <div class="title-div">
    <script> vic=0; </script>
    <form name=Show id=Show style="position:absolute;visibility:visible;top:700px;left:0px;" >
    <input size=100 name=Show0 >
    <input size=10 name=Show1 >
    <input size=10 name=Show2 >
    <input size=10 name=Show3 >
    <input size=10 name=Show4 >
    <input size=10 name=Show5 >
    <input size=10 name=Show6 >
    <input size=10 name=Show7 >
    <input size=10 name=Show8 >
    <input size=10 name=Show9 ><br>
    <textarea name=TA rows=1 cols=100 ></textarea>
    </form>
    <script type="text/javascript">
    <!--
    function bycls(nme,el){
      for (var reg=new RegExp('\\W'+nme+'\\W'),els=el.getElementsByTagName('*'),ary=[],z0=0; z0<els.length;z0++){
       if(reg.test(' '+els[z0].className+' ')){
        ary.push(els[z0]);
       }
      }
      return ary;
     }
    document.Show.Show0.value=bycls('title-div',document);
    
    //-->
    </script></body>
    
    </html>
    Vic
    God Loves You and will never love you less.
    http://www.vicsjavascripts.org/Home.htm
    If my post has been useful please donate to http://www.operationsmile.org.uk/

  6. #6
    Join Date
    Mar 2005
    Location
    SE PA USA
    Posts
    30,495
    Thanks
    82
    Thanked 3,449 Times in 3,410 Posts
    Blog Entries
    12

    Default

    I think we're both wrong, mine failed on a single class name with no spaces, yours (if I understand it correctly) erroneously reports 2 matches here:

    Code:
    <!DOCTYPE html>
    <html>
    <head>
    <title></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type="text/javascript">
    function bycls(nme,el){
      el = el || document;
      for (var reg=new RegExp('\\W'+nme+'\\W'),els=el.getElementsByTagName('*'),ary=[],z0=0; z0<els.length;z0++){
      if(reg.test(' '+els[z0].className+' ')){
        ary.push(els[z0]);
       }
      }
      return ary;
     }
    </script>
    </head>
    <body>
    <div class="title-span"></div>
    <div class="title"></div>
    <script type="text/javascript">
    alert(bycls('title').length);
    </script>
    </body>
    </html>
    The regex finally adopted by most respondents in the thread I referred to is perhaps the solution (substitute nme for className to make it a drop in replacement):

    Code:
    new RegExp("(?:^|\\s)" + className + "(?:$|\\s)")
    works with the above example. Potentially the best solution in that thread was:

    http://www.webdeveloper.com/forum/sh...4&postcount=35

    It might still benefit from one or more of the efficiencies mentioned in the earlier posts that it left out, like testing for the equality of a single class name first before applying the regex test. But that might make it less faithful to the specification.

    And its author says right out it's not fully tested.

    It also assumes an array is what's wanted and when falling forward converts the nodelist to an array. This can actually be good. Nodelists behave erratically cross browser. Some browsers add to/subtract from them if the DOM changes, others do not. With an array, neither of those will happen, so you know what to expect.

    In my mind it's best feature is the callback which prevents having to iterate over the array again to perform whatever action is desired.

    Added Later:

    I tried out that function from that post that had impressed me. It had several problems. Here's a corrected version with my other additions (push for non-push browsers, document.all for those that support it that might not support getElementsByTagName in some or all of its forms) included:

    Code:
    /**
     * @param   string  required  Class name or names to find
     * @param   string  optional  Tag name to search for
     * @param   object  optional  DOM node to search from
     * @param   func    optional  Callback function executed on each iteration.
     *                            Receives two arguments, index and DOM node
     * @return  array   Array of matching DOM nodes
     */
    
    function getElementsByClassNameCustom(cname, tagName, parent, callback) {
      tagName = tagName || "*";
      parent = parent || document;
      callback = callback || function(){};
      var el = null;
      var matches = [];
      var i = 0, j = 0;
      var els = document.all && !window.opera? (tagName === '*'? parent.all : parent.all.tags(tagName)) : parent.getElementsByTagName(tagName);
      var cnames = cname.split(' ');
    
      function testallnames(name){
      	var cname, reg;
      	while (cname = cnames[j++]){
      		reg = new RegExp("(?:^|\\s)" + cname + "(?:$|\\s)");
      		if(!reg.test(name)){return false;}
      	}
      	return true;
      }
      
      while (el = els[i++]) {
        j = 0;
        if ( testallnames(el.className) ) {
          callback( matches.push(el)-1, el);
        }
      }
      
      return matches;
    }
    
    function getElementsByClassNameNative(cname, tagName, parent, callback) {
      parent = parent || document;
      callback = callback || function(){};
      
      var els = parent.getElementsByClassName(cname, tagName);
      var el;
      var i = els.length;
      var matches = [];
      
      // Wrap the element collection in an Array
      while (el = els[--i]) {
        callback( matches.push(el)-1, el);
      }
      
      return matches.reverse();
    }
    
    window.getElementsByClassName = getElementsByClassNameCustom;//!document.getElementsByClassName ?  : getElementsByClassNameNative;
    
    if (![].push){
    	Array.prototype.push = function(){
    		var a = arguments;
    		for(var i = 0; i < a.length; ++i){
    			this[this.length] = a[i];
    		}
    		return this.length;
    	};
    }
    Example usage, utilizing all of the available parameters on two class names:

    Code:
    getElementsByClassName('title title-span', 'div', document.body, function(i, el){el.style.display = 'none';})
    Using only the required on a single class name:

    Code:
    getElementsByClassName('title')
    Note: The specification requires that, in the case of two or more class names, only those elements with all of the classes should be returned.

    Usage for the original question:

    Code:
    <div id="tileM" onclick="getElementsByClassName('switchM', '', '', function(i, el){el.classname='Tname'});"
    Last edited by jscheuer1; 05-31-2012 at 06:36 PM. Reason: add detail. later fix final function, add OP usage
    - John
    ________________________

    Show Additional Thanks: International Rescue Committee - Donate or: The Ocean Conservancy - Donate or: PayPal - Donate

  7. The Following User Says Thank You to jscheuer1 For This Useful Post:

    ApacheTech (06-01-2012)

  8. #7
    Join Date
    Apr 2012
    Location
    Chester, Cheshire
    Posts
    329
    Thanks
    7
    Thanked 35 Times in 35 Posts

    Default

    Thanks JS. I've been looking for a cross browser version of that for a while and I hit the same problem as you mentioned. The corrected function you posted works like a charm.

    Added to code library of useful functions. (With credit of course.)

  9. #8
    Join Date
    Mar 2005
    Location
    SE PA USA
    Posts
    30,495
    Thanks
    82
    Thanked 3,449 Times in 3,410 Posts
    Blog Entries
    12

    Default

    Here's an even better version:

    Code:
    /* @param  string   required  Class name or names (space separated) to find, must be the first parameter
    
     	Optional Parameters in any order:
     * @param  string   optional  Tag name to search for
     * @param  object   optional  DOM node to search in
     * @param  function optional  Callback function executed for each element found.
                                  Receives two arguments, the element, the index in the array of the element.
    
     * @return array    Array of matching DOM nodes before callback if any, regadless of if its action removing it from the nodelist */
    
    ;(function(){
    
      function testallnames(name, cnames){
        var names = name.split(' '), j = -1, n = names.length, nameObj = {}, aname, cname;
        while ((aname = names[--n])){
          nameObj[aname] = true;
        }
        while ((cname = cnames[++j])){
          if(!nameObj[cname]){return false;}
        }
        return true;
      }
    
      var getEls = (function(){
        return document.all && !window.opera? function(tagName, parent){
          return tagName === '*'? parent.all : parent.all.tags(tagName);
        } : function(tagName, parent){
          return parent.getElementsByTagName(tagName);
        };
      })();
    
      function getElementsByClassNameCustom() {
        var a = arguments.length - 1, t, arg, cname, tagName, parent, callback;
        for (a; a > -1; --a){
          arg = arguments[a];
          if(!arg){continue;}
          t = typeof arg;
          t === 'function'? callback = arg : t === 'object'? parent = arg : a? tagName = arg : cname = arg;
        }
        if(!cname || typeof cname !== 'string'){throw(new Error('Not Enough Arguments, getElementsByClassName expects at least one class name as an argument.'));}
        tagName = tagName || "*";
        parent = parent || document;
        callback = callback || function(){};
        var el = null;
        var matches = [];
        var i = -1;
        var els = getEls(tagName, parent);
        var cnames = cname.split(' ');
    
        while ((el = els[++i])) {
          j = -1;
          if ( testallnames(el.className, cnames) ) {
            callback(el, matches.length);
            matches[matches.length] = el;
          }
        }
    
        return matches;
      }
    
      function getElementsByClassNameNative() {
        var a = arguments.length - 1, t, arg, cname, tagName, parent, callback;
        for (a; a > -1; --a){
          arg = arguments[a];
          if(!arg){continue;}
          t = typeof arg;
          t === 'function'? callback = arg : t === 'object'? parent = arg : a? tagName = arg : cname = arg;
        }
        if(!cname || typeof cname !== 'string'){throw(new Error('Not Enough Arguments, getElementsByClassName expects at least one class name as an argument.'));}
        parent = parent || document;
        callback = callback || function(){};
    
        var els = parent.getElementsByClassName(cname, tagName);
        var el;
        var i = els.length;
        var matches = [];
    
        while ((el = els[--i])) {
          callback(el, matches.push(el) - 1);
        }
    
        return matches.reverse();
      }
    
      window.getElementsByClassName = !document.getElementsByClassName? getElementsByClassNameCustom : getElementsByClassNameNative;
    })();
    Advantages/Changes:

    • No regular expressions for much faster lookups when the native getElementsByClassName function is missing.

    • Optional parameters are truly optional in that they may come in any order.

    • Only one function name exposed to the global scope (there were three before)

    • The method for getting elements by tag name when there's no native getElementsByClassName function is set once onload of the page, rather than branched out each time the function is used.

    • Callback function now uses the DOM element as first parameter, index as second as most callbacks will be to the element itself.

    • Throws an informative error if there is no class name argument.

    • Array.push for those that lack it eliminated, since only non getElementsByClassName browsers would need it, native methods used for the custom function.


    Usage for the original question in this thread:

    Code:
    <div id="tileM" onclick="getElementsByClassName('switchM', function(el){el.classname = 'Tname';});"
    The other usages from my previous post are the same.
    Last edited by jscheuer1; 06-01-2012 at 10:32 AM. Reason: minor code improvements, later changes to accommodate IE 5
    - John
    ________________________

    Show Additional Thanks: International Rescue Committee - Donate or: The Ocean Conservancy - Donate or: PayPal - Donate

  10. The Following 2 Users Say Thank You to jscheuer1 For This Useful Post:

    ApacheTech (06-01-2012),riptide (06-03-2012)

  11. #9
    Join Date
    Nov 2006
    Posts
    236
    Thanks
    4
    Thanked 1 Time in 1 Post

    Default

    okay so there really wasn't any good classname function on the web. So thank you for making a good one. This is less complicated the the ultimate version. I know the one I first posted didn't have any iteration going on in it. I was sleepy and if I had been more awake I would have been trying to use something else. The browser that I'm coding in does have getElementsByClassName so using any function with that name probably caused problems.

    anyway

    <div id="tileM" onclick="getElementsByClassName('switchM', function(){this.classname = 'Tname';});"

    The part that's in red it is unfamiliar coding [S]why do I add function()[/S]
    scratch that I understand what function is and why it's being called

    and the {;} now why am I adding this.classname. I thought 'this' would refer to the element that is calling the function which would be tileM. is it referring to switchM?
    I tired using this script and it doesn't work but it's not giving an error. I had to link it to an external script. Did this need to go in the html or is this related to using this.classname
    I will check in other browsers in a second. I'm in firefox now.
    Last edited by riptide; 06-01-2012 at 08:46 AM.

  12. #10
    Join Date
    Mar 2005
    Location
    SE PA USA
    Posts
    30,495
    Thanks
    82
    Thanked 3,449 Times in 3,410 Posts
    Blog Entries
    12

    Default

    First off, I've changed the code yet again. See my previous post which has now been edited to reflect that. The syntax for your original question is now:

    Code:
    <div id="tileM" onclick="getElementsByClassName('switchM', function(el){el.classname = 'Tname';});"
    Also edited in the previous post to reflect that. None of these latest changes would affect its performance in IE 6+ or other modern browsers though for the use you're putting it to.

    But to answer your new question:

    The this keyword is whatever we tell the browser it is. In the callback it was the element(s) selected by the getElementsByClassName function. However, the syntax for telling that to the browser didn't exist in IE 5, so now I'm passing it in as a parameter, which BTW was the original method.

    And ordinarily in that scenario this would be the element clicked, except that it's not in the function that executes onclick, it's in the function that executes on each element match of the selected class name(s). But, as I say, if you use the latest revised code, you won't have to worry about that anyway, the element is now being passed as a parameter.

    Making the code external is no problem. In fact nothing you mention sounds like a problem.

    This code has now been tested fairly thoroughly by myself and ApacheTech. That's not to say that there can't be any other tweaks required for situations I've not yet anticipated. But it's likely that if you're having problems using it, there's something else wrong on the page and/or its resources than this code.

    Either way:

    If you want more help, please include a link to the page on your site that contains the problematic code so we can check it out.
    Last edited by jscheuer1; 06-01-2012 at 10:58 AM. Reason: add detail
    - John
    ________________________

    Show Additional Thanks: International Rescue Committee - Donate or: The Ocean Conservancy - Donate or: PayPal - Donate

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •