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


Video explorations on their way

Aside

We will soon return to our regularly scheduled programming:
…Some software and hardware reviews and how-tos (coming soon: Music lessons with iPhone and AmpKit!)
…I’m using CompendiumNG and/or designVUE to make storyboards for my own brand of guitar lessons
…and I’m also working on ways to make html5 video more interactive, making videos to make more interactive, and making videos of making videos to make more interactive.

The teachable moment, is a threshold to a concept.

Apr 12

Mobile blogging

Synopsis I was pretty excited when I found the WordPress smart phone app and saw it gives the user the ability to shoot and add images and video instantly, straight from the phone. My excitement quickly faded though, when I viewed the post in Firefox from my desktop. The app will upload a QuickTime .mov file, which can only be viewed in Chrome and Safari. Even with a browser that can view the file, if it’s rotated then you have a sideways video.

I also learned a great deal about the HTML5 video tag — and its limitations. You see the post pretty much as it was delivered by WordPress iOS, but I also added one of my custom rollovers containing a better video produced from screencasting (Camstudio 2.6) with an overdubbed narrated script (Audacity 2.0) and some text overlays (Vegas Movie Studio 10.0), uploaded to YouTube and embedded as an iFrame. YouTube will convert and serve the right video format for the device requesting it, which is reason enough to choose this method, but it also lets you upload your narration as a plain text “Transcript file” and it will convert it to subtitles. Very cool!


This post is from my iPhone using the new WordPress iPhone app. It’s set up for an account on WordPress.com by default, but you can easily change it to a self hosted one if you have one… and if you’ve “enabled XML-RPC publishing protocols” Say what!!?? Continue reading