Wednesday, August 22, 2007

Hacking the moo.fx Accordion into the ultimate DHTML Accordion widget

For long, I've been looking for a good Accordion widget. First I began with the Yahoo one but unfortunately I ran into serious interference troubles when using it on the same page I was using the Google Ajax search widget.

Then I came to the Moo.fx accordion and that was the good one.
Still, this Accordion has some limitations:
  1. If your page is large, your browser won't scroll properly to the selected accordion tab.
  2. It would be better if the user could click on an expanded tab to collapse it back/
  3. You might want to have a collapse/expand button on each tab.
  4. You might want your accordion to degrade gracefully for non JavaScript enabled browsers (accessibility + SEO optimization) and have links to separate pages instead of tabs in this case.
  5. You want to disable the Mootools Accordion from following those accessibility links because you just want to expand the tab instead. The default Accordion implementation really sucks to this respect.
  6. You might want a tab CSS rollover effect.
I addressed all those issues for the web site I'm building now: Livetribune.com.

Issue #1:
Use the prototype scrolling feature to scroll to the expanded tab (I'm using Moo for Prototype):

myAccordion = new fx.Accordion(myLinks, myDivs, {onActive: function(toggler, element){
setTimeout("Element.scrollTo('" + toggler.id + "');window.scrollBy(0,-150)", 400);
});


Issues #2 and #3:
hack Accordion a bit to allow collapsing back any tab. And also add/remove a class name to the toggle DOM element in order to then use different CSS styles, for instance to display a different button backgound-image when to tab is collapsed or expanded.
Here is my modified Accordion class, I simply changed the showThisHideOpen function :

showThisHideOpen: function(iToShow){
var forceHide = iToShow == this.previousClick;
if (!this.options.alwaysHide){
if (forceHide)
this.previousClick = null;
else
this.previousClick = iToShow;
var objObjs = {};
var err = false;
var madeInactive = false;
this.elements.each(function(el, i){
this.now[i] = this.now[i] || {};
if (i != iToShow || forceHide){
this.hideThis(i);
$(this.togglers[i]).className = $(this.togglers[i]).className.replace(' expanded', '');
} else if (this.options.alwaysHide){
if (el.offsetHeight == el.scrollHeight){
this.hideThis(i);
madeInactive = true;
} else if (el.offsetHeight == 0){
this.showThis(i);
} else {
err = true;
}
} else if (this.options.wait && this.timer){
this.previousClick = 'nan';
err = true;
} else if (!forceHide){
this.showThis(i);
$(this.togglers[i]).className += ' expanded';
}
objObjs[i+1] = Object.extend(this.h, Object.extend(this.o, this.w));
}.bind(this));
if (err) return;
if (!madeInactive) this.options.onActive.call(this, this.togglers[iToShow], iToShow);
this.togglers.each(function(tog, i){
if (i != iToShow || madeInactive) this.options.onBackground.call(this, tog, i);
}.bind(this));
return this.custom(objObjs);
}
}


Issue #4, #5 and #6
We can consider it all together. For good accessibility and degradability, we need our accordion tooggle elements to be standards 'a' links. In that a href element, we put a link to a page that will display approximatively the same content than in the tab, possibly with a restful URL so that's good for SEO optimization.

Also, now that we have those a with their href, we are able to make a CSS rollover using the a:hover CSS pseudo selector to translate the background image of our toggle element. Remember: IE6 only accept the :hover selector on links and moreover the href of those links have to be defined for the :hover selector to apply properly.

But the catch is that if you use links with href as Mootools Accordion tooggle elements, your browser will follow the links and refresh the window instead of collapsing/expanding your accordion tabs. Ending your onActive custom function doesn't fix this nasty behavior. The only way I found was to patch the Accordion widget and add the return false; statement in the initialize function in the onclick trigger of Accordion.js:

initialize: function(togglers, elements, options){
this.now = {};
this.elements = $A(elements);
this.togglers = $A(togglers);
this.setOptions(options);
this.extendOptions(options);
this.previousClick = 'nan';
this.togglers.each(function(tog, i){
if (tog.onclick) tog.prevClick = tog.onclick;
else tog.prevClick = function(){};
$(tog).onclick = function(){
tog.prevClick();
this.showThisHideOpen(i);
return false;
}.bind(this);
}.bind(this));
this.h = {}; this.w = {}; this.o = {};
this.elements.each(function(el, i){
this.now[i+1] = {};
//el.style.height = '0';
el.style.overflow = 'hidden';
}.bind(this));
switch(this.options.start){
case 'first-open': this.elements[0].style.height = this.elements[0].scrollHeight+'px'; break;
case 'open-first': this.showThisHideOpen(0); break;
}
},


for details: have a look to Livetribune. I'm still working on this site, so please be indulgent if you are coming in a bad time. Livetribune will be ready in October.

Hope you can now build your ultimate accordion widget!

Raphaël Valyi.

Very Good Javascript lessons from Yahoo

So that's it, the web2.0 is popping everywhere all over the place and you need to make your sites sexy using Javascript and AJAX.

Then you probably feel JavaScript is a crappy language you never wanted to learn. So you keep learning bits of it as you need to. But doing this you'll probably end up reading a lot of crappy examples too and that will keep you in the vicious circle of thinking JavaScript is only a toy language.

But what if you take some 2 hours and really learn the basic correctly from Yahoo people who really know what they are doing? What if you finally learn what is that damned prototype stuff and how you can prevent libraries to collide their namespaces? I did it yesterday and I'll never regret it. I still prefer Ruby (JRuby) or Java many times over Javascript, but know at least I'll know how to code JavaScript and will be much more interested and effective in making the most of existing frameworks.

So if you can, really watch those screencasts:
Again, I've read lot's of Javascript tutorials or even books before. But nothing approaching the clarity and effectiveness of those lessons (except may be the AJAX in Action book).

Have fun,

Raphaël