Jun 25

Quick fix for poor screen reader support of title attribute

As someone who remembers when ARIA was just an idea, I’ve spent a fair amount of time adding title="something descriptive" to many of the links, images, and possibly other elements on web pages I’ve authored and edited. But as developers began relying on authentic users of assistive technology to test their work, and those with differing abilities brought their knowledge and experience into the discussion and onto the Web, we learned we were wrong. Some of us knew that early on, or part of it anyway, and in the paralysis that may sometimes grip those unsure what to do, I stopped using title for anchor tags (i.e. links; <a>) altogether.

ARIA’s all grown up now

ARIA grew up and became the specification. The bulk of screen readers put their support there, and aria-label="making this meaningful" has supplanted the title attribute, freeing it to be what it is.

There’s enough explanation about this on the Internet already, so I’m going to keep this one very short. There are a lot of links in my pages that are in various states that reflected my limited understanding at the time, and maybe in yours too—live and learn! As I’m mostly working with WordPress sites these days, and WordPress has built-in jQuery, I’m sharing this snippet of code that takes text from existing “titles” and places it in an aria-label, or takes the linked text displayed to the visitor and makes it a bit more friendly and conversational than the typical screen reader default: “Link text. Link”

Most of my WordPress sites have a js folder in the theme I’ve chosen’s main folder at …wp-content/themes/theme-name/js, and in it a file called functions.js. Below is a standalone jQuery closure that should work in any site that has jQuery installed. In practice I took the indented part, between the first and last lines, and pasted it below the existing scripts in the existing closure. In Drupal you might put it in …/sites/all/themes/theme-name/js (and load it with a preprocess.inc). If you’re not using a CMS you probably need to wrap it in <script></script> tags, and make sure you place it below the prerequisite jQuery include.

For the blog you’re reading now, which didn’t already have a functions.js file in place, rather than stuff it into another I placed the following line below wp_head(); in header.php, found in the WordPress root folder.

wp_enqueue_script( 'my-custom-functions', get_template_directory_uri() .'/js/functions.js' );

This quick fix should probably be considered a temporary one, but it sure makes my pages sound better with ChromeVox, so I’ll carry on and wait for experts like Geoff Collis and others to tell me what to do next.

(function($) {
  // If there's a title attribute, make sure it's copied to aria-label (for screenreaders)
  // If there's no title, elaborate on the linked text.
      var
        links = $('a')
      ;
      links.each(function(i){
      	if (this.title){
      		$(this).attr({'aria-label':this.title});
      	} else {
      		this.title="Go to " + $(this).text() + ".";
      		$(this).attr({'aria-label':this.title});
      	}
      });


})(jQuery);


Feb 26

The baby in the bath water?

I got my start as a “webmaster” in the Crocker-ian sense. I wrote jSyncWithMedia as a project for my master’s degree in education. With it you can synchronize a slide and caption show to your speech or video using the native <audio> and <video> tags. It contains some naïve and outright bad code, but it’s based on a thesis, and I got it to work. I noted that storytelling is in many ways central to teaching, and narration is at the center of storytelling. Teachers and storytellers highlight certain phrases, create mental images and metaphors, to emphasize the specifics of what they’re telling. I knew html css sql and was trying out JavaScript using only resources available on the Internet. I tried to synchronize events to an audio and or visual timeline. If nothing else, I proved that a tenacious person with strong but basic HTML/CSS/JavaScript background and access to a search engine could, in 2010, build a working jQuery plugin.

It’s nothing like Mozilla’s Popcorn.js in scalability, but If there’s one thing about it that’s still interesting it might be the CSS animation based on WAI-ARIA attributes aria-expanded aria-hidden and their values, which I manipulated with jQuery.

Selectors $(‘[aria-expanded=”false”]’) $(‘[aria-hidden=”true”]’)

/* CSS file
 * A fade in and out that can be controlled
 * by changing aria- attribute values
 * programmatically.
 */

[aria-expanded="false"] {
    display:none; 
} 

[aria-hidden="true"] {
    visibility:invisible; 
} 

[aria-expanded="false"] {
	opacity: 0;
	-webkit-transition: opacity 0.5s linear;
	-moz-transition: opacity 0.5s linear;
	-o-transition: opacity 0.5s linear;
	-ms-transition: opacity 0.5s linear;
	transition: opacity 0.5s linear;
}

[aria-expanded="true"] {
	opacity: 1;
	-webkit-transition: opacity 0.5s linear;
	-moz-transition: opacity 0.5s linear;
	-o-transition: opacity 0.5s linear;
	-ms-transition: opacity 0.5s linear;
	transition: opacity 0.5s linear;
}

In theory this can work with other CSS animations. The big challenge for practicality is the timeline interface. I created one that works, using Audacity .aup files, which are XML documents, and the timings extracted from its label track.

<!-- SNIPPET FROM .aup FILE CONTAINING LABELS -->
    <labeltrack name="Label Track" numlabels="11" height="253" minimized="0">
        <label t="1.69726544" t1="11.83946136" title="li:woodshed"/>
        <label t="11.92225480" t1="27.32183391" title="li:busting"/>
        <label t="20.79346939" t1="62.46764754" title="li:turntable"/>
        <label t="34.31787926" t1="43.54934739" title="li:changes the pitch"/>
        <label t="50.00723540" t1="62.46764754" title="a:audacity.soundforge.net"/>
        <label t="53.85713018" t1="62.50904425" title="img:audacity_logo.png"/>
        <label t="62.46764754" t1="62.55044097" title="off:ALL"/>
        <label t="76.70811855" t1="91.03138299" title="img:copy-paste.png"/>
        <label t="91.11417643" t1="109.90828642" title="li:copy menu item"/>
        <label t="110.15666673" t1="114.79309915" title="li:easily practice"/>
        <label t="114.79309915" t1="123.81758369"
               title="li:Choose File-&gt;Save Project As..."/>
    </labeltrack>

