Operation Cleanout in process

Web development in the last year(s) has undergone a change, we stopped mixing presentation with structure, and thus made it easier to rebrand and change the layout throughout the site simply by changing the style sheet.

Further separation is possible by not using any inline styles and classes, but inheritance and contextual selectors.

HTML:
<table border="0" cellpadding="0" width="100%" cellspacing="5">
<tr>
 <td><font face="Arial" size="-2">Lorem Ipsum</font></td>
</tr>
</table>

became

CSS:
td.content {font-family:Arial,sans-serif;font-size:12px;}
HTML:
<table cellpadding="0" 
style="width:100%;border:none" cellspacing="5">
<tr>
 <td class="content">Lorem Ipsum</td>
</tr>
</table>

and finally

CSS:
body {font-family:Arial,sans-serif;font-size:12px;}
p {padding:5px;}

HTML:
<p>Lorem Ipsum</p>

The same evolution can and must happen for Javascript.

Keeping Javascript separate

The first rule of the unobtrusive Javascript club is don't talk about the unobtrusive Javascript club. No, hang on, it is...

Never, under any circumstances add Javascript directly to the document.

One of the big powers of Javascript is that it comes in a seperate file. Much like CSS, this means you can apply one collection of functions to every page of the site, and if you need to change its functionality, you can do that in one document rather than going through each page.

This means - unless there are special circumstances - all we ever need to see in an HTML document is:

<script type="text/javascript" src="scripts.js"></script>

This is all we ever need, no more inline Javascript. Rinse and repeat.

Javascript is an enhancement, not a secure functionality

We only use Javascript to enhance a functionality that is already given, we don't rely on it. Javascript can be turned off or filtered out by proxies or firewalls of security aware companies. We can never take it for granted.

This does not mean we cannot use Javascript, it only means we add it as an option rather than a requirement.

HTML:
<form action="index.php" onsubmit="return checkform(this)">
 <p><label for="login">Login:</label>
 <input type="text" name="login" id="login" /></p>
 <p><label for="pw">Password:</label>
 <input type="password" name="pw" id="pw" /></p>
 <p><input type="submit" value="send" /></p>
</form>
Javascript:
function checkform(f)
{
 var error='';
 error+=f.login.value==''?'\nlogin':'';
 error+=f.pw.value==''?'\npassword':'';
 if (error!='')
 {
  alert('Please enter the following:'+error);
 } 
 return error=='';
}

Is perfectly valid, and will prevent the users from experiencing a reload when they forgot to enter a value.

HTML:
<form action="index.php">
 <p><label for="login">Login:</label>
 <input type="text" name="login" id="login" /></p>
 <p><label for="pw">Password:</label>
 <input type="password" name="pw" id="pw" /></p>
 <p><input type="button" onclick="checkform()" value="send" /></p>
</form>
Javascript:
function checkform()
{
 var f=document.forms[0];
 var error='';
 error+=f.login.value==''?'\nlogin':'';
 error+=f.pw.value==''?'\npassword':'';
 if (error!='')
 {
  alert('Please enter the following:'+error);
 } else {
  f.submit();
 }
}

Seemingly does the same, but has one problem: If Javascript is turned off, the button does nothing whatsoever, no matter how often the frustrated user clicks it. Let's repeat: Javascript is not to be trusted, let's not rely on it!

Check the availability of an object before accessing it

A lot of Javascript errors are occuring simply because the scripter was too lazy to check if a method or an object is available or not.

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

could result in a Javascript error, when the object o is not available.

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

works all the time.

