Unobtrusive JavaScript

I am very pleased to see the numbers of visitors on my Unobtrusive JavaScript self training course, and I am currently translating it to German. I realised there was no way to leave a comment yet, and going through emails is tiring sooner or later, so let’s start a post here instead. :-)

56 Responses to “Unobtrusive JavaScript”

  1. Pieter Says:

    I stumbled upon the link to this blog item :D.
    Thank you for the fantastic course! I have learned a lot from it.

  2. Fatalis Says:

    Make it a protected wiki, perhaps.

  3. Madness Says:

    well I can just say it’s damn useful, and I referred it a lot :).
    the web needs more of that.

  4. Sebastian Redl Says:

    Interesting. I had written four distinct functions that each did one of the four actions, except that my replace simply replaces c1 with c2 if it is available, but never c2 with c1.
    My implementations look nearly exactly the same. The only differnces are that I stored the dynamic regexps in their own variables, and that I didn’t care about a lone extra whitespace in add_class.
    I believe my remove implementation is a bit more efficient, though:
    var regex = new RegExp(“\\s*\\b”cls“\\b”);
    o.className = o.className.replace(regex, “”);

  5. Maddie Says:

    I loved the lesson! It is indeed very useful. I am using it for an question and answer page much neater. I do have one question though. I put the content in a table and now only the first example works…. and I am not sure why they do not all work?

    Edit How about sending a URL? It is hard to guess what the issue might be.

  6. timb Says:

    sebastian, you can even get rid of that temp variable with

    o.className = o.className.replace(new RegExp('\s*\b'+c+'\b'),'');

  7. StraTechnologist Says:

    Unobtrusive Javascript
    Javascript is a wonderful tool to enhance the usability of web sites. It is the extra layer above the mark-up “what is this text” and the CSS “how should it be displayed”. Javascript adds a new dimension, the “how should …

  8. Stylecramper Says:

    I find the technique of creating custom attributes for elements on the fly really interesting, but what does the W3C think of it? Especially considering that the values being set are object references rather than strings? Doesn’t that violate the DTD?

  9. Evan Says:

    You present a number of interesting points. I’ve still been using the “evil” onblah=”foo()” syntax; I’m going to have to look into changing.

    That said, I must say that your pages themselves are some of the LEAST unobtrusive Javascript uses I’ve seen in some time. When the page is loading, all the examples are shown expanded. It is displayed for quite some time (25 seconds or so on my last attempt; this is off of cable), when all of a sudden the onload handler kicks in and collapses everything. This is extraordinarily annoying and disorienting. I start reading the page when it first is displayed, and I’m in the middle of a paragraph when suddenly what I’m reading shifts up an inch because an example collapsed. Or I’m in the middle of reading an example and what I was reading is no longer displayed.

    Perhaps you can come up with a way to fix this; I can’t think of any off the top of my head. But if you can’t, I suggest that you give serious thought to leaving the examples expanded. If your site was less interesting, I probably would have left it.

    Edit
    YMMV I guess. So far, I only got positive feedback on the collapsing examples, but I may turn it off or make it an option.

  10. Evan Says:

    I fully understand why people would like the collapsing examples; I generally like stuff like that. It’s just the collapsing while I’m trying to read something that I find annoying.

    I thought of a potential way to fix it though.

    When the page loads, have the examples not present initially. Then create them when the user clicks on the expand button. As an alternative (probably nicer) approach, use the CSS style ‘display:none’ around them right from the beginning, and have the javascript change it to block to display it.

    The downside of this is that it violates probably your most important piece of advice, which is to not rely on Javascript. If it’s off, the user won’t be able to get to the examples. The solution to this is to duplicate the example contents in <noscript> tags.

    The downside of that is that now you are duplicating part of the page. So the master plan I have is to leave the first occurance empty. (So you’d have something like <div id=”example1″ class=”hiddenexample”<<!– placeholder for example –>.) Then in your onload handler, copy the information over from the noscript tag to the div tag.

    I have a proof-of-concept that illustrates what I’m trying to get at. It’s not perfect from any point of view. (I’m using Javascript right in the head — I would say fine for this small thing — and inline event handlers. I don’t really check for the availiability of objects.) It doesn’t work perfectly. IE runs it fine (though maybe with a security warning), but Mozilla displays the <b> tags instead of making it bold. There’s a DOM way of copying the content (instead of with innerHTML), but it doesn’t work any better with Mozilla and completely breaks IE. But making the noscript tags into, say, div makes both methods work in both browsers. Go figure. Anyway, I figure the problems are due to the noscript content not being available if Javascript is enabled.

    The idea is a bit hackish and may not be very efficient or even outright wrong, but if you could get the bugs worked out it might work.

  11. Chris Says:

    Regarding the collapsing examples:
    Evan, well done, you are on the right track. However, you overcomplicate things.
    As the collapsible examples work now, they are unobtrusive – they don’t collapse without JavaScript and the links to show / collapse them are generated only when DOM is available.
    However, as you discovered, they can be a usability issue on slow connections or transfers.

    They are not 100% accessible either, as the links generated all hahve the same wording, which does appear as an annoying list in screen readers.

    The only way around that issue is to offer the initial collapse as an option, and store the user’s likes in a cookie or even better in her profile.

    I will add that option in a future version, right now I am busy translating the course into German and revamping the form example. I might also add a chapter on XHR/AJAX.

    Your ideas have triggered several alarm bells in my head:
    * Never hide things with CSS and show them with JS, especially not via display:none as that also hides elements from screen readers and may never enable the user to see them
    * NOSCRIPT is a hack, it is totally contradictory to the ideas of this course. It is an indicator that somewhere you rely on JavaScript

  12. Evan Says:

    Thanks for the feedback.

  13. Stephane Deschamps (nota-bene.org) Says:

    They are not 100% accessible either, as the links generated all hahve the same wording, which does appear as an annoying list in screen readers.

    I’ve worked around this problem by recuperating the title’s text and by using it as the basis for the alt of the plus/minus image.

    Example:
    title says: “blah”
    generated plus/minus says (alt and title): “blah (open this section)” and once clicked changes to “blah (close this section)”.

    This added with a this.focus() gives the disabled user a somewhat similar experience.

  14. Lars Says:

    Great stuff. You make this stuff fun to learn in debth.

  15. Jalansutera Says:

    Hi.. your tutorial is very impressive. Thank you very much that I can get a lot of new information about Javascript and I will try to implement it on my blog. Thanks!

  16. A Kiwi Geek writes... Says:

    Unobtrusive Javascript
    I’ve just found this excellent series on Javascript – it’s not a strategic guide and perhaps for the more experienced coder who has the difference between server side and client side all sorted out and understands how to use CSS properly. Well worth …

  17. low|tek Says:

    There is also an italian traslation in weekly chapters, actually there is three. URL’s of the first chapter (linked to others) http://www.pixline.net/39

  18. Andrew Says:

    I was unable to get your example for swapping classes (when more than one class is present) with your regex. I was only able to get it to work using something like this:

    RegExp(‘(.*)’c1‘(.*)’)

    I know it’s probably not good practice, but since I’m not needing to grab hold of any particular values all I need is a yes or no (not the actual chunk). For whatever reason though, that word boundary was just not working for me. Here is my example usage:

    class=”news-item closed”
    ->
    class=”news-item open”

  19. Joel Says:

    the cloneNode function requires an attribute `true` to function:
    somenode.cloneNode(true);

  20. Phoenix Says:

    ill start out by saying i love your tutorial! without it, i would have been writing my JS the 90s way (havn’t dont too much js work since then :D)
    i have now decided to try and code my site js in an external, non obtrusive way
    except there is one part i am kind of stuck on
    basically put, i have 2 diffrent methods for the presentation for my site, one is using directx image gradients for IE and using an image generator i wrote for non IE, but in a non obtrusive fashion
    i am assuming i should put the images directly inside the page, as they will work in both IE and FF, and if i dont, and someone has JS disabled they will not see the gradients
    but the thing is, for IE browsers, i do not want the browser to request the image, because that would be defeating one of the purposes (taking some load off my server) as well as requiring them to download an unnessecary image
    as far as i know, when onload is called, the images have already been downloaded, so, can you think of a way i could do the IE/FF check, and, before the ‘s have downloaded, decided wether or not they should be removed? i am assuming (possibly?) even if i call the JS right after the img tag, the image has already been downloaded, and using the JS to generate the image tags would break compatibility for any non-IE browser with JS disabled…

    any thoughts/ideas/comments would be appriciated!

  21. Jon Says:

    How would this technique work if you needed to pass ASP variables as arguments to you Javascript functions? Then you’d have to maintain the ASP code inside of the external javascript file, no?

  22. Luis Morais Says:

    Hi Chris,

    Is there anyone translating your ‘Unobtrusive Javascript’ into Portuguese (Brazilian) already?

    If not, could I translate it and use it a Brazilian Web Standards magtazine I am creating. Please email me for the magazine’s url.

    Cheers,

    Luis Morais
    writing from the UK

  23. Geoff Says:

    Your form required values javscript works great, except for one issue I am having.

    In my submit form, on the submit button, I have this, to disable the button, so it is not hammered on, I have the following code:


    <input type="submit" name="Action" value="Add" class="control" onclick="this.form.submit();this.disabled=true;
    this.form.preview.disabled=true;" />
    </input>

    I was wondering, if there was a way to still use that onclick routine, and integrate the checkform code, and do not gray out the submit button until everything has been validated? As of now, if I add your call:

    "submit" name="Action" value="Add" class="control" onclick="return checkform(this);this.form.submit();this.disabled=true;
    this.form.preview.disabled=true;" />

    The form does error with the fields not being completed, however, the form is still submitted.

    Any thoughts?

    Thanks!

  24. kemar Says:

    Can anyone send me a sample of the formsend.php used in this tutorial. I am very new to this. I need it to study it. Thank you very much.

  25. omini Says:

    does anyone know of a way to make a special message in case the user doesn’t fill out a valid email address? I’m new to javascript and can’t figure it out

  26. kOk Says:

    In “6. Keep effects mouse independent

    Just making sure that things only work when Javascript is enabled is not enough.”

    I think should read:
    “Just making sure that things don’t only work when Javascript is enabled is not enough.” or
    “Just making sure that things work when Javascript is disabled is not enough.”

    Thanks for the tutorial, K.

  27. Jason Says:

    I am just curious if it is possible to use javascript and the DOM to assign the form’s onsubmit in the Javascript rather than in the markup.

  28. Chris Heilmann Says:

    Of course it is, just like any other event handler the form can get an onsubmit handler.

    document.getElementsByTagName(‘form’) [0].onsubmit = functionname;

  29. David Danier Says:

    In Chapter 4 (“Call of the wild (scripts)”) you write about the problem overwriting window.onload. You have an example that uses some EventHandler-stuff that only works in a few browsers. Because I think thats not a real solution, here comes mine:

    var starter = new function()
    {
    // Variables
    var self = this;
    var list = new Array();

    // Function to add new load-functions
    self.add = function(func)
    {
    list.push(func);
    }

    // Method that is called as onload-event
    self.run = function()
    {
    var i;
    for (i = 0; i < list.length; i++)
    {
    func = list[i];
    func();
    }
    }

    // Init the document
    if (window.onload)
    {
    self.add(window.onload);
    }
    window.onload = self.run;
    }

    Just add an onload-function using:
    starter.add(function foo() { alert('test'); });

    If this is saved to an external file you can include it into all your HTML-documents. If any window.onload-function is set before loading the script it will be saved.

  30. Ischa Gast Says:

    I was reading this: http://www.onlinetools.org/articles/unobtrusivejavascript/chapter5.html

    It’s absolutely wonderfull but I am just missing one check and that’s the one on the radiobuttons.

  31. Samuel Cochran Says:

    In the last code example of Chapter 2 there is an error: by having a single function toggling “_on” states with both mouseover/out AND focus/blur there becomes a problem when the user CLICKS on the button. Try clicking the button to give it focus. The mouse overs are then reversed. Is this a desired behaviour, or just confusing? One would have thought once focused the button would stay “_on” until blurred, regardless of mouse over status.

  32. Alexander van H. Says:

    In chapter 2 you write that document.getElementsByTagName(tagName) returns an array. That is not true. It returns an object HTMLCollection, which consists of all those elements, a variable length and a function item(), used to pick one of those elements.

    I got to this problem when I was using some code like:
    var completeText="";
    var paragraphs=document.getElementsByTagName("p");
    for(var i in paragraphs) {
    completeText+=paragraphs[i].innerHTML;
    }

    Because for( … in … ) doesn’t use the length variable of the HTMLCollection, it doesn’t know two of the elements in the object (you say array) that are not HTMLParagraphElements.

    For example, if there are two p’s, then document.getElementsByTagName(“p”) is something like
    {
    0:[HTMLParagraphElement],
    1:[HTMLParagraphElement],
    "length":2,
    "item":function(index) {
    return this[index];
    }
    };

    (Actually, length and item() are prototypes of object HTMLCollection, but that doesn’t matter for now.)

  33. imran Says:

    Hi

    Please can any one help me to manage a stick up footer on the website for IE 5.0 +. I Think its with CSS or Javascript r both.

    As you can see 1 on http://www.bmw.co.uk

    Any kind of help will be appericiated

    thanks

    Imran Hashmi
    http://www.visionstudio.co.uk

  34. Carol S. Says:

    My comment is on the very excellent “how to reach what we want to change” tutorial.

    The mouse “stick” works great if you have multiple menu files on a site. But what if you have the following:

    1. A single menu file with all button images given the class “roll”.
    2. The javascript rollover script in a separate file
    3. Both of the above files are called as SSI into the rest of the site files.

    How would you make the mouse click “stick” and carry over to the next page in that scenario without having to create separate files for each menu item?

    CS

  35. Mike Says:

    Regarding a comment by Evan about the onload handler kicking in and hiding various elements. You can use document.write to write some css into the document to hide the elements. That way they are hidden instantly and if javascript is not available the document.write won’t run and the elements will be visible.

    This is also an example of when document.write is a very useful part of unobtrusive javascript.

    Edit: Yes and no. It is a nice fix, but not really unobtrusive, as it still may not work with some browsers and XHTML. Another way to solve this issue is to use Dean Edward’s “window.onload script workaround”:http://dean.edwards.name/weblog/2005/09/busted/ .

  36. Ross Shannon Says:

    This was a great course, and I’ve begun sending my readers to your course as an introduction to how to code unobtrusively.

  37. jcm Says:

    In chapter 1, you suggest the function:

    function color(o,col)
    {
    if(o)
    {
    o.style.background=col;
    }
    }

    saying that it “works all the time”.
    It’s wrong, since ‘o’ can point to an object without style.
    The correct syntax is:
    if (o && o.style)

  38. Jules Manson Says:

    I too learned a lot from this very user-friendly course however as a beginner with Javascript (only three years ago) I thought about seperating behavior from content (structure). I recall looking through the web for mouse scripts that do not depened on inline events and I found none. That being the case for me I naturally assumed that inline scripting was the only way to influence mouse behaviors. Why did no one (Javascript experts) ever think of doing inline mouse events that only need an CSS selector in the affected tag back then? As a beginner at scripting it seemed obvious to me that if the experts could not create one then it was probably impossible.

  39. Rob Curry Says:

    Great article – and I nearly understand it, but like many designers, I only hack JS, though I kinda understand how it should work. Syntax is my problem, sooooo… Your dynamic attribute applying is so cool and could answer a painful problem I’m having. I have a list menu with no ids, but hrefs(of course) and I need to apply a style to the current page link, presumably using the current page url? I think this should work, but am having trouble putting the pieces together. The idea is to make any list link style stick on it’s own page, regardless of the number or url of the link. I hope you understand what I’m after – any help would be great and thanks again!

  40. Jules Manson Says:

    P.S. here is a very simple, clean, and elegeant cross-browser solution for replacing the now deprecated “target” attribute. This was fashioned from Kevin Yank’s article at sitepoint.com.

    Paste this to your javascript library and link it to head section of (X)HTML:


    // Replaces "target" attribute." Works exactly like target.
    // modified by Jules Manson off of: http://www.sitepoint.com/article/standards-compliant-world
    // use with <a href="anypage.htm" rel="nameofwindoworframe">any page</a>

    window.onload = initPage; function initPage(){targetLink(); /*add all onload functions here*/}

    function targetLink() {
    if (!document.getElementsByTagName) return;
    var allLinks = document.getElementsByTagName("A");
    for (var i=0;i<alllinks .length;i++) {
    var el = allLinks[i];
    if (el.getAttribute("href") && el.getAttribute("rel"))
    {var aTarget = el.getAttribute("rel"); el.target = aTarget;}
    }
    }

    That’s it! Happy target practice!

  41. nick Says:

    I like the “unobtrusive” way a lot.
    I like JAH/AHAH a lot too and wanted to combine them.

    unfortunately, i failed very miserably.
    Bill Says:

    Good tutorial Chris, very progressive. I note that using the ‘really unobtrusive way’, functions are called without parenthesis. Does this mean that we are not able to add an event on a specific element and call a specific function that requires parameters. (could we pass a function literal perhaps?)

  42. Pablo Noel Says:

    excelent and usefull article, by the way, your paypal acount, inst work :S

    “We are sorry, we are experiencing temporary difficulties. Please try again later. If this error occurred while making a payment, avoid duplicate payments by checking your Account Overview before resending a payment.

    Message 3005″

  43. Pete Harrison Says:

    After spending weeks trying to come up with a definitive approach to form handling, I came across your approach to unobtrusive JS and am very impressed. Although fairly technical, my js is not so good, but after reading your course, not only did I learn a great deal, I understood over 95% of it.

    I have tried to take your appraoch to form handling on further by adding a whole list of validation criteria e.g. less than, greater than, numeric etc

    I also wanted to add a error message about the error to the particular field, which I’ve managed, but am strugling with removing during the clean up process you had.

    I am still commenting the code and no doubt someone could have done it better, but I think this is a pretty good entention to your great work.

    Regards
    Pete

  44. Pete Harrison Says:

    Sorry, I meant to put the link into the text. Here it is
    http://www.hhhconsulting.co.uk/dev/fd/details/n-jslogin/login/testform.htm

  45. Thomas Linard Says:

    Many of your examples don’t pass JSLint . It is deliberate?

  46. Thomas Linard Says:

    Sorry for the link. JSLint: http://www.crockford.com/jslint/

  47. Jane Says:

    Great tutorial Chris, thanks for making it available. One question on the Pop-up Solution in Chapter 2. I found that all the links would open in a pop-up. Managed to fix it by changing
    if(as[i].getAttribute(‘target’)!=”) to
    if(as[i].hasAttribute(‘target’))… just wondering if you used ‘getAttribute’ for a specific reason? Thanks

  48. Frank Stepanski Says:

    Hi,

    I submitted my donation on 2/24, how do I obtain your course book?

    Thanks,
    Frank

    Edit Sorry, I was on vacation. I sent you the coursebook via email!

  49. Brendan Says:

    Fantastic course. A life saver. Wouldn’t be able to do anything like that without your course. Thank you so much

  50. p Says:

    I believe you example

    http://www.onlinetools.org/articles/unobtrusivejavascript/ex_rollover2.html

    does not work in Safari 2.0.3 on mac os x 10.4, intel.

    or at least, it doesn’t work on mine …

  51. tim Says:

    Anyone got any ideas how to change pseudo classes with JS? I want to change the a:link, a:hover, and a:visited…

  52. Giorgio Says:

    Dear Chris,
    I will use your fantastic script in a working xhtml page .
    It work fairly but I get an error and I wasn’t be able to debug a runtime error (Row 37 Character 4 Error Argument not valid Code 0).
    Can you help me? All the best from Italy.

    JS
    function collapse()
    {
    if(!document.createTextNode){return;}
    var p=document.createElement(‘p’);
    p.appendChild(document.createTextNode(‘Click on the headlines to collapse and expand the section’));
    var heads=document.getElementsByTagName(‘h4′);
    for(var i=0;i<heads .length;i++)
    {
    var tohide=heads[i].nextSibling;
    while(tohide.nodeType!=1)
    {
    tohide=tohide.nextSibling;
    }
    cssjs(‘add’,tohide,’hidden’)
    cssjs(‘add’,heads[i],’trigger’)
    heads[i].tohide=tohide;
    heads[i].onmouseover=function()
    {
    cssjs(‘add’,this,’hover’);
    }
    heads[i].onmouseout=function()
    {
    cssjs(‘remove’,this,’hover’);
    }
    heads[i].onclick=function()
    {
    if(cssjs(‘check’,this.tohide,’hidden’))
    {
    cssjs(‘swap’,this,’trigger’,'open’);
    cssjs(‘swap’,this.tohide,’hidden’,'shown’);
    } else {
    cssjs(‘swap’,this,’open’,'trigger’);
    cssjs(‘swap’,this.tohide,’shown’,'hidden’);
    }
    }
    document.body.insertBefore(p,document.getElementsByTagName(‘h4′)0);
    }
    function cssjs(a,o,c1,c2)
    {
    switch (a){
    case ‘swap’:
    o.className=!cssjs(‘check’,o,c1)?o.className.replace(c2,c1):o.className.replace(c1,c2);
    break;
    case ‘add’:
    if(!cssjs(‘check’,o,c1)){o.className+=o.className?’ ‘+c1:c1;}
    break;
    case ‘remove’:
    var rep=o.className.match(‘ ‘+c1)?’ ‘+c1:c1;
    o.className=o.className.replace(rep,”);
    break;
    case ‘check’:
    return new RegExp(‘\\b’c1‘\\b’).test(o.className)
    break;
    }
    }
    }
    window.onload=collapse;

    XHTML 1.0 Strict

    Spots & Dots…

    To Tuscany.
    What game will I find there?

    Thousands of wild goats

    Alexandre Dumas, The Count of Monte Cristo

    Buy Now

    ….

  53. Glorya Rutter Says:

    Chris – I missed this submit comment area to the unobtrusive javascript, so I emailed you. Would you kindly check your email as I requested use of your script on a commercial site for a contact page. Many thanks. Glorya

  54. Amy Says:

    A really good one to cover is how to use the same javascript code on the same page even after we include more than one into the same onload. Because most identical javascripts won’t work on the same page.

  55. Joe P Says:

    After working through the Self Training exercises, I tried to transfer some of my knowledge to a website that I am working on. I have created a rollover button script similar to the one described in the training and it seems to work well. My only problem is this, the delay between the original image and the display of the “mouseOver” image is very noticeable even over highspeed connections. I believe that I need to cache the images so that the rollover is smoother, but every attempt I have made fails to preload the images and/or smooth out the rollover effect.

    Basically, my scripting calls a setupPage() function for window.onload. The setupPage() function executes the image preload script and the rollover setup script.

    My preload image scripts are as follows:

    function newImage(arg) {
    if (document.images) {
    rslt = new Image();
    rslt.src = arg;
    return rslt;
    }
    }

    function preLoadImages() {
    if(document.images) {
    image1 = newImage(“img/test.jpg”);
    }
    }

    Am I missing something on the preload script or the call to the script?

    Thanks in advance for any help.

Wait till I come! is the blog of Christian Heilmann , a developer evangelist living and working in London, England. Download vcard.

Feed me, Seymour: Entries (RSS) and Comments (RSS).