§

https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Using_HTML5_audio_and_video

http://www.w3.org/2010/05/video/mediaevents.html

Jan 28

WCAG 2.0 in a nutshell, and a problem that illustrates its use

The “Web Content Accessibility Guidelines” (WCAG) 2.0 are the accessibility standard most new websites in Ontario and many other places around the world have to meet nowadays. Here’s a front end accessibility lesson that can show us a few things about applying WCAG 2.0, at a couple different levels. I’ll demonstrate a JavaScript solution to a specific problem, I’ll sort of ‘reverse engineer’ from that problem to locate where it sits within the framework of the four principles—that content must be Perceivable, Operable, Understandable, and Robust—and I’ll show how I use the WCAG 2.0 site to understand any accessibility issue—whom it affects, how, how to fix it, and how to know that I’ve done so successfully. As a bonus, I’ll pop over to the jQuery API site and look at the selector reference. I think the WordPress “hack” I show for adding this to your blog is out of date—the “Admired” theme I’m using now has a way better built-in method—so you’ll need to adapt it. No clue at all what I’m talking about? I didn’t learn this in school either… sometimes you just have to dig in and figure it out.

Understanding WCAG 2.0

Understanding… WCAG 2.0 means understanding that the work began as a collaborative effort to define the 4 Principles of an accessible internet site, which after a decade of ongoing consultation with an ever-growing international community are now guidelines—not exactly the same as “rules”—and a list of criteria—things front-end developers must, should, and can do—to succeed at removing the barriers some groups will otherwise face when accessing and using the internet. [It…] is not prescriptive, but offers options…

Understanding how to use the huge body of work we call WCAG 2.0 means understanding that the work began as a collaborative effort to define the 4 Principles of an accessible internet site, which after a decade of ongoing consultation with an ever-growing international community are now guidelines—not exactly the same as “rules”—and a list of criteria—things front-end developers must, should, and can do—to succeed at removing the barriers some groups will otherwise face when accessing and using the internet. Because the list is not prescriptive, but offers options, it seems of the utmost importance to first know your audience, and next, to understand as the Web Consortium’s Accessibility Group sets out in WCAG 2.0, the best way your organization can guarantee your audience access to your content.

David Berman said in his workshop, and I think it makes perfect sense, that the differences between accessibility and usability are, for all intents and purposes, purely semantic. Providing access for people with varying abilities, simply makes things more usable for everyone.

The specific problems I’ll address are, ‘opening too many new windows’ and ‘changing things without telling me.’ In order to keep site visitors from leaving a site, Web developers often open links in new windows, usually by using the target attribute, and by assigning it a value of "_blank":


<a href="http://SOME_LINK" target="_blank">Linked text</a>

WCAG 2.0 in a nutshell

As I said earlier, there are 4 Principles. Websites must be 1) perceivable, 2) operable, 3) understandable, and 4) robust. If you like acronyms: POUR some accessibility sugar on me (use <abbr title="Spelled Out">SO</abbr> to create tool tips screen readers can use)! Each principal has “guidelines, “…which are further categorized into levels. Level A must be done, or some group will not be able to access the content. Level AA should be done, or some group will have difficulty accessing the content. Level AAA can be done to improve usability or enhance accessibility further. Too many windows causes problems in understanding, which is principle #3. This can be especially challenging for those with disabilities related to vision or cognition.

The Understanding WCAG 2.0 site provides information by which to understand each guideline, and provides “success criteria” so you know when you’ve achieved each level, and examples of techniques you can use to get there. “Success criteria” are written as statements that are recognizably/measurably false until one meets the guidelines. The problems that prevent the statement from being true are your challenges to overcome.

Know your organization, your audience, and your content. Use valid HTML wherever you write code. If most of your site visitors are knowledgeable about technology it may not be necessary to open new windows, as they will use their familiar browsing setup to choose when and how to open them, and if your code is valid it will work as they expect. There’s no WCAG 2.0 guideline that says not to open new windows, but we must think more carefully about how doing so may create barriers to ease of access and use.

Guideline 3.2 says: make webpages appear and operate in predictable ways. Opening pop-up windows could be problematic for screen readers. If they don’t know the window is opening they can get lost. This guideline also covers many situations, such as focus or context changes, and page reloads—anything a user can potentially do that changes the content. WCAG 2.0 by no means prohibit pop-up windows, but we must prevent them from becoming barriers or annoyances. We should minimize the number of new windows, stop using target=”_blank”, and let users request a new window or otherwise inform them it’s about to open. If we look further in the Table of Contents we find a discussion about pop-ups under 3.2.5, with suggestions…

Situation C: If the Web page uses pop-up windows:

Including pop-up windows using one of the following techniques:

H83: Using the target attribute to open a new window on user request and indicating this in link text (HTML)

SCR24: Using progressive enhancement to open new windows on user request (Scripting)

3.2.5 also has an “Advisory” about additional techniques.

Additional Techniques (Advisory) for 3.2.5

Although not required for conformance, the following additional techniques should be considered in order to make content more accessible. Not all techniques can be used or would be effective in all situations.

Opening new windows by providing normal hyperlinks without the target attribute (future link), because many user agents allow users to open links in another window or tab.

G200: Opening new windows and tabs from a link only when necessary

Understanding the problem, we now make a plan

Objective

