Archive: February, 2008

JavaScript, abbr and Internet Explorer

The other day I wrote about how screen readers handle abbreviations. I mentioned the lack of support for the abbr element in Internet Explorer (IE 6 and below) and how it may have led people to believe that screen readers working with Internet Explorer don’t support it either. I went on to explain that modern screen readers do support both abbr and acronym elements, but perhaps not in the way that developers expect.

I’ve been doing a bit of poking around and it seems that abbr elements have been available in Internet Explorer’s Document Object Model (DOM) for some time, which I didn’t realise. I’ve been able to access the title attributes of abbr elements as far back IE 5.01. However, what we haven’t been able to do is style these elements with CSS or access their child nodes (i.e. their content) with JavaScript; that is until IE 7 introduced proper abbr support.

There have been a few ways to force IE 5 and 6 to support abbr; from using extra markup, through hacking it with JavaScript, to using the html namespace to trick the browser. Now it seems there’s been a really simple JavaScript solution that’s lain dormant for about four years.

A simple JavaScript fix using createElement()

Thanks to a tip from Sjoerd Visscher, Jeremy Keith observed that we can con Internet Explorer into understanding abbr and persuade it to recognise the element as a proper part of its DOM.

So, armed with this new knowledge from Sjoerd and Jeremy, I ran another test using this createElement() JavaScript fix. The results show that IE 5.5 and IE 6 will add abbr to the DOM properly if you add this at the beginning of your JavaScript:

document.createElement("abbr");

You can style it and it even displays the title attribute as a tool tip for you! Lovely! Internet Explorer 5.01 doesn’t play ball – why am I not surprised?!

Remember, we’re not doing this for the benefit of screen readers. The modern screen readers JAWS and Window-Eyes can expand the contents of abbr and acronym elements using their title attributes. All we are doing is making sure Internet Explorer itself is fully aware of abbr, enabling us to style it with CSS and manipulate it properly within the DOM. So, whether or not you want to do this is entirely based on what you do in the browser – don’t do it for the sake of assistive technology.

Further reading

PHP + Twitter + Google Calendar + SMS

Working on a little side project, I wanted to be able to add an event to Google Calendar by text message like our American friends can using GVENT. I figured I could hook my Twitter account up to Google Calendar using their APIs. I tinkered with it for a couple of hours and it works!

Background

If you don’t know about Twitter, it’s a free service that allows you to share messages with your network of friends by a number of means, which includes via text messages sent to Twitter from your mobile phone.

Twitter has this useful feature called direct messages. It enables private messaging between friends over the Twitter system, provided you and your friend are following each other on Twitter. This means that you can send a message over the system without it turning up in your public time line of messages – your ‘tweets’. You just put a letter ‘D’ and your friend’s Twitter username in front of your message and send. So, I can send a text message to Jon via Twitter like this “d jontangerine Why, I’d love tangerines by post! Thank you!”

How is this useful for adding an event to Google Calendar? Well, a short text message is all I need to add an event using Google Calendar’s really smart Quick Add feature. A script set up to periodically check for new direct messages via the Twitter API can take the body of any message and pass it to the Google Calendar API.

It’s a bit round about in implementation, but it allows you to update a Google Calendar by SMS. In fact, you could use any Twitter-ing interface (Twitter.com, Twitterific), but then you may as well Quick Add your event with Quicksilver (Mac users) or some other widget or even – shock horror – sign into the Google Calendar site!

Obviously, I’m not the only person who’s thought of hooking up the APIs; Twittercal will give you basic functionality if that’s all you need. It doesn’t allow you to post to any calendar other than your default one, which wasn’t useful for my application, and I wanted to be able to do a bit of processing in PHP before posting the event to the calendar.

Twitter to PHP

So, I can set up a new Twitter account for my application, become friends with it (!) and send messages to it. That’s a good start. Now I need a PHP script that listens in for new direct messages on my new Twitter account.

To interact with Twitter, it helps to be familiar with the Twitter API. You might also find a wrapper for the Twitter API useful. It’s not entirely necessary, but will save you a bit of time. Here’s one David Billingham made earlier: Twitter PHP Library (Ver 0.1). It’s not complete, but it’s a good start.