This technique is mainly used to check if a browser is able to support a certain Javascript functionality. Back in the days of trial and error while developing (it didn't matter, the clients paid, or the .COM we worked for) this was achieved via a browser-sniffing script, an ill-fated concept from the start (every time a browser was updated or a new one came around, the sniffer script needed updating). For most of the examples used nowadays it is necessary to check if the browser is able to understand the W3C[1] DOM[2], which brings us to the next rule.

Create Javascript, not browser specific dialects

Unless there is a really good reason, we should never use browser specific extensions to the web standards. The times of checking for document.layers (Netscape 4.x) or document.all (Internet Explorer < 5) are over. Modern browsers all support the document.getElementsById DOM, and that's what we use and test for.

Javascript:
function doDOMstuff()
{
 if(document.getElementById && document.createTextNode)
 {
  [...]
 }
}

The second check is only necessary as some earlier versions of Opera claimed to understand the DOM, but failed to do so properly.

Don't hijack other script's variables

When we create a function or functionality we should make sure that all the variables used are local, to avoid one function overwriting a variable that is in use by another.

Javascript:
var i=42; // global variable
function dothings()
{
 for (i=0;i<200;i++) // i gets changed
 {
  // some code 
 }
}
function dothingsright()
{
 var i; // define i locally
 for (i=0;i<200;i++)
 {
  // some code 
 }
}
alert(i); // = 42
dothingsright()
alert(i) // = 42 (due to local definition)
dothings()
alert(i) // = 200, oops!

Keep effects mouse independent

Just making sure that things only work when Javascript is enabled is not enough. We also have to be aware of users who cannot use a mouse at all or only badly. Therefore we must ensure that all effects are triggered by mouse and by keyboard access.

The biggest problem with mouse independence are form elements that get validated or trigger an effect onchange or onblur. Don't use them, it is as simple as that.

A common use of this inaccessible technique is select boxes as navigations.

HTML:
<form>
<p>
<label for="go2">Go to:</label>
<select name="go2" id="go2" onchange="send(this)">
 <option value="index.html">Home</option>
 <option value="chapter1.html">Operation Cleanout</option>
 <option value="chapter2.html">Reaching things</option>
</select>
</p>
</form>
Javascript:
function send(f)
{
 var chosen;
 chosen=f.options[f.selectedIndex].value;
 self.location=chosen;
}    
Test this inaccessible selectbox example.

Seemingly the main problem is that when you use a keyboard to tab to the element and hit arrow down to select something you can never get past the first option, as send() gets triggered when you go from the first to the second option.

Experienced keyboard users will know though that you have to hit alt+arrow down first to expand the whole drop-down before choosing.

However, this is not known to all users. Furthermore, the example does nothing when Javascript is not available.

HTML:
<form action="send.php" method="post" onsubmit="return send(this)">
<p>
<label for="go2">Go to:</label>
<select name="go2" id="go2">
 <option value="index.html">Home</option>
 <option value="chapter1.html">Operation Cleanout</option>
 <option value="chapter2.html">Reaching things</option>
</select>
<input type="submit" value="go" />
</p>
</form>
Javascript:
function send(f)
{
 var chosen;
 chosen=f.go2.options[f.go2.selectedIndex].value;
 self.location=chosen;
 return false;
}    
PHP:
<?PHP if(isset($_POST['go2'])){
 header('Location:'.$_POST['go2']);
}?>
Test this accessible selectbox example.

This, in comparison, works fine, and saves us one server hit to the redirect script send.php, which gets accessed only when Javascript is not available.

What about the onkeypress?

When we read through the Web Content Accessibility Guidelines[3], we are asked to use device independent event handlers for our scripts:

Otherwise, if you must use device-dependent attributes, provide redundant input mechanisms (i.e., specify two handlers for the same element): Use "onmousedown" with "onkeydown". Use "onmouseup" with "onkeyup" Use "onclick" with "onkeypress"

This sounds perfect in theory, but in real life situations, the onkeypress handler is badly supported in different browsers. Users that are dependent on keyboard browsing do normally have a key set up to simulate clicking, either the enter key or space bar, and both of these do fire the onclick event. By using onkeypress we might hijack other keyboard functionality the user wants. Examples are the type ahead functionality[4] of Mozilla, Opera's extensive keyboard shortcuts[5] (like 'A' for next link) or JAWS' keyboard controls[6].

So by doing the right thing according to the accessibility guidelines, we might make our script even less accessible.

In the examples to follow in this course, we will add onkeypress as extra handlers, but keep them commented out. This is done to be able to enable these should the necessity occur, or if you have to pass an accessibility audit testing with automated tools rather than real people.

Links

Creative Commons License Unless otherwise expressly stated, all original material of whatever nature created by Christian Heilmann and included in this web site and any related pages is licensed under the Creative Commons License.