I don’t want folk leaving my pages abruptly or permanently, and I don’t think all my visitors know everything about their browser’s and other equipment’s context-sensitive help menus, access key options, etc., so I’ve elected to automatically open some content in new windows. I’ve decided I can sensibly limit the number of windows that open from any of my blog pages to a maximum of 2 by applying a simple self-enforced rule. I’ll still use the target attribute, but instead to create one “named window” for links to other areas of my site (rcfWin), and one “named window” for links to external sites (extWin). I’ll open all external links in their own window, which means I can easily design something that will apply retroactively to all such links. Go to the API selectors page and scroll down to Attribute Starts With Selector [name^="value"] to get the syntax. I want to select all the links (a) with an href attribute whose value begins with http:// (or https://). We can get away with [href^="http"].

I have to weigh all the advice to find the best way to handle my internal links. If you’ve linked text in the middle of one article to another article there’s a distinct chance the user will click it and start reading. If you don’t want that, the most sensible choice is usually to lose the link—link only at the end of the information and only to the next logical jump in a sequence. But if you feel you must have the option, to keep it as an available option you can create a CSS class name and tell jQuery to look for that. You’ll still have to add it manually to any links, past present or future, you want to behave that way. Or you could do it the other way around and use your class to prevent opening in another window or tab.


<a class="open-in-rcfWin" href="/MY_INTERNAL_LINK" target="rcfWin">Linked text</a>
<a href="http://SOME_EXTERNAL_LINK" target="extWin">Linked text</a>

* Aside: I’ve already manually removed the http://www.rcfouchaux.ca from internal links because of its effect on WordPress “pingback links,” which I’ve got going on here. I’ll have to explain those later, but it comes in handy that I’ve done this, as you’ll soon see.

Problem

This blog just turned 3, and I’ve got a lot of blog pages. I have to find some way to automate at least some of this. I might have used target="_blank" sometimes, and not others. I might have already used target="extWin".

jQuery to the rescue!

jQuery library—write less, do more

jQuery is a “library” of code that makes standard JavaScript easier to use by preparing commonly used patterns and tasks and giving them logical, easier-to-remember names. jQuery selectors let’s us find and select specific elements and groups of elements on a web page and then manipulate them in pretty astonishing ways. If your site is WordPress like this one you’ll have to find out if jQuery is already included in your theme, or if it can be added easily (or if you have admin access to your web root and know how you can add it to any web site). Due to historical reasons I combine methods. I let the Admired Theme supply the jQuery and I keep extras in my own file. To make it use the scripts in my file I need admin-level server access to edit my theme’s header.php, which is found in wp-content/themes/YOUR_THEME/. Find wp_head(); alone on its own line and add a line of code after it wp_enqueue_script( ALIAS, PATHTOFILENAME );. The path to the file has to be complete, should be a ‘relative’ path, and depends on your server. I always make the alias the first part of the filename.

<?php
	/* JavaScript for threaded comments.
	 ----------------------------------*/
	if ( is_singular() && get_option( 'thread_comments' ) )
		wp_enqueue_script( 'comment-reply' );

	/* wp_head() before closing </head> tag.
	---------------------------------------*/
	wp_head();
	
	/* Include own script(s) AFTER wp_head() tag.
	---------------------------------------*/
wp_enqueue_script( 'MY_CUSTOM_SCRIPT', '../[actual_path_to]/MY_CUSTOM_SCRIPT.js' );
 
/* etc... */

Thereafter you make changes to that file and then replace it on the server. Keep in mind that header.php will be over-written if and when you update your theme, so keep backups of any code you add.

Adding the behaviors we want to the elements we want

The jQuery magic starts when you wrap the selector in $('SELECTOR');. I’ll be creating a set of extWinLinks $('[href^="http"]'); and rcfWinLinks $('.open-in-rcfWin');

There are nearly always more than one way to solve a problem with jQuery. My general approach will be to create a function as the page loads, and call it when the page is ready. I’ll supply more details in the code comments!

To recap: we’ll take all http links and assign target=”extWin” regardless if they’ve got a target attribute set or what it might be set to. We’ll also create a class name to apply to internal links we think should open their own window, but never the same window an external link may already be open in. Bonus: We’ll add the sentence ” … Opens in a new tab or window.” to every link that does that. Because this last bit of code will be repeated in both the previous functions we’ll write it as a standalone function in its own right, and call it from the other two when needed (those jQuery.each(); loops that repeat in each function are good candidates for the same treatment, but I left it so you can better compare what’s happening in each case).


        /*
         * Window openers
         * Require jQuery
         *
         */
          
          // Declare variables in a single statement at the top of the script.
          // Select external links and store in a variable named extWinLinks
          // Select internal links and store in a variable named rcfWinLinks
          // Create two functions to set the targets on the two sets of elements. 
          var
             extWinLinks = $('a[href^="http"]').not( 'a[href~=".rcfouchaux.ca/"]' ), // use a comma if you have more
             rcfWinLinks = $('.open-in-rcfWin'),
             do_extWinLinks = function() {
                 // Set the target attribute to 'extWin'
                 extWinLinks.attr({ target:'extWin' });
                 
                 // Go through each item and get its title if it has one, or set it to an empty string.
                 extWinLinks.each( function( el,i ) {
                     var my = $(this), myTitle = my.attr('title') || '' ;
                         my.attr({ title : appendNotice( myTitle ) });
                 });
             },
             do_rcfWinLinks = function() {          
                 // Set the target attribute to 'extWin'
                 rcfWinLinks.attr({ target:'rcfWin' });
                 
                 // Go through each item and get its title if it has one, or set it to an empty string.
                 rcfWinLinks.each( function( el,i ) {
                     var my = $(this), myTitle = my.attr('title') || '' ;
                         my.attr({ title : appendNotice( myTitle ) });
                 });
             },
             appendNotice = function( title ) {
                 // Store the notice as a variable
                 var
                     notice = ' … Opens in a new tab or window.'
                 ;
                 
                 // return the appended notice (but don't add a leading space)
                 // This syntax, if what's left of ? is true returns left of :, otherwise right of :
                 return ( title.length > 0 ) ? title + ' ' + notice : notice ;
             }
          ; // I make the final semicolon obvious so I can find it later
          
          // Call the functions. 
          do_extWinLinks(); 
          do_rcfWinLinks();  
  

