November 17, 2009

Progressive enhancement example: Language Selection

It's easy to get lost in the Internet while looking for information. It's one of its greatest points: There are thousands of millions of websites out there. Information is the key purpose of the net and nobody can deny it.

But, as in many other cases, quantity does not equal quality. Poor designed websites, too bloated, with many inlined styles or little semantics (if none), are begging for trouble: Their information have lost accessibility.

Accessibility: Why are we here?

When it comes to accessibility, the Net clearly fails the test. Many of those rich Javascript websites are not accessible by any means but by a small portion of the community, thus devaluing its own information. Not only there are plenty of browsers with no JavaScript capabilities or with Javascript disabled, if not readers for blind people, text-only browsers, interfaces specially adapted for disabled people, web crawlers and bots.

Companies, as such, should take into consideration disabled people, but, harsh as it sounds, they don't give a shit. But if not for them, do it for the bots. Everyone wants to look pretty to Google's eyes, right?

Introducing Progressive Enhancement

So, here comes progressive enhancement in our help. Designing with PE in mind means to develop the content for the least capable devices, and add features to only those able to use them, thus enriching their experience.

So, for a text browser, the content will be plain text (after all, that's what 90% of the HTML content is, isn't it?). For CSS1 browsers, you may add some style. Add some more CSS2 selectors to improve the experience in brand new browsers. Add some unobtrusive javascript to finish the work for the most capable ones. That's it! It was no rocket science, was it?

HTML: Hands on!

The following example shows the building of a "Choose language" menu using progressive enhancement. First comes first, so we begin with the HTML markup:

<div class="langs">
   <h2>Change language</h2>
   <ul>
      <li><a href="http://en.example.org" class="lang-en" title="English"><span>English</span></a></li>
      <li><a href="http://es.example.org" class="lang-es" title="Español"><span>Español</span></a></li>
      <li><a href="http://fr.example.org" class="lang-fr" title="Français"><span>Français</span></a></li>
      <li><a href="http://ca.example.org" class="lang-ca" title="Català"><span>Català</span></a></li>
      <li><a href="http://it.example.org" class="lang-it" title="Italiano"><span>Italiano</span></a></li>
      <li><a href="http://de.example.org" class="lang-de" title="Deutsch"><span>Deutsch</span></a></li>
      <li><a href="http://pt.example.org" class="lang-pt" title="Português"><span>Português</span></a></li>
   </ul>
</div>

It's easy to create this with a dynamic language, like our beloved PHP:

// current language first
$languages = array(
   'en' => 'English',
   'es' => 'Español',
   'fr' => 'François',
   'ca' => 'Català',
   'it' => 'Italiano',
   'de' => 'Deutsch',
   'pt' => 'Português',
);
?>
<div class="langs">
   <h2>Change language</h2>
   <ul>
      <? foreach($languages as $langISO6391 => $langName): ?>
         <li>
            <a href="http://<?=$langISO6391?>.example.org" class="lang-<?=$langISO6391?>" title="<?=$langName?>">
               <span><?=$langName?></span>
            </a>
         </li>
      <? endforeach ?>
   </ul>
</div>
  • Result of step 1

    Our simple HTML example rendered by a graphic browser

Now it looks equally fine in all graphic and text browsers. Its markup is fully semantic, so a web reader could hint the user, easing its navigation. Also, as there's no content generated dynamicly on the client, the bots can read all the content and collect the links.

CSS1: Adding some cool style

Let's make our first jump to style and add some flags. Many would argue that using flags for languages is not correct, but as it's a common misconception, I will be one more:

  • CSS sprite flags for languages

    An image that contains two rows of flags, side-by-side, ready for the CSS sprite technique

Add this to the <head> tag:

<link rel="stylesheet" href="/css/lang.css" type="text/css" />

And create the lang.css file with the next content:

.langs a {
   background-image:url(/img/lang.png);
   display:block;
   font-size:0;
   height:11px;
   width:16px;
}
.lang-ca { background-position:0 -11px; }
.lang-de { background-position:-16px -11px; }
.lang-en { background-position:-32px 0; }
.lang-es { background-position:-16px 0; }
.lang-fr { background-position:-48px 0; }
.lang-it { background-position:-32px -11px; }
.lang-pt { background-position:-48px -11px; }

.langs ul {
   list-style:none;
   margin:0;
   overflow:hidden;
   padding:0 0 0 6px;
}

.langs li {
   float:left;
   margin:2px;
}

.langs li a span { margin-left:-9999em; }

Text browsers obviates CSS style declarations, so this change doesn't affects them. Neither will web readers, as the images are in the CSS and the markup hasn't changed at all. But in all our graphical browsers we may see a different story.

  • Result of step 2

    Example rendered by a CSS1 capable browsers

Javascript: Toward user interaction

Now enters the Javascript. Let's say we want to show only the current language but, when the link is clicked, all the flags must unfold. Clicking the current language once more, closes it back. This is easy to achieve with some CSS+Javascript tricks and the fabulous jQuery. Let's add to our HTML:

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

Now, create the lang.js file and type this:

$('.langs li:first-child a')
    .live('click', function(ev){
        ev.preventDefault();
        $(this).parents('.langs').toggleClass('open');
})

I love how jQuery makes things easy! This adds/remove the 'open' class from the 'langs' parent only when the first flag is clicked. Now we can fall into the trap of adding CSS to style the opened list and the closed one, but we will be changing the style in every non-Javascript capable browsers, too, maybe rendering the menu useless. The trick is to add this just after opening the body tag in the HTML.

<script>document.body.className += (document.body.className === '' ? '' : ' ') + 'js'</script>

This simple one-liner adds the 'js' class to the body. But ONLY if the browser supports and executes Javascript! That makes a difference in our CSS, so now we can type the next without worry:

body.js .langs li + li { display:none; }
body.js .langs.open li + li { display:block; }
body.js .langs h2 { float:left; margin-left:-9999em; }

Theres some CSS2 in it, so if you want your CSS1 browsers to render it correctly, you must workaround this. The easy solution is to add a class to the first entry in the HTML markup. Another way is to use Javascript to add the class on page load. Let's do the latter:

jQuery(function($){
   $('.langs li:first-child').addClass('first')
})

Now the previous CSS looks like this:

body.js .langs li { display:none; }
body.js .langs .first, body.js .langs.open li { display:block; }

Text browsers doesn't use javascript so this changes made no sense to them, nor for any Javascript disabled browser or web readers.

CSS2: Adding some even cooler style

Finally, we will add a CSS2 arrow next to the first flag, to catch the attention of the user and to emphasize its dynamic nature. We will use the next simple sprite image for the left/right arrow:

  • The itty-bity arrow image

    arrows

And now we will unleash all the power of CSS2:

body.js .langs.open li + li a:before { display:none; }
body.js .langs li a:before {
   background-image:url(http://localhost/chorus/img/arrows.png);
   background-position:-4px 0;
   content:'';
   display:block;
   font-size:0;
   height:9px;
   left:19px;
   position:relative;
   top:1px;
   width:5px;
}
body.js .langs.open li a:before {
   background-position:0 0;
   left:-8px;
}

Note here the use of the + selector (adjacent sibling) is correct, because this change is a mere enhancement, not necessary for CSS1 only browsers. Also, it uses the complex and versatile 'before' pseudo-element. This is a must in every real browser. It's a pity IE6 still exists.

  • Example rendered by a Javascript and CSS2 capable browser

    Only the current language is shown
  • Click to unfold the flags

    All the flag are displayed

Happy end

Whoa! The menu now looks pretty different for sure after all this steps. We've achieved the top of the web art in terms of accessibility. Our website will be rendered correctly in all major browsers, and remain accessible even with Javascript disabled. Also, the blind impaired and text browser users still have full access to all the basic funcionality: Information. Our loved Google Bot and the rest of the bots will be able to crawl our html, index it and fully understand the semantics, with no meddlers.

I hope you liked it and that will be of use to anyone. Schüß!

No comments:

Post a Comment