views:

89

answers:

5

I'm working on some custom Javascript for a CMS template at work. I want to be able to make a certain <li> element on the page receive the class of "current" for that page only. So in the global page head I have something like this:

<script type="text/javascript">
    function makeCurrent(id) {
      var current = document.getElementById(id);
      current.setAttribute("class", "current"); // for most browsers
      current.setAttribute("className", "current"); // for ie
    }
</script>

Then in each individual page <head>, I have something like this:

<script type="text/javascript">makeCurrent("undergraduate")</script>

On the page I have a nav <ul>, with something like:

<li id="undergraduate"><a href="...">Undergraduate Program</a></li>
<li id="graduate"><a href="...">Graduate Program</a></li>

etc.

When I load the page, the class is not applied. When I look in the Firebug console, it gives the error message:

current is null
(x)  current.setAttribute("class", "current"); 

I'm still getting the hang of writing solid, raw javascript (learning backwards after jQuery, you know how it goes), but I want to write it in just JS. What idiotic newbie mistake am I making?

Thanks everyone!

+1  A: 

The element does not exist yet when that script is being evaluated. Put it in the body's onload handler or something instead, so it executes once the DOM is in place.

An example of how to do this without touching any markup:

function callWhenLoaded(func) {
  if (window.addEventListener) {
    window.addEventListener("load", func, false);
  } else if (window.attachEvent) {
    window.attachEvent("onload", func);
  }
}

callWhenLoaded(function() { makeCurrent("undergraduate"); });
Chuck
Yep, stupid mistake. Although my hands were a bit tied with it because I can't add per-page script tags anywhere but the head in this particular CMS, if I add them before the closing body tag then they are site-wide.
Jason Rhodes
I changed tactics and tried to compare the href attribute of the nav links to window.location.href and add a "current" class if they match. It worked... on Firefox and Chrome, but IE7 is freaking out!
Jason Rhodes
@Jason Rhodes: I added an example of how to do this without having to touch the body tag.
Chuck
Thanks, @Chuck!
Jason Rhodes
+3  A: 

If you execute the javascript before the DOM tree has finished loading, it will return null. So an idea would be to call the function all the way at the end before you close the body tag.

This is why most JavaScript libraries have support for a dom-ready event, modern browsers have this as well (domcontentloaded) however for wide browser support it's a little trickier to do it for yourself (well, not that difficult, 3 or 4 different ways I think.)

CharlesLeaf
+2  A: 

The DOM is not fully loaded if you run makeCurrent in your head. You should put that script after your <li> tags.

Your code can be optimized: you can set a class attribute directly with current.className = 'current';.

Lekensteyn
+1  A: 

The reason is that your script is being run before the page load is complete, and therefore before the DOM is populated.

You need to make sure you only call the function after page load is complete. Do this by triggering it using document.onload() or an onload event on the body tag.

Spudley
+1  A: 

After all the technical answers have been spewed out already, I'm going to skip all those which it very well could be and go for some of the more obvious ones I've run into which have caused me to facepalm once I've realised:

  • Typo in the identity
  • The identity isn't what you think it is because it's being generated or partially generated by the web framework you're using i.e. in ASP.NET you could set the client id to "MyControl" only to find that by the time it is rendered in the client it's "Page_1$Control_0$MyControl$1"
  • You've prepended it with a # in one or more of the incorrect places, for instance, although you're not using jQuery in your example if the object id is MyControl, in jQuery and CSS you reference it using #MyControl, but in the actual id of the object, you didn't use #. In document.getElementById() you don't use a # like you would in jQuery and CSS, but you may have used it inadvertently.
  • You've set the name element in the control instead of the id.

As other people have mentioned though, it could be down to not waiting for the element to be available at the time you're referencing it.

BenAlabaster