To summarize

Know your organization, your audience, and your content. Use valid HTML wherever you write code. If most of your site visitors are knowledgeable about technology it may not be necessary to open new windows, as they will use their familiar browsing setup to choose when and how to open them, and if your code is valid it will work as they expect. There’s no WCAG 2.0 guideline that says not to open new windows, but we must think more carefully about how doing so may create barriers to ease of access and use. We might consider limiting their number—by using a named window, not the well-known keyword _blank—and warn our users it will open in a way that screen readers will discover and convey to any users who may be using one. This discussion follows a line of thinking you can adapt to meeting other WCAG 2.0 success criteria. This JavaScript shows only one way to reduce the number of windows your site opens, and to inform users in advance in a way their technology can understand.

I’ve coded all the external links in this post differently, but they should all open in the same tab or window. Hover your mouse over any links on this page to see if the ” … Opens in a new tab or window” notice worked. Here’s a class="open-in-rcfWin" internal link and here’s another one. The next one has no class set, so it will replace the content of this page with the home page: ciao for now!

§

Understanding WCAG 2.0 Latest version: www.w3.org/TR/UNDERSTANDING-WCAG20/

How to Meet WCAG 2.0 – Quick Reference: www.w3.org/WAI/WCAG20/quickref/

Nov 24

JavaScript the least of (my) hurdles with MIDI.js

On #musedchat Monday Nov 18 I saw an opportunity to mention an open source Scoring/Engraving tool and was soon encouraged to send more links to open source resources. I’ve been collecting such links in the bookmarks of various browsers for years, and I’ve followed through on my initial interest to various degrees, so in some cases I found myself checking links along with my memory. In one case—MIDI.js—I said a big “Oh yeah! I was gonna try installing that, wasn’t I?”

How easy it is to try out any of the hundreds of thousands of interesting and potentially useful JavaScript offerings one can find on the Internet depends a lot on how much documentation the developer and interested community have provided and good examples of the code in (ahem) “authentic situations.” No matter how good it is, if you want it you’ll soon have to view source and dig in. The code behind MIDI.js looked to me daunting, but on closer inspection it turned out the biggest hurdle was recognizing the playlist of MIDI files is an array of Base64-encoded strings hard-coded in the file. If you know what that is, and if you can encode your MIDI file as a Base-64 string then copy/paste it over the ones in the original example—and if you set up the folder structure identically to the original (clone the Git repository straight into /xampp/htdocs if you work with a LAMP setup like mine)—it just works! But it looks just like the author’s.

The design is not responsive1 and doesn’t fit my blog or mobile device. The colours are very cool but I don’t need them. The note display has so many pedagogical applications my brain is exploding—but it doesn’t fit on my pages, same goes for the player and buttons. The JavaScript knowledge I need is “just enough” to identify where to safely slice and dice the code and recognize the very few places I might change something. It was more important I knew Base-64 encoding is something I’d likely find a web site to do. The highest-level skill needed here is actually CSS. I need to restyle the player so it fits my pages in the contexts I expect it to be viewed. And I want those notes to play along exactly as they do now, just in a completely different layout.

Here’s what it looks like today. (It’s not worth listening to in anything but Chrome).

An undefined error originally came from this line, but I haven’t found where MIDI.lang is supposed to be defined, and the sentence really doesn’t tell me anything I care to know if I define it as “English.” I made sure the page has a title and I even gave it a language attribute but no luck only by hard-coding it as shown below could I stop it so far. Will dig in [to the MIDI object, its properties and methods] later.

// I added...
// Quick suppression of undefined
MIDI.lang = MIDI.lang || 'en-ca';
/// above...
// this is the language we are running in
var title = document.getElementById("title");
title.innerHTML = "Sound being generated with &quot; + MIDI.lang + ".";
// I also added at line 108
pausePlayStop(true);  // Sorry Gasman. Autoplay is just wrong!
// Just above that I see <code>song[songid++%3]</code>, the '%3' implies something hard-coded that applied to the fact there used to be 3 midi files. To be continued... 

Some site JavaScript tweaks

Aside

HTML, CSS and JavaScript iconsI noticed the previous article, in which I begin to elaborate on what I mean by “thick” learning “situations,” was jumping to the iFrame that contains an audio clip. I knew why right away—the JavaScript plugin that syncs the audio and images is the first jQuery plugin I ever wrote (2011), I was trying to make sure the user could press the space bar after the page loaded, and the audio would play. It works until you load it in an “inline frame,” the < iframe > tag you may have noticed if you’ve ever copy/pasted YouTube “embed” code anywhere. I’d like to rewrite the entire routine “knowing what I know now,” but the jumping was annoying me and likely confusing others, if not turning them away. So I opened the plugin source file I haven’t opened in over a year. Then I remembered another problem, so I “fixed” that too.

The plugin itself is an example of what can happen when someone who had never taken a formal computer science course or even a programming class, but is reasonably good at finding the right documentation and making some sense of it, gets an idea and digs in, for better or worse. I came up with an approach that’s, well, completely different than Mozilla’s Popcorn, for example. The fact it works as well as it does must have at least as much to do with the power of computers and browser javascript engines as with anything I did (intentionally), but I’ll talk about that another day.

When good code goes bad

I think, but haven’t tested it, that I got away with this where the iframe is on another site because scripts don’t normally run if they’re on separate domains. It seems the “focus()” command was “bubbling up the DOM,” which is a bit like SCTV doing a 3D movie skit in your browser window, if that means anything to you. [I didn’t “embed” that in an iframe because it’s more entertaining than this post, you’d watch it and I’d probably lose you. Don’t click it now… oh shoot, that probably won’t work either.] I just meant to post news of the two tweaks and get back to the next instalment on making learning situations “thicker” using mind mapping tools.

