Accessibility and You
We all have items we focus on from day to day when building sites. It is always a challenge to get an idea of what really matters to a client and even what they know about their consumers. Most developers/designers spend hours trying to convince clients that they need to focus on ensuring everyone that comes to their site can access everything in it, but most clients want flashy, dynamic and even some times, over-thought items on their site just because it looks cool or someone has said the magic phrase, “It’s Web 2.0!”. For most, it’s working with people that don’t realize they could be missing out on multiple audiences when they ask for something. For others, it’s that they want what they like and everyone else must like it too. And even for more, it’s that they just don’t know any better.
At the company I work for today, there are many such individuals that see the web from a certain perspective and don’t seem to understand what it means to use certain techniques or technologies within the web world. Even trying to explain sometimes gets the glassy eyed stare of an individual that either doesn’t comprehend or just doesn’t appear to care to listen to the other side of what it is they’re going to be doing to users. Sometimes it’s as simple as “what if the user can’t see this?” which always seems to get a response of “what do you mean they can’t see this? They click the link and it appears!”
Understanding your audience is key
Knowing that the battle of a usable website for all of your visitors is a balancing act on a ninjato. Very short and very tretcherous. Short, only because without the right knowledge, you’ll never convince the individuals that you need to. Tretcherous because once you start down the path of trying to explain that there are those people that cannot use advanced items like modal windows or other DHTML techniques that deliver that interactive experience that a general web audience expects with today’s sites. With today’s analytic software options on the market today (free and paid), you can tell a pretty accurate story on quite a few items from where your traffic comes from geographically to when those users visit to even how many of them have JavaScript disable and which version of Adobe’s Flash Player they have. But, currently, no screen reader software creator on the market today has made the move to let website owners know that a computer with their software on it has visited their site. Without this knowledge it becomes increasingly difficult to educate your clients on the needs to balance their site with the right kinds of interactive features and good old fashion page refreshes to get a user through their experience.
Then how do I get this information?
Well, until recently, there was little anyone could do to find out if the user visiting your site really needed an alternative experience to ensure they were able to get the full use of your site. When I started looking into what could be done to garner this information, I was only able to find one product which did this; the infamous FlashAid application. While this application did work, it was designed only for Internet Explorer and took approximately 2 seconds to return the information it was built to seek out. Not fast enough by today’s web standards, especially since most dynamic features are set as soon as the Document Object Model is ready for manipulation.
What were we to do? Well, improve upon the design, of course! The thought behind the FlashAid tool is still a great one today. More than 99% of users on the web today have the Adobe Flash Player, and thanks to sites like YouTube and Google Video users are quick to upgrade their player to the most recent one very quickly. This makes a Flash-based tool the perfect host for gathering information on accessibility tools.
OK, so, what’s the code already?!?
Well, the code is in two parts: a Flash component and JavaScript.
While probably all of this could be written directly in Flash, I chose to use a hybrid approach to allow users to more easily change how the component behaves. Yes, it’s two files to download, but with a little finese and proper planning, it won’t make much of a difference.
Enter the JavaScript
Inside the JavaScript package are two functions, first the Assistive Technology check code. This code is the meat behind the technology checker and does two main things. First, it adds, using SWFObject, the Flash application (shown later), stores a cookie on the user’s machine for use in other JavaScript applications and finally gives the ability to post the results of that Flash file to analytic software.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /*globals SWFObject deconcept dcsMultiTrack escape */ /* Assistive Technology Checker * Version: 1.0 * 2009/04/29 * Author: George Walters II (walterg2_NOSPAM_@gmail.com) * Remove _NOSPAM_ to send me any comments/suggestions on improvements */ /* To launch: as soon as possible, simply add in assistiveTech.init(debug); where debug is either true or false. * This will allow for the flash to run in as little time as possible and mitigate as much of the delay in this * code running as possible. * Requires: SWFObject */ var assistiveTech = { assistiveTechLocale: "./flash/assistiveTech.swf", replacementDiv: "assistiveTech", requireVersion: "9", debug: false, debugFlag: "", techAssist: false, cookieName: "assistiveTech", init: function(debugFlag){ assistiveTech.debug = (debugFlag) ? assistiveTech.debugCheck(debugFlag) : assistiveTech.debug; var cookieCheck = assistiveTech.cookieExists(); if (!cookieCheck) { var so = new SWFObject(assistiveTech.assistiveTechLocale, "assistiveTechSwf", 1, 1, assistiveTech.requireVersion); so.addParam("quality", "low"); so.addParam("allowScriptAccess", "all"); // Pass the success handler's name to the Flash application. Flash // will call this handler using ExternalInterface. so.addVariable("callback", "assistiveTech.flashSuccess"); // Try and write out the SWFObject var success = so.write(assistiveTech.replacementDiv); // If there's a problem in writing the tag, try to give the user more // information (they've probably got the wrong version of the player) if ( success ) { // Successfully embedded. Now give the Flash movie focus // or else it will be ignored by the screen reader and not // get an update on isActive. document.getElementById("assistiveTechSwf").focus(); if ( assistiveTech.debug ) { // Need to figure out what we want to do if debug is turned on and the flash piece succeeds } } else { // Embedding the Flash application failed. // Display the version message in the assistiveTech div only // if we've been asked to do so. if ( assistiveTech.debug ) { var version = deconcept.SWFObjectUtil.getPlayerVersion(); if (document.getElementById && (version.major > 0)) { document.getElementById(assistiveTech.replacementDiv).innerHTML = "<p>This sample requires Flash Player version " + assistiveTech.requireVersion + ". You have Flash player " + version.major + "." + version.minor + "." + version.rev + " installed. <a href='http://www.macromedia.com/shockwave/download/index.cgi?P1_Prod_Version=ShockwaveFlash'>" + " Download the latest Flash Player</a> to run the sample.</p>"; } } // Call the failure callback this [ assistiveTech.flashFailed ](); } } }, setCookie: function() { var nextMonth = new Date(); nextMonth.setSeconds(0); nextMonth.setMinutes(0); nextMonth.setHours(0); nextMonth.setMonth(nextMonth.getMonth() + 1); nextMonth.setDate(1); var expires_date = new Date( nextMonth ); document.cookie = assistiveTech.cookieName + "=" + escape( assistiveTech.techAssist ) + "; expires=" + expires_date.toGMTString() + "; path=/"; }, getCookie: function() { var cookies = document.cookie.split(';'); for ( var i = 0; i < cookies.length; i++ ) { var cookieData = cookies[i].split('='); var cookieName = cookieData[0].replace(/^\s+|\s+$/g, ''); if (cookieName === assistiveTech.cookieName) { return cookieData; } } return false; }, returnCookieValue: function() { var cookieInformation = assistiveTech.getCookie(); return cookieInformation[1]; }, cookieExists: function(){ var cookieInformation = assistiveTech.getCookie(); if (assistiveTech.debug) { return false; } else if (cookieInformation && cookieInformation.length > 1) { return true; } return null; }, debugCheck: function(debugFlag){ assistiveTech.debugFlag = debugFlag; if (debugFlag === "on" || debugFlag === "off") { return true; } else { return false; } }, flashSuccess: function(accessibilityFlag) { assistiveTech.techAssist = (assistiveTech.debug && assistiveTech.debugFlag === "on") ? true : ((assistiveTech.debug && assistiveTech.debugFlag ==="off") ? false : accessibilityFlag); assistiveTech.setCookie(); var ATEnabled = assistiveTech.techAssist ? 'Yes' : 'No'; // Add in your favorite Analytics software call here }, flashFailed: function() { // Do something if the user doesn't have Flash 9 or higher installed if (assistiveTech.debug === "on") { alert('something went wrong'); } } }; |
The other piece is a Query String parser. Chances are you already have something to do this or have built one yourself very similar to this one. But, to make the code complete and allow for a debug mode, I’ve included the one below as part of the script. Does the normal items you’d expect from a Query String parser, evaluates the query string and allows you to get the value of a parameter passed on the query string. Very simple.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | var ParseQueryString = { params:{}, init: function(qs) { if (!qs) { qs = location.search.substring(1, location.search.length); } if (qs.length === 0) { return; } // Turn <plus> back to <space> // See: http://www.w3.org/TR/REC-html40/interact/forms.html#h-17.13.4.1 qs = qs.replace(/\+/g, ' '); // parse out name/value pairs separated via & var args = qs.split('&'); // split out each name=value pair for (var i = 0; i < args.length; i++) { var pair = args[i].split('='); var name = decodeURIComponent(pair[0]); var value = (pair.length === 2) ? decodeURIComponent(pair[1]) : name; this.params[name] = value; } }, get: function(key, defaultText) { var value = ParseQueryString.params[key]; return (value !== null) ? value : defaultText; }, contains: function(key) { var value = ParseQueryString.params[key]; return (value !== null); } }; |
Don’t forget our friend Flash!
And now for the Flash application. With the help of Michael Krotscheck (@krotscheck on Twitter), an ActionScript file was born. With only 19 lines of ActionScript, we have an application that checks for Accessibility and returns the information to a JavaScript callback function determined from a variable you pass into the Flash application. Since this is an ActionScript package, you can put this directly into a SWF file by use of the Flex SDK.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | package { import flash.accessibility.Accessibility; import flash.display.LoaderInfo; import flash.display.Sprite; import flash.external.ExternalInterface; public class assistiveTech extends Sprite { public function assistiveTech() { var info : LoaderInfo = this.root.loaderInfo; var method : String = info.parameters.callback; if ( method && ExternalInterface.available ) { ExternalInterface.call( method, Accessibility.active ); } } } } |
Next Steps
Of course, I’m not finished just yet. With the code I have above, you can see that it’s JavaScript library independent. That is on purpose and I have plans to lock this down a bit more by encasing it into private and public functions, adding options and the like to make this a complete packaged piece of software. More on that to come though. For now, it’s time for bed.