Christian Heilmann

Posts Tagged ‘webdevtrick’

Creating accessible JavaScript menu systems – some basic steps that get forgotten

Tuesday, June 23rd, 2009

OK, so you want to write yet another collapsing menu/section/widget thingamabob. This is really easy and in the following few examples I want to point out some ideas that make it much easier for you.

The following is what we’re going to build – it doesn’t have any bells and whistles like smooth animation, random unicorns, swoosh noises or whatever else you might want to come up with, but it is a good solid base to work from.

Collapsing menu example by  you.

Click the image to go to the live example.

The Markup

The first trick is to use easy to understand and highly styleable markup for your widget. You see a lot of widgets that use endless DIVs, SPANs, IDs on every element and classes on every sub-element. If you build a simple solution (and not a catch-all reusable widget that is part of a framework) none of that is needed. In this case the markup is the following:


In other words: a nested list with headings for the different sections. This makes sense without styling or scripting which is still and important point considering mobile devices or environments that have JavaScript turned off (and yes, they are not mythical but do exist).

Assistive access does not mean lack of JavaScript

The first mistake that people do is to think that this is all you need to be accessible – as users with assistive technology have no JavaScript, right? Wrong. Assistive technology is not a browser replacement but in most cases hooks into browser functionality and offers easier access. In other words, what the browser gets the assistive technology gets and you have to make sure it still makes sense without a mouse (to name just one example).

Thinking too complex

The next extra step people take is starting to loop through the menu construct to hide the elements with JavaScript. This is made much easier with clever libraries that don’t use the DOM to do that but piggy-back on the CSS selector engine, but for good old IE6 this is still not an option. And you don’t need to. In order to hide the nested lists in the above example all you need to do is to apply a class to the main list and leave the rest to CSS:




This means that in order to show any of the nested lists, all you need to to is to add a class called “open” to the parent list item. The CSS to undo the hiding is the following:



You can add the classes on the backend when you render the page – which makes a lot more sense to indicate a current open section of the page than any JavaScript analyzing of window.location or other shenanigans. See the first stage demo. You can also define a “current” class which means you can style it differently and the script will simply quietly skip that section and not collapse it at all.

That is the beauty in leaving all the showing and hiding to the CSS. Of course you can’t animate in CSS (unless you use webkit), but you can make your animation precede the change in classes.

Hiding and showing

So the way to show and hide things is simply to add and remove classes. We need to use event handling to listen for click events to do that. Click events are the only safe ones as they fire with keyboard and mouse. The problem is that a header can be made clickable, but is not keyboard accessible. To work around this, let’s use DOM scripting to inject a button element into the headers. You can use CSS to make it look not like a button:



The second stage demo shows how this works and here’s the code.


(function(){

// get the menu and make sure it exists
var m = document.getElementById(‘menu’);
if(m){

// add a class to allow the CSS magic to happen
m.className = ‘js’;

// loop over all headers
var headers = m.getElementsByTagName(‘h3’);
for(var i=0;headers[i];i++){
// get the content of the current header
var content = headers[i].innerHTML;
// create a button, delete the content of the header
// replace it with the button and set the content to
// the cached header content
var a = document.createElement(‘button’);
headers[i].innerHTML = ‘’;
headers[i].appendChild(a);
a.innerHTML = content;
}

// apply a single click event to the menu
m.addEventListener(‘click’,function(e){

// find the event target and chec that it was a button
var t = e.target;
if(t.nodeName.toLowerCase()===’button’){

// get the LI the button is in
var mom = t.parentNode.parentNode;

// check if its class name is not content and
// remove the open class if it exists, else
// add an open class

if(mom.className!==’current’){
if(mom.className = 'open'){
mom.className = '';
} else {
mom.className = 'open';
}

}

// don't do the normal things buttons do
e.preventDefault();
}

},true);
}

})()

This code also takes advantage of event delegation – there is so no need to apply and event handler to each heading – even if that is really easy to do with a library and an each() or batch() command. Event handling is a great concept and the tricks you find when you bother to read the docs are staggering.

Hiding is not removing

This is where most menu scripts stop. Great stuff, it expands and collapses and everybody is happy. Unless you are an unhappy chap and you need to use a keyboard to access it – or an older mobile device. Try it out yourself – use the second example by tabbing from link to link. You’ll find that you have to tab through the invisible links to reach the next section to open or hide. That surely can’t be the idea, right?

The trick to work around is to change the tabindex property of an element. A value of 1 will remove it from the tabbing order and setting it back to 0 will make them available again. So, to make keyboard access easier, let’s remove the tab order upfront and reset or remove it when we show and hide the menus. This is terribly annoying as we have to loop through the links. I hate loops, but browsers are not our friends.