Problem 1 was the jumping focus. I searched for the function I used, which was ‘.focus()’ and thought of two ways to approach it. The good programmers at jQuery have created a function named “stopPropagation()” but that’s not as much fun for me because I don’t really know how it works, I know when to type it in non-iframe-embeded code to make specific types of problems stop happening, then I go do something else. That’s always tempting, but the deciding factor was I wanted to turn it on and off. Plugins are supposed to make that easy. It was. I added a new variable ‘jswmAutoFocus‘ set it true by default (so it doesn’t break my old stuff all over the blog) and then wrapped focus() in an IF statement, so it only happens “if jswmAutoFocus is true.” The only trick is you have to look for it as settings.jswmAutoFocus once you set it false, which you now do in the blog post, not the plugin code. (I’ll go back and experiment with stopPropagation() too, it’s probably called for here and these aren’t mutually exclusive solutions.)

 // snipped code that preceded this...
		methods = {
		init : function(options) {
			/*
			 * These styles enable you to change nearly 
			 * anything about the appearance. You can 
			 * also quite easily add your own. 
			 *  *  *  *  *  *  *  *  *  *  *  *  *  *  */
			var /* The first group need to be declared before the 
			     * "defaults" object, because we use them inside it
			     */
				jswmAutoFocus = true, // New line
				jswmTitleId = "jswmTitle",  /* etc... */

The IF statement looks like this. ‘===’ means “deeply equals,” (not just the word ‘true,’ but boolean type true) (setTimeout(code_delayed_by_number_of,milliseconds) is a function that delays whatever code on the left of the comma by the number of milliseconds on the right:

if (settings.jswmAutoFocus === true)
{
        setTimeout("$('.jswm audio:first').focus();",250);
}

Problem 2 was that I used old fashioned User Agent detection, not recommended feature detection using modernizr. The thing is I wanted the old method because I knew how to “spoof” it and pretend to be something I wasn’t, and that let me test some things more quickly. Not recommended for “robust” “scalable” applications, but the only harm for me in using it was that I knew all along it was deprecated and would one day produce an error. I saw the error a few weeks ago when I updated the jQuery on my site to 1.9, I’ve since installed 1.10 so it was time to deal with it.

jQuery released a “migration” plugin that returns this feature and others. I don’t need the others right now, only this one. It’s very likely I could download the “debug” version and extract just the right bits, but this seemed like a challenge within my grasp. I know the original was a property of the global jQuery object and was itself an object. I knew from jQuery dicumentation it had 4 “keys.” I knew it returned only true, false or undefined, and the latter would be written in red text in the error console of Firebug. I know that in JavaScript you can return an object from a function, and you can assign a function to a variable name. So I knew if I could write a function, that produces an object, that returns value of true or false (and is more discreet about advertising its errors) and assign it the same name I gave the old one ($brwsr), it should work and the error should go away. With Firebug open as my scratch pad I wrote the function shown below. The dot in the old code, $brwsr = $.browser, means ‘browser’ is a property of ‘$’ (which is jQuery itself), I gave it my own name so it will come when I call it and not sneak away and pick up other properties somehow when my back is turned, and so it wouldn’t have to travel through the entire jQuery object every time I call. It just happens to begin with a ‘$’ because in 2011 that’s how I reminded myself it’s a nickname for a piece of the jQuery object.

I used this list of User Agent strings (and I don’t care about versions tonight). You make a “regular expression” in JavaScript by using ‘/‘ where you would have used “'” or ‘"‘ and you have to know the secret code. Then you can call a “method” (a function that’s already built into an object straight “out of the box”) of regular expressions, called test(), on the User Agent, which you called ‘ua’ like so: /myRegularExpressionPattern/.test(ua). I used a couple different regExp abilities just for variety and to demo, see the comments beside them.

var getBrowser = function(){
 var ua = navigator.userAgent,
     brwsrObj = {browser:"unknown"}
 if (/firefox|konqueror/i.test(ua)) // the 'i' means case-insensitive
 {
    brwsrObj = {mozilla:true}; // key : value
 }
 else if (/ MSIE /.test(ua)) // the MSIE is upper case between spaces
 {
    brwsrObj = {msie:true};
 }
 else if (/AppleWebKit/.test(ua)) // case sensitive
 {
    brwsrObj = {webkit:true};
 }
 else if (/Opera[\/ ]/.test(ua)) // the backslash \ prevents JS from thinking this / is the end of the regExp
 {
    brwsrObj = {opera:true};
 }
 return brwsrObj;
},
$brwsr = getBrowser(); // The old name now stores the function, which returns a similar object. 

I don’t think for a minute this is as comprehensive as what I’ll find in the migrate.js source when I eventually open it. It is what it is: a quick fix to overcome a deprecated property in an alpha version of a script I wrote 2 years ago. It just needs to hold up until I install the beta… which I just need to write first.

§

Bookmarklets update

Aside

Last June I explored “bookmarklets,” which I’ve found to be an engaging way to explore JavaScript, primarily by leveraging the power of pet peeves—enticing novice learners to change something they don’t like about a web page to be more the way they’d like it to be. As an example I offered a simple script that toggles the visibility of the ads on a Facebook home page.

javascript:(function()
{
  var 
  rCol=document.getElementById('rightCol'),
  isHidden=rCol.style.visibility==='hidden'?true:false;
if(isHidden)
  {
    rCol.style.visibility='visible'
  }
else
  {
    rCol.style.visibility='hidden'
  }
})();
To actually use this as a bookmarklet, first remove all the line breaks and optimize spaces.

Such a snippet can lead to a discussion of the differences between CSS display and visibility or more advanced ideas like native functions and how and when to import external libraries. My bookmarklet soon evolved into the next snippet, which uses an updated id and display:none;

javascript:(function(){var%20adsDiv=document.getElementById('pagelet_side_ads'),isHidden=adsDiv.style.display==='none';if(isHidden){adsDiv.style.display='inline-block';}else{adsDiv.style.display='none';}})();

This worked fine on the Home page, but not elsewhere. Looking into it with Firebug you quickly see the id is different—on a Messages page, for example, it’s pagelet_ego_pane. You might try simply adding a conditional, if it’s not 'pagelet_side_ads' try ‘pagelet_ego_pane’. But this will fail in JavaScript when the document.getElementById method can’t find the id it’s looking for. While there are elegant advanced ways to do this, and we don’t know how many different ids there might be, I followed the learner’s first suggestion and let it turn into an introduction of try/catch blocks and error handling. These work as follows:

try
  { /*something. If it fails...*/ }
catch(errorObjectName)
  {/*report the error and/or try something else*/}

The errorObjectName is usually error and you can retrieve error.message from it, amongst other things.

If at first you don’t succeed, try/catch again

Here’s the expanded code that tries to find an element with id="pagelet_side_ads" and if that returns and error, it catches that and “tries” again with id="pagelet_ego_pane". If that fails it attempts to log that information to the console. Notice I didn’t say “tries” as there’s no additional try/catch block and I want to avoid using the same word with two meanings. What do you think would happen if the browser has no console object containing a log() method? Below the expanded version is a single line version of the same code (except generic console.log() has been refined to console.error() see this link) to use, as expanded code doesn’t work in this context.

javascript:
(function() 
{
  try
    {
      var adsDiv=document.getElementById('pagelet_side_ads'),
          isHidden=adsDiv.style.display==='none';
      if (isHidden) 
      {
        adsDiv.style.display='inline-block';
      }
      else
      {
        adsDiv.style.display='none';
      }
     }
  catch(e) 
  {
    try 
    {
      var adsDiv=document.getElementById('pagelet_ego_pane'),
          isHidden=adsDiv.style.display==='none';
      if (isHidden) 
      {
        adsDiv.style.display='inline-block';
      }
      else 
      {
        adsDiv.style.display='none';
      }
    }
    catch(e)
    {
      console.log('Failed. Message: ',e.message)
    }
   }
})();

Single line version for general use:

javascript:(function(){try{var adsDiv=document.getElementById('pagelet_side_ads'),isHidden=adsDiv.style.display==='none';if(isHidden){adsDiv.style.display='inline-block';}else{adsDiv.style.display='none';}}catch(e){ try{var adsDiv=document.getElementById('pagelet_ego_pane'),isHidden=adsDiv.style.display==='none';if(isHidden){adsDiv.style.display='inline-block';}else{adsDiv.style.display='none';}}catch(e){console.warn('toggleFBAds failed. Message:',e.message,'. This can mean the id was not found.')}}})();

Drag it to your Bookmarks toolbar: Toggle FB ads

You wouldn’t want to nest try/catch blocks in catch statements endlessly, so you might extend this into lessons on arrays, loops, recursion… that depends on you and your learners’ situation.

§

Further reading

console on the Mozilla Developer Network

try…catch on the Mozilla Developer Network


Jun 18

Bookmarklets for fun and practice

Bookmarklets are JavaScript links that can be stored in your browser’s Bookmarks or Favorites folder, or attached to a bookmarks toolbar, and then used to do something relative to that page. I think bookmarklets have a lot of value for teaching and self-teaching JavaScript.

What can I learn playing with bookmarklets?

You need to create and use a fundamental unit of HTML: a link (also known as “anchor”; <a href="somewhere">Link</a> ). You can start with the most basic javascript:alert('Hello world');. You can learn how to use a closure javascript:(function(){ alert('Hello world'); })();. You’ll be forced from the start to pay close attention to syntax. If you haven’t yet, you’ll quickly figure out how to use Firebug (and/or any modern browser’s “F12 Developer Tools”) to inspect DOM elements to get their id and other properties. Ultimately you’re limited only by your skill, which will improve quickly, and imagination. What would you change about the blog page you’re reading now, if you could?

Where to start?

I started with a pet peeve about a page I visit regularly. My first bookmarklet ever hides the right column in Facebook, so I don’t have to see the ads. Take that, Mark! If you click the link below while on this page nothing will happen. But if you drag the link to your Bookmarks Toolbar (in Firefox, “Bookmarks Bar” in Chrome, etc…) do that while viewing your Facebook page (or any page that coincidentally has a right column div with id="pagelet_side_ads") you will toggle its visibility.

Bookmarklet1: Toggle FB ads Drag the link to your bookmarks bar to try it.

To embed a bookmarklet so it can be dragged to the toolbar you just place a link on your Web page:

<strong>Bookmarklet: <a href="javascript:(function(){var adsDiv=document.getElementById('pagelet_side_ads'),isHidden=adsDiv.style.display==='none';if(isHidden){adsDiv.style.display='inline-block';}else{adsDiv.style.display='none';}})();">Toggle FB ads</a></strong> 

The imagination runs wild

Still milking my own pet peeves, I wanted to collect lyrics of several songs I needed to learn for the weekend warrior band I play in. A recurring pattern is, we find a song by an artist that is a good fit for our sound, and due to that we later add 3 or 4 more by the same artist. Besides, I just like having lyrics handy… why not just get all the lyrics for that artist at once? But that would mean a lot of clicks and copy/paste! With some intermediate JavaScript you can collect all the links and titles, visit each page in a queue, get just the lyrics you want, and display them in one place in a fraction of the time. To engage students and make meaning of any learning situation requires context and relevance. Do you know any young people who like music, and might be engaged by collecting lyrics of their favourite artists?

I may explain this code in another post, but for now it’s about bookmarklets and an example of what one might do with one. If you don’t understand this paragraph you’ll need to do some vocabulary homework. This script only works at www.lyrics.com. That page has a version of jQuery installed so I used it. To write the script I used Firebug to identify IDs and classes of elements on the page that I use as “selectors” to have access. I fiddled in the console until I had a working script. Meticulous syntax is important… your script goes on one line, so semi-colons are in, comments are out.

Bookmarklet: Get Lyrics This works on Artist pages at www.lyrics.com

This script is quite a bit more involved than the first. I did succeed in making it work in the link code like the first one, but there’s an easier way. In order to make more complicated scripts work you should use the bookmarklet to load your script from a file on your server. I created rcf-get_lyrics.js and placed it on this server. You can copy/paste the script below and just change the path to point to your own file.

javascript:(function(){var url="http://www.rcfouchaux.ca/rcf-get_lyrics.js",n=document.createElement("script");n.setAttribute("language","JavaScript");n.setAttribute("src",url+"?rand="+new Date().getTime());document.body.appendChild(n)})();

I’ll discuss the code in more detail in a future post, but quickly, you create a script element and set the source to the file, then append this new element. There’s a unique identifier, which I’m not using for anything right now, created from the time and appended to the url.

Few limits

The most impressive bookmarklet I’ve ever seen, one that immediately became essential to all my JavaScript learning and development, is Alan Jardine’s Visual Event. Visual Event is an open source JavaScript bookmarklet which provides debugging information about events that have been attached to the DOM (Document Object Model; it means the entire web application represented by the “web page” loaded in the browser’s window).

Alan’s script demonstrates how to load a complex script off a server using only a small manageable amount of code in the bookmarklet itself. As you see, I borrowed it but removed his protocol check for simplicity. I think the aforementioned date/time “query string” he adds prevents the browser from caching the script indefinitely, but I didn’t research that—it’s a guess.

Another impressive project that’s available as a bookmarklet is pjscrape. If I wanted to turn my lyrics scraper into a real utility I’d probably start with pjscrape.

Update 2: I’ve placed both the bookmarklets on their own page. I expect I’ll add to the list.

Update 3: I’ve added a variation on the Facebook ad hider, using display:none and targeting only the ads—compare the two and try to figure out what’s different. I’ll add all future updates on the bookmarklets page.

§

  1. This was updated since original publication and now uses id="pagelet_side_ads" and display:none;
May 28

Make Captivate HTML5 Work in Firefox

screen shot of error dialogWith the slow but steady demise of Flash, SWF-based elearning software makers, like Adobe, have been “cramming” (a word from disruptive innovation) HTML5 into their products, like Captivate (I’m using v6.1), to keep up. Because the HTML5 spec itself is still growing in inconsistent and at times uncertain directions there are bound to be holes and gaps in the implementations. For Captivate 6.1 the first one I encountered was the “This browser does not support some of the content…” error I got when I attempted to view my HTML5 output in my browser of choice—Firefox. It was version 20, and I know Firefox has supported HTML5 audio and video tags since version 3.5 so it had to be something silly. In the proprietary software world that may mean license issues, and so it does with Firefox (Free, Open, Libre software) and the MP3 (not so much). Captivate exports only MP3s, Firefox (and Opera) play only OGGs. So until Adobe offers a checkbox on the Publishing page you can do the following, or hope your visitors use only IE, Safari or Chrome—as the majority probably do… but we’re big on inclusion around here so let’s not leave anyone out!

I quickly ascertained it’s the MP3-only audio export preventing Firefox from doing Captivate sound. As you probably know, some browsers do MP3, others do OGG. You may even remember why. Fortunately that’s the only reason Firefox won’t play Captivate HTML5. All you have to do are

  1. create OGG versions of each MP3 and
  2. edit two JavaScript files to
    1. detect Firefox
    2. provide the appropriate file extension and
    3. suppress the error message.

Simple enough you say, but I figured I may as well google it and see what others have done. I did, and found all the hints I needed in hermit9911’s answer to this post (scroll down a few). hermit just needs OGG. I want both to work depending on browser, so I had to improvise on hermit9911’s theme. Here are the steps I ended up following.

1. Create OGG versions of each MP3

There are many programs that convert audio formats. I used Audacity’s “Chains” feature (macros). Since I’ve met many more people who know of Audacity than know of its Chains feature, I’ve done a separate screencast of that process:

Made using Camstudio (screen video), iPhone (narration),
Audacity (mix/process audio) & Sony Vegas (mix/render video)
Can you suggest a good open source video editor? Please use the comments with my thanks!

2. Edit two JavaScript files

detect Firefox

Project.js is found in the root export folder. It’s “minified,” which means line breaks and extra spaces are removed and it’s pretty hard to read, but we can add our tiny bit of code right at the beginning. I don’t see any need for feature detection in this case, so I settled for user agent:

var is_firefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; 
// converts the string to lowercase then creates Boolean, 
// true if it finds 'firefox' anywhere in the name. Otherwise : false 

provide the appropriate file extension

Once you have a question that can be answered yes or no the best syntax is usually as follows:

myExtension = is_firefox ? '.ogg' : 'mp3'; <br>// Yes? Send '.ogg' No : send '.mp3

Then just search & replace existing .mp3' with '+myExtension Everything between ”s is literal, myExtension is variable depending on the answer to is_firefox?yes:no;

UPDATED: If you have Administrator rights and access to the Adobe Captivate install folder you can add these changes to the template Captivate creates the file from. Otherwise both files are overwritten every time you publish, and you have to repeat all these steps. If you can access and edit
[InstallFolder]\HTML\assets\js\CPHTML5Warnings.js
use that file instead (path to install folder will differ depending on operating system and installation) otherwise follow the instructions as written:
Next, open [PublishFolder]/assets/CPHTML5Warnings.js and make the two additions suggested by hermit9911. (I looked up the earliest version of Firefox that supported the audio tag and replaced hermit9911’s xx with 3.5).

this.BrowserEnum.FIREFOX_MIN_SUPPORTED_VERSION = 3.5; // sets minimum version, used in code below

suppress the error message

Finally at the very bottom of the file you will find a series of IF and IF ELSE statements… add the following after the closing semicolon of the last one:

else if((this.browser == this.BrowserEnum.FIREFOX) <br>  &amp;&amp;<br> (this.browserVersion &gt;= this.BrowserEnum.FIREFOX_MIN_SUPPORTED_VERSION )) <br> lSupported = true;

Browse to index.html in Firefox, press Play and watch your movie. With a bit of practice this will add all of about 5-10 minutes to your publishing routine. Alas, you’ll have to redo Project.js—and convert all your audio files— every time you publish.

As an extension activity, make this work for Opera (yes, you can use find/replace but watch case-sensitivity. Do each separately… firefox/opera and FIREFOX/OPERA). Use flow control instead? (Hint: yes) Look back at the code above and explain why you need to do that. Would you change the variable name? To what?

My entire solution instructions and code snippets
(including my answers to the extension questions)

/* ADD TO TOP OF Project.js */
var wantsOggsOverEasy = ((navigator.userAgent.toLowerCase().indexOf('firefox') > -1) || (navigator.userAgent.toLowerCase().indexOf('opera') > -1)), myEXT=wantsOggsOverEasy?'.ogg':'.mp3'; 
/* nb comma/semi-colon usage - I declared 2 local vars in 1 dec */

/* This is the text to search for
   .mp3' and change it to '+myEXT
  EXAMPLE
  FROM: src:'ar/Mouse.mp3',du:182
       TO: src:'ar/Mouse'+myEXT,du:182

IF you have Admin privileges find the Captivate install folder and edit the warnings file found in
[InstallFolder]\HTML\assets\js\CPHTML5Warnings.js. Otherwise you can save these changes in a safe place and you'll need to replace [PublishFolder]/ar/CPHTML5Warnings.js in the published location every time you publish. (Project.js is generated programmatically by the publishing engine, so to the best of my knowledge it must be fixed after each publish, also protect your OGG files.)  */

/* In CPHTML5Warnings.js 
(somewhere between lines 19 and 20) non-destructively ADD:  */

        this.BrowserEnum.FIREFOX_MIN_SUPPORTED_VERSION = 3.5;
        this.BrowserEnum.OPERA_MIN_SUPPORTED_VERSION = 10;

(approximately line 112) AFTER final 
          lSupported = true; AND BEFORE

          return lSupported; non-destructively ADD 

		else if((this.browser == this.BrowserEnum.FIREFOX) && (this.browserVersion >= this.BrowserEnum.FIREFOX_MIN_SUPPORTED_VERSION))

            lSupported = true;	
		else if((this.browser == this.BrowserEnum.OPERA) && (this.browserVersion >= this.BrowserEnum.OPERA_MIN_SUPPORTED_VERSION))
            lSupported = true;

§

N.B. Post was edited for clarity since first publishing.

May 06

Multimedia in eLearning? Bring Popcorn and Butter!

Popcorn WebMaker is a Mozilla project. The video you see in the frame below is actually 3 YouTube videos, linked and enhanced using Popcorn (popcorn.js) and Butter (butter.js). Popcorn uses JavaScript to synchronize events you plan and implement with the audio or video that’s playing. Butter is an HTML5 timeline interface that lets you set it all up, it works much as Adobe Captivate, although not nearly as advanced—yet. Open source technologies tend to be less refined until they find a niche market, and eventually interest and a community attach a commitment to their further development. A classic example is the transistor radio. While audiophiles built ever more expensive high-fidelity vacuum tube amplifiers and receivers, with special speakers and advanced crossovers, a cheap, portable unit, sometimes with a 1.5 inch paper speaker sounding like a telephone, caught on with teenagers, with the eventual result that transistors and miniature speakers created a new market, marginalizing the status quo in the process; vacuum tubes now inhabit niche markets (rock guitarists in particular have helped keep the industry from disappearing altogether). JavaScript supplies interaction that was impossible within the video file itself.

Very Basic Web App 101

I’ve already noted an irony… I was unable to watch these videos on my iPhone, and yet I uploaded HTML5-friendly .webm files. That seems to be about YouTube, though, not Popcorn.

Will Popcorn and Butter disrupt Adobe? You won’t see it on corporate training sites any time soon, but you can rest assured the number of people who know what it is and try to use it will surpass Captivate’s in a short time — and they’ll have lots of fun at https://popcorn.webmaker.org/ exploring ideas and re-mixing the ideas of others. I’ve only just begun exploring this exciting new resource. I hope you will join me.

Try Mozilla’s Popcorn Maker for yourself. https://popcorn.webmaker.org/

I’ve already encountered some PopcornMaker gotchas, including an inability to reliably hold HTML code in Text or Popup events, making it difficult to do some of the lessons I had in mind. I expect to write a few more blog entries on Popcorn, and though I got a late start I’m taking part in Teach the Web: a Mozilla Open Online Collaboration for Webmaker mentors. I’ll have much more to say, and I’ll tackle the gotchas, when I see and hear how others have approached this fledgling resource in the 21st-Century-Educator’s repertoire.

§

Mar 29

Instant gratification as intrinsic motivation.

“I learned HTML CSS and JavaScript exactly the same way I learned guitar—by stealing other people licks.” chord diagram, E major, first position.
I’ve said this a few times, but I’m coming to believe my point is largely being missed. I think if the point’s worth anything at all it’s incumbent on me—the communicator—to give it another try.

Continue reading