I’ll not use the library for now so that I can show you how to talk to Twitter using our friendly PHP CURL functions.

  1. Set yourself up with an account for your application. Follow the new account and make sure it’s following you as well. Direct messages won’t work unless the feeling’s mutual!
  2. Grab the code below and save it to a new file on your Web server, e.g. “tcal.php”. The code is commented to guide you through what it does.
  3. Set TWITTER_CREDENTIALS to your application’s Twitter username and password.
  4. Make sure you have some direct messages sent to the Twitter account. Try sending it a test message, something like “d yourapp Smashing Pumpkins in The O2 Arena on Saturday at 7pm”.
  5. Run the script and you should see your messages in XML.
<?php

// Define credentials for the Twitter account define('TWITTER_CREDENTIALS', 'twitter_username:twitter_password');
// Set up CURL with the Twitter URL and some options $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, 'http://twitter.com/direct_messages.xml'); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 2); curl_setopt($ch, CURLOPT_VERBOSE, 1); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// Twitter uses HTTP authentication, so tell CURL to send our Twitter account details curl_setopt($ch, CURLOPT_USERPWD, TWITTER_CREDENTIALS);
// Execute the curl process and then close $data = curl_exec($ch); curl_close($ch);
// If nothing went wrong, we should now have some XML data echo '<pre>'.htmlentities(print_r($data, true)).'</pre>';
?>

Using a library obviously tidies things up and should provide some built-in error checking and return an XML object to us. You can use the library I mentioned earlier, but you’ll need make sure you add your Twitter account details to the class. We end up with this:

<?php

// The Twitter library require_once 'twitter.class.php';
// Create a new Twitter object and query for direct messages $twitter = new Twitter(); $direct_messages = $twitter->directMessages();
// We should now have some XML data in an object echo '<pre>'.htmlentities(print_r($direct_messages, true)).'</pre>';
?>

Okay, now we have a nice XML object to work with.

Google Calendar from PHP

Now we’re tuned into Twitter, we want to send any messages on to Google Calendar.

The Google Calendar API is a Google data (GData) API. The easiest way to interact with Google Calendar in PHP is with Zend’s standalone GData client library. This library also comes as part of the Zend Framework, but unless your application uses the framework already, it’s a bit of a sledgehammer!

The GData client library needs PHP version 5.1.4 or higher. If you want PHP 4 compatibility, this might make life a bit harder for you. PHP 4 coming to the end of its life and support for it ceased at the end of 2007. If you’re not yet using PHP 5, I strongly suggest your developments move towards using it.

After downloading the client library and installing the ‘Zend’ directory somewhere useful in your application, you can start to use it! Try adding this chunk of code in place of where we echoed the XML data from Twitter. Again, the comments tell you what’s going on.

<?php

// Define credentials for the Google Calendar account define('GCAL_USER', 'google_user@example.com'); define('GCAL_PASS', 'google_password');
// If you need to target a specific calendar, uncomment and enter // its calendar ID here. The calendar ID is available on the // calendar settings page, next to the Calendar Address buttons. //define('GCAL_ID', '[hash]@group.calendar.google.com');
// Include Zend GData client library require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar');
// Get Google Calendar service name (predefined service name for calendar) $service = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
// Authenticate with Google Calendar $client = Zend_Gdata_ClientLogin::getHttpClient(GCAL_USER, GCAL_PASS, $service);
// Iterate through the direct messages we got from Twitter foreach ($direct_messages as $dm) { $message = (string)$dm->text;
// If you echo the message body here, you'll notice that the // extraneous text is removed from its beginning for us //echo $message;continue;
// You could do some processing here if you wanted; // e.g. allow only direct messages from certain users detect // a particular flag or message format for processing
// Create a new Google Calendar object $calendar = new Zend_Gdata_Calendar($client);
// Create a new event $event = $calendar->newEventEntry();
// Make it a Quick Add event $event->quickAdd = $calendar->newQuickAdd('true');
// Add our message as the event content $event->content = $calendar->newContent($message);
// Send the new event to be added to Google Calendar if (defined('GCAL_ID')) { $newEvent = $calendar->insertEvent($event, 'http://www.google.com/calendar/feeds/'.GCAL_ID.'/private/full'); } else { $newEvent = $calendar->insertEvent($event); }
// Play nice and ensure there's a wait between calls sleep(1); }
?>

Running that should post your direct message on to Google Calendar and, provided Google Calendar understood your message, a new event will be added.

Once we’ve used the message, we don’t want it to get picked up and processed when the script is next run. We can delete a message using the Twitter API, but we’ll need to add something to the Twitter library we’ve been using. The following function can be added to the Twitter class to allow us to delete a direct message using its ID. I put this in after the other direct message functions.

<?php	// ...