(function(){

// get the menu and make sure it exists
var m = document.getElementById(‘menu’);
if(m){

// add a class to allow the CSS magic to happen
m.className = ‘js’;

// loop over all headers
var headers = m.getElementsByTagName(‘h3’);
for(var i=0;headers[i];i++){
// get the content of the current header
var content = headers[i].innerHTML;
// create a button, delete the content of the header
// replace it with the button and set the content to
// the cached header content
var a = document.createElement(‘button’);
headers[i].innerHTML = ‘’;
headers[i].appendChild(a);
a.innerHTML = content;
}

// loop over all nested lists
// and set the taborder of the nested links to -1 to
// remove them from the tab order

var uls = m.getElementsByTagName(‘ul’);
for(var i=0;uls[i];i++){
if(uls[i].parentNode.className!‘open’ &&
uls[i].parentNode.className!==’current’){
var as = uls[i].getElementsByTagName(‘a’);
tabOrder(as,-1);
}

}

// apply a single click event to the menu
m.addEventListener(‘click’,function(e){

// find the event target and chec that it was a button
var t = e.target;
if(t.nodeName.toLowerCase()===’button’){

// get the LI the button is in
var mom = t.parentNode.parentNode;

// check if its class name is not content and
// remove the open class if it exists, else
// add an open class. Also, remove or add the
// links to the tab order

if(mom.className!==’current’){
if(mom.className = 'open'){
mom.className = '';
tabOrder(as,-1);
} else {
mom.className = 'open';
tabOrder(as,-1);
}

}

// don't do the normal things buttons do
e.preventDefault();
}

},true);
}

// remove from or add elements to the tab order

function tabOrder(elms,index){
for(var i=0;elms[i];i++){
elms[i].tabIndex = index;
}

}
})()

The third demo does exactly that – use your keyboard and marvel at being able to tab from parent element to parent element when sub-sections are collapsed.

Fixing it for Internet Explorer 6 and 7bad old browsers

Now, this is all good and fine but of course we need to fix the code to work around the quirks of browsers that don’t understand the W3C event model or consider an element with a name attribute the same as one with and id. We could fork and fix in the code we have here, but frankly I am tired of this. People give us libraries to work around browser differences and to make our lives easier. This is why we can use YUI for example to make this work reliably cross-browser:



Good start

This is just a start to make this work. A real menu should also have support for all kind of keys that a menu like this in a real app gives us and notify screen readers of all the happenings. For this, we need ARIA. Right now, I’d be happy to get some comments here :)

The road to professional web development – my university talk in Taiwan (now with Audio)

Thursday, April 16th, 2009

I just came back from giving my first talk in Taiwan and I have to say it seemed to have worked out well. The room was packed and people asked very good questions afterwards.

The first mistake - presentational markup

Talking and translation into Chinese

[slideshare id=1297512&doc=1297512]

In the slides I covered the history of web development, what we did wrong and what we should avoid in the future. I also covered the YUI and how it embodies some of the priniciples great web development is based on.

Update: Liang-Bin Hsueh posted an audio recording of the talk missing only the first five minutes.

Creating progressively enhanced DOM applications with ViewsHandler

Tuesday, August 19th, 2008

When I taught a bunch of students last month the ways of the DOM and explained progressive enhancement they were very happy about the ideas but rightfully exclaimed that DOM scripting can get verbose and repetitive. This is when I had to idea to write ViewsHandler , a small framework to write DOM applications.

ViewsHandler is not meant to be a JavaScript templating engine, as I found that whilst you have to create a lot of HTML with DOM, the parts that change in the app are not that many. Templating engines would replace the whole view, ViewsHandler instead offers you to create HTML, add it to a shell or application canvas and store references to the parts that change. That way you only create your HTML once and then only do minor changes to cached DOM elements when you need to.

As a demo I created a small Flickr slide show using ViewsHandler which was exactly the task that I had given my class :)

What do you think?

Conjuring YUI from thin air

Saturday, August 2nd, 2008

I love the YUI loader as it is a great way of including the YUI on the fly. The coolest bits about it is that it gets the YUI components from the CDN and knows the dependencies so I don’t have to. So if I need the YUI for something, I don’t need extra SCRIPT nodes a maintainer has to include, just my SCRIPT. However, what we still need is including the YUI loader itself.

Unless… you use the YAHOO_config listener. This thing is older than both YUI get and YUI Loader and is an object method that gets called every time a YUI component is loaded. So why not load the YUI Loader using this?

One problem is that the YUI Loader doesn’t call the config listener saying it is a loader, but saying it is the get utility. Another issue is that it does not work to execute the Loader immediately after it called itself “get”. The workaround is to use a timeout.

Wrap all of that inside the YAHOO_config object and you’ll conjure the YUI out of thin air. The following example loads YUI Dom, YUI Event and alerts “done” once all is ready. Check it out here


YAHOO_config = function(){
var s = document.createElement(‘script’);
s.setAttribute(‘type’,’text/javascript’);
s.setAttribute(‘src’,’http://yui.yahooapis.com/2.5.2/’+
‘build/yuiloader/yuiloader-beta-min.js’);
document.getElementsByTagName(‘head’)[0].appendChild(s);
return{
listener:function(o){
if(o.name === ‘get’){
window.setTimeout(YAHOO_config.ready,1);
}

},
ready:function(){
var loader = new YAHOO.util.YUILoader();
var dependencies = [‘yahoo’,’dom’,’event’];
loader.require(dependencies);
loader.loadOptional = true;
loader.insert({
onSuccess:function(){
console.log(‘done!’);
}

});
}

};
}();

Thanks to Alex Liu to get the setTimeout trick.

Agent YUI – don’t miss these YUI tutorials

Sunday, July 27th, 2008

My esteemed colleage Klaus Komenda seems to spend as much time as I do writing cool stuff for the masses, but somehow he doesn’t crop up in a lot of to-read lists. For shame, I say, pulling up my trousers until they reach my armpits (yes, I watched Simpsons) and I point you, esteemed reader to a series of articles explaining the YUI from ground up entitled Agent YUI:

Yes, I am also taken with them as I like Bond a lot.