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. :-)


May 21st, 2005 at 1:39 pm
I stumbled upon the link to this blog item :D.
Thank you for the fantastic course! I have learned a lot from it.
May 22nd, 2005 at 9:17 pm
Make it a protected wiki, perhaps.
May 24th, 2005 at 12:37 pm
well I can just say it’s damn useful, and I referred it a lot :).
the web needs more of that.
May 24th, 2005 at 4:09 pm
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, “”);
May 25th, 2005 at 7:30 pm
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.
May 25th, 2005 at 8:13 pm
sebastian, you can even get rid of that temp variable with
o.className = o.className.replace(new RegExp('\s*\b'+c+'\b'),'');
May 26th, 2005 at 9:53 am
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 …
June 4th, 2005 at 7:15 pm
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?
June 5th, 2005 at 9:04 pm
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.
June 5th, 2005 at 10:43 pm
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.
June 6th, 2005 at 12:06 am
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
June 6th, 2005 at 9:11 pm
Thanks for the feedback.
June 7th, 2005 at 10:04 am
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.
June 11th, 2005 at 1:11 am
Great stuff. You make this stuff fun to learn in debth.
June 13th, 2005 at 2:57 am
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!
June 14th, 2005 at 1:39 am
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 …
July 2nd, 2005 at 4:36 pm
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
July 20th, 2005 at 10:54 pm
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”
July 24th, 2005 at 10:07 am
the cloneNode function requires an attribute `true` to function:
somenode.cloneNode(true);
August 7th, 2005 at 7:57 pm
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)
‘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…
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
any thoughts/ideas/comments would be appriciated!
August 14th, 2005 at 2:34 pm
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?
August 29th, 2005 at 7:11 pm
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
September 1st, 2005 at 5:49 pm
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!
September 2nd, 2005 at 11:40 pm
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.
September 13th, 2005 at 7:03 pm
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
September 15th, 2005 at 5:57 pm
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.
September 22nd, 2005 at 8:54 pm
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.
September 22nd, 2005 at 9:03 pm
Of course it is, just like any other event handler the form can get an onsubmit handler.
document.getElementsByTagName(‘form’) [0].onsubmit = functionname;
September 25th, 2005 at 2:01 pm
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.
September 30th, 2005 at 2:52 pm
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.
October 3rd, 2005 at 6:43 pm
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.
October 27th, 2005 at 8:15 pm
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 variablelengthand a functionitem(), 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
lengthvariable of the HTMLCollection, it doesn’t know two of the elements in the object (you say array) that are notHTMLParagraphElements.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.)
November 3rd, 2005 at 8:29 pm
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
November 5th, 2005 at 2:40 pm
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
November 21st, 2005 at 2:01 pm
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/ .
November 22nd, 2005 at 1:25 am
This was a great course, and I’ve begun sending my readers to your course as an introduction to how to code unobtrusively.
December 2nd, 2005 at 3:15 pm
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)
December 8th, 2005 at 3:55 am
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.
December 8th, 2005 at 7:01 pm
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!
December 16th, 2005 at 2:15 am
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!
January 5th, 2006 at 10:20 pm
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:
January 13th, 2006 at 11:25 pm
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?)
January 16th, 2006 at 4:44 am
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″
January 16th, 2006 at 12:11 pm
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
January 16th, 2006 at 12:13 pm
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
January 24th, 2006 at 10:23 pm
Many of your examples don’t pass JSLint . It is deliberate?
January 25th, 2006 at 3:55 pm
Sorry for the link. JSLint: http://www.crockford.com/jslint/
February 14th, 2006 at 6:22 am
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
February 26th, 2006 at 1:48 am
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!
March 24th, 2006 at 4:37 pm
Fantastic course. A life saver. Wouldn’t be able to do anything like that without your course. Thank you so much
April 28th, 2006 at 6:38 pm
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 …
July 14th, 2006 at 3:47 pm
Anyone got any ideas how to change pseudo classes with JS? I want to change the a:link, a:hover, and a:visited…
July 18th, 2006 at 9:57 am
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…
What game will I find there?
Thousands of wild goats
…
Alexandre Dumas, The Count of Monte Cristo
Buy Now
….
August 7th, 2006 at 2:51 pm
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
October 31st, 2006 at 1:22 pm
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.
November 8th, 2006 at 10:42 pm
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.