/** * Deletes a direct message */ function deleteDirectMessage($id) { if (!is_numeric($id)) { return false; } $request = 'http://twitter.com/direct_messages/destroy/'.$id.'.xml'; return $this->process($request); }
// ... ?>

Now we just call this new method of the Twitter class to delete the message once we’re done with it.

<?php	// ...

// Send the new event to be added to Google Calendar if (defined('GCAL_ID')) { $newEvent = $calendar->insertEvent($event, 'http://www.google.com/calendar/feeds/'.GCAL_ID.'/private/full'); } else { $newEvent = $calendar->insertEvent($event); }
// Delete the direct message $twitter->deleteDirectMessage((integer)$dm->id);

// Play nice and ensure there's a wait between calls sleep(1); }
?>

And we’re done! All that’s left to do it set up a cron job on a server to run our script periodically.

I advise not running the script too often – bombarding the Twitter servers. I set mine to run every 20 minutes, but from what I’ve read you should be able to run it every minute without any trouble. If you’re going to be doing anything particularly intensive, you should probably contact Twitter first.

Here’s the code in full. Enjoy!

<?php

// Define credentials for the Google Calendar account define('GCAL_USER', 'google_user@example.com'); define('GCAL_PASS', 'google_password');
// If you need to target a specific calendar, uncomment and enter // its calendar ID here. The calendar ID is available on the // calendar settings page, next to the Calendar Address buttons. //define('GCAL_ID', '[hash]@group.calendar.google.com');
// The Twitter library // Make sure you've set your Twitter username and password and // added the function deleteDirectMessage() require_once 'twitter.class.php';
// Include Zend GData client library require_once 'Zend/Loader.php'; Zend_Loader::loadClass('Zend_Gdata'); Zend_Loader::loadClass('Zend_Gdata_ClientLogin'); Zend_Loader::loadClass('Zend_Gdata_Calendar');
// Create a new Twitter object and query for direct messages $twitter = new Twitter(); $direct_messages = $twitter->directMessages();
// Get Google Calendar service name (predefined service name for calendar) $service = Zend_Gdata_Calendar::AUTH_SERVICE_NAME;
// Authenticate with Google Calendar $client = Zend_Gdata_ClientLogin::getHttpClient(GCAL_USER, GCAL_PASS, $service);
// Iterate through the direct messages we got from Twitter foreach ($direct_messages as $dm) { $message = (string)$dm->text;
// If you echo the message body here, you'll notice that the // extraneous text is removed from its beginning for us //echo $message;continue;
// You could do some processing here if you wanted; // e.g. allow only direct messages from certain users detect // a particular flag or message format for processing
// Create a new Google Calendar object $calendar = new Zend_Gdata_Calendar($client);
// Create a new event $event = $calendar->newEventEntry();
// Make it a Quick Add event $event->quickAdd = $calendar->newQuickAdd('true');
// Add our message as the event content $event->content = $calendar->newContent($message);
// Send the new event to be added to Google Calendar if (defined('GCAL_ID')) { $newEvent = $calendar->insertEvent($event, 'http://www.google.com/calendar/feeds/'.GCAL_ID.'/private/full'); } else { $newEvent = $calendar->insertEvent($event); }
// Delete the direct message $twitter->deleteDirectMessage((integer)$dm->id);
// Play nice and ensure there's a wait between calls sleep(1); }
?>

Related

Screen readers and abbreviations

I was pointed at some advice this week that was telling developers not to worry too much about using the abbr or acronym HTML elements. The reason given for the advice was lack of support for these elements from screen readers. There are certainly a few things to consider when using these abbreviation elements, but lack of support in screen readers is not a valid reason to forget about them. Besides the fact that their use is of benefit to more than just screen reader users, screen readers do actually support abbr and acronym.

The abbreviation elements have confused developers for some years now. I thought I’d try to clear things up a little with some information about how modern screen readers support and handle abbreviations.

What are the abbr and acronym elements?

The W3C quite simply tells us:

The ABBR and ACRONYM elements allow authors to clearly indicate
occurrences of abbreviations and acronyms. […] The title attribute of these elements may be used to provide the full or expanded form of the expression.

Differing opinions on the definition of an acronym sparks debate about how to use the elements correctly. Juicy Studio has an in-depth look at the confusion surrounding abbreviations that’s well worth a read if you want some background on that. Essentially, it’s debated whether an acronym is always spoken as a word (e.g. NATO) or may sometimes be spoken one letter at a time (e.g. FBI). If you ask the W3C, both examples I’ve just given are acronyms. Grammarians will often say that an acronym should be pronounceable as a word, demoting FBI from acronym status. This leads to confusion when trying to decide when to use acronym in markup. However, most people will agree that an acronym is a form of abbreviation.

A common approach to marking up abbreviations

One approach, which is often quoted as the best approach, boils down to practicalities of pronunciation and the technology involved rather that who wins the argument over grammatical considerations.

In theory, the acronym element could be used to distinguish between those abbreviations that are usually spoken as a word and those that should be spelled one letter at a time. In practice, this is not the case. I used this approach too until I began researching how abbreviations work in JAWS several months ago and changed my mind.

Abbreviations are supported in JAWS and Window-Eyes, which I’ll come back to in a moment. What JAWS does not appear to do is use the difference between abbr and acronym to inform how it speaks. I’m not sure about Window-Eyes having not tested it – yet!

Instead of differentiating between the elements, screen readers analyse words to determine whether or not they can be spoken as a word (using lexical analysis) – they don’t know whether or not it should be, so they guess. Screen readers may use the different elements to inform their analysis of words, but I haven’t found research to support that.

Aural style sheets could be used by a developer to specify the required pronunciation, but near-zero support for aural style sheets puts a damper on that one.

So, in fact, it actually doesn’t matter one way or another which element you use. Since, an acronym is a form of abbreviation and screen readers don’t seem to pay any attention to which element you use in your markup, I don’t use acronym at all any more.

It’s probably a topic for another time, but I currently use something along the lines of <abbr class="acronym"> where a plain abbr doesn’t quite scratch the itch. It allows me to classify an abbreviation, should I wish to do so, and provides a useful hook for applying aural CSS, should it ever enjoy a good level of support. XHTML 2.0 even drops acronym in favour of using abbr. Even though XHTML 2.0 is still a draft, hopefully it means I’m along the right track.

Accessibility myth: screen readers don’t support abbr and acronym

Interpretation of “support” for abbreviations depends on what you’re expecting the abbr and acronym elements to do for you.

If you’re expecting all words marked up as acronyms to be pronounced as a word and all others letter-by-letter, you’re out of luck. Screen readers will do their best to figure out what should and shouldn’t be read as a word. If the screen reader gets it wrong, a user can tell it to stop, go back over an offending word pronouncing it one letter at a time and then continue with the text. For common problem words, a user can set an entry in their screen reader’s custom dictionary that specifies a personal preference for how a particular abbreviation should be spoken.

But there’s more to it than pronunciation alone. The second half of our W3C quote gives us another use for the abbreviation elements.

The title attribute of these elements may be used to provide the full or expanded form of the expression.

A bit of Googling suggests that JAWS and Window-Eyes, two of the most common screen readers, have been able to speak expanded abbreviations marked up with abbr and acronym since 2003, when JAWS was in version 4.51 and Window-Eyes was in version 4.5. Older versions of these screen readers did not have this support for abbreviations.

In JAWS, the user can configure whether or not the software speaks the expansion provided by the title attribute in place of the abbreviated form. This can be found under the Verbosity settings HTML Options in the Configuration Manager (seems to have been removed from the Verbosity settings in JAWS 9.0). I know that Window-Eyes has similar options, but I’ve not tried them myself.

My own testing, primarily with JAWS, has found the options to work in recent versions. However, I have identified a couple of quirks with abbreviations in JAWS 7.10 and JAWS 8.0 that are worth noting. The settings for abbreviations haven’t worked properly in certain versions, but support has been around for quite a while and seems fairly stable now.

Another thing worth mentioning: don’t get tripped up by thinking that Internet Explorer’s lack of support for the abbr element in version 6 and below means that screen readers can’t use them. If you’re thinking, “Surely screen readers sit on top of a browser and if the browser doesn’t support something, the screen reader cannot either?” you’re about to learn something new. As the saying goes: “There’s more than one way to skin a cat!” Screen readers don’t rely on the browser’s document model for everything – they can use other ways to access the information they need.

Please be careful

So, there you go. Make of that what you will. If you want to know more about using the abbr or acronym elements in your markup, stay tuned!

I’ll close with a plea – please be careful when giving advice or publishing research!

While many people like short and sweet advice, I don’t like seeing information touted as “fact” without evidence – I see it too often in the papers (I say with a wry smile). If you’re going to provide advice to people, be able to back it up with links so that people can find out more. If you don’t, confusion breeds and gives birth to myth.