Convert “localhost” to your Mac’s current IP address

When developing web pages, I use MAMP.app or my Mac’s built-in Apache. Viewing the page means using an address such as http://localhost/mypage.html. If you use custom host names (especially easy with the excellent VirtualHostX.app), you may wind up with a localhost address such as http://projectname/mypage.html.

This works great when you’re just testing the pages on your Mac’s browsers. However, once you cross boundaries into Windows testing (via VMs or separate laptops), localhost will no longer resolve. Why? Because localhost is local to your machine.

If you want to view the page in a VM or on another machine, just swap the domain name with your machine’s IP address. For example http://localhost/mypage.html becomes http://10.0.1.14/mypage.html. (Note: you must be on the same network or have a public IP address.)

This works very well, but it’s tiresome to manually grab the IP address anytime you want to use a VM or share the page with coworkers, especially if you’re on DHCP and don’t have a static IP address.

I decided to make my life a little easier by writing an AppleScript that looks at the open tabs in Chrome and Safari then replaces “localhost” (or custom domain) with my current IP address. Saving this as a service enables me to go to Chrome > Services to run the script.

Chrome > Services

If you’d like to give it a try, the AppleScript is available as a Gist on GitHub.

I guess there’s no such thing as a secure PDF

I was reading the SCORM 1.2 reference docs today. I wanted to copy a passage for my notes, but the PDF is password-protected and prevents anyone from copying text. (REALLY irritating, considering the ADL is a quasi-government organization and the docs should be open to all.)

What to do? Well, turns out there are at least two super easy ways to bypass the password protection: Upload it to Google Drive or import it to Evernote.

Google Drive

The Google Drive site includes a built-in PDF reader; when I opened the PDF in the web viewer, I was able to copy text freely. Better yet, I was able to save the PDF as an unlocked file by selecting “Print” then choosing “Save as PDF” in the print options.

Evernote

When dragging the file onto the Evernote app (Mac), the PDF shows up in a preview window. I was able to copy text freely. No need to save as a PDF since it’s already stored in Evernote!

Security, schmescurity

So I guess there’s no such thing as a secure PDF. I’m sure there are other services like to Google Drive and Evernote, and there are definitely other techniques for defeating protection, including screen captures, OCR, and the old fashioned approach of printing to paper then scanning the prints. If you truly need a document to be secure, don’t distribute it electronically.

iTunes, TV Shows and Apple TV

iTunes vexes me. For better or for worse, we’re an Apple household and own an Apple TV, so I’m kind of stuck with iTunes for managing my media files.

My wife and I have also purchased a significant amount of DVDs over the years, which I ripped to iTunes using the trusty old Handbrake (love you, Handbrake!). These DVDs include a lot of TV shows, such as Doctor Who and Magnum PI.

My workflow has always been: rip via Handbrake, then import into iTunes by dragging the m4v files onto the iTunes window. By default, the TV shows don’t have any metadata (no proper titles, descriptions, episode numbers, or artwork), and iTunes automatically files them under Movies. This means they’ll show up in Apple TV with no description, no preview picture (such as DVD box art), and no sequence information.

I recently heard someone mention iDentify, a Mac app that adds metadata to movie files. It’s not free, so I had reservations about buying it. However, $10 is a small price to pay for cleaning up such a big mess, especially if you’re a bit OCD like me. I decided to give it a try, and it works very well, especially for TV shows — if you manually specify each file’s season and episode number, iDentify will take care of the rest by performing lookups at thetvdb.com. Sweet.

iDentify took care of the metadata and artwork problem, but the files were still cluttering my Movies menu, making it very hard to navigate with a remote control. For example, Magnum PI went eight seasons and has over 150 episodes, so we’d have to navigate past 150 Magnum PI titles to get to any videos whose name began with N-Z. Very annoying.

For a long time my workaround was to create custom genres and shove the TV shows there, then stick to genres when navigating Apple TV. This always felt kludgy, and I wondered why I couldn’t just drag the TV show episodes onto the TV Shows section in iTunes. This weekend I decided to look into it, and stumbled onto a MacWorld article containing a solution so simple I had to do a double face-palm: change the Media Kind from Movie to TV Show.

iTunes file properties dialog, 'Options' tab

Once set, the video is automagically moved from the iTunes Media/Movies folder to the iTunes Media/TV Shows folder, and shows up in the TV Shows menu!

Be sure to input the show’s name in the Video section so the episodes will be properly grouped.

iTunes file properties dialog, 'Video' tab

The MacWorld article pointed out that this technique can be extended to group ANY videos. This piqued my interest — my wife and I own a lot of DVDs that contain high-quality special features, including the entire James Bond collection, Star Wars collection, and classic films like Lawrence of Arabia. As I mentioned, I’m partially OCD, so I’ve ripped quite a few of these special features. Until now, they’ve all cluttered up my Movies menu just like the TV shows did.

TV Show grouping to the rescue! By changing the videos’ Media Kind to TV Show, they get moved to the TV Show section and can then be grouped. For example, I grouped all of my James Bond special features under the heading “James Bond Featurettes”. Now when I navigate the TV Shows section of iTunes or Apple TV, I only see ONE listing for James Bond Featurettes and no longer need to sift through 100+ titles.

iTunes still leaves a lot to be desired, but I’m a happy camper now that my files are well-organized and have proper metadata.

Setting OS X Desktop Picture Based on Time of Day

I recently changed jobs (Hello, FireEye!) and was issued a new MacBook Air. I spend a lot of time looking at the screen and was getting bored with the supplied desktop pictures. I also start work very early most days (7am-ish), and thought it would be nice to have a desktop picture that matches the mellow-ness of such an early hour.

Of course, this leads to daydreaming — “scope creep” in professional parlance — and next thing you know, I started thinking “well, maybe I could also set it to show a nice evening-themed picture at night”. Then “maybe I can get it to change both screens” (I use a laptop with an external display).

I also liked the challenge of putting together a script as quickly as possible. (In my off-hours, of course!)

I downloaded some nice wallpaper images from National Geographic, then created six folders that correspond to the major periods of the day: morning (early and late), afternoon (early and late), and evening (early and late). I organized my National Geographic photos into those six folders, based on the mood each photo evokes. For example, this one is an early morning photo.

Then I rolled up my sleeves and got out the trusty old AppleScript Editor. The resulting AppleScript is posted on GitHub, if you’d like to take a gander.

The gist:

  • It selects a folder based on the time of day.
  • It randomly selects an image from within that folder and displays it as the desktop picture.
  • It supports more than one monitor, with an option to either display the same image on all monitors, or display different images on each monitor.

The resulting AppleScript must be run at a regularly scheduled interval. I’m currently using GeekTool to run the script every 15 minutes, but I might eventually switch to a crontab job for less overhead.

Regardless, I’m quite happy with the way it turned out, and have already started daydreaming about other things I can hack together with AppleScript.

Importing Google Contacts into iCloud

I signed up for the new iCloud service, and wanted to sync my Google contacts so they will show up on my various Apple devices. MobileMe, iCloud’s predecessor, had built-in support for syncing with Google accounts, so I assumed iCloud would be a no-brainer. Unfortunately, it turns out iCloud does not auto-sync with Google.

It’s easy to completely wipe the iCloud contacts and replace them with Google contacts. However, I didn’t want to import ALL contacts — I wanted a curated list of contacts so I won’t have to scroll through hundreds of names just to find a friend’s phone number. Also, like many people, my Google contacts and my iPhone contacts were not quite synced, and I had some contacts on the phone I didn’t want to lose.

I hunted high and low for automated solutions, including software tools, when I realized it’s actually pretty easy to perform a manual sync. The basic steps are:

  1. Import contacts from iCloud into Google
  2. Clear all contacts from iCloud
  3. Replace iCloud contacts with a curated list of contacts from Google

I expanded the tasks into ten steps, listed below. They look more complicated than they really are — the entire process should only take you around 5 minutes, unless you get caught up in cleaning your Contacts list, which in my case took over an hour!

Note: These steps assume you’re on a Mac, you’ve already set up iCloud on your Mac and iDevices, and all of your devices are synced. All we’re doing is adding Google contacts to the mix.

The steps

  1. On your Mac, Launch Address Book.app and export all Address Book contacts to vCard format.

    Edit > Select All, then File > Export > Export vCard

  2. Take a deep breath, then delete all of the contacts from Address Book.app. This will also automatically delete all contacts from iCloud. (Obviously you should only do this if you’ve confirmed your export in step 1 was successful.)

    Edit > Select All, then Edit > Delete Cards

  3. Launch a web browser and log in to the Contact Manager for your Google account.
  4. Clean up your contacts in Google Contact Manager. This makes it easier to sort through them later. The most important thing is to merge all duplicates where possible.

    More > Find & merge duplicates…

  5. Create a new group in Contacts Manager — I named mine “Sync with iCloud” — and add any contacts you would like to import to iCloud. This enables you to selectively import contacts from Google to iCloud. If you want to sync ALL of your contacts, you can probably skip this step.
  6. Import the Address Book vCard you created in step 1 into Google Contact Manager. This will ensure you don’t lose any contacts that were on your Mac/iPhone but not in Google.

    More > Import

    Upon successful import, you’ll notice Google Contacts Manager has created two new groups: “cards” and a group with today’s date with a name like “imported on [date]”. These two groups are identical, so I’m not sure why Google creates both of them.

  7. Perform another ‘merge’ to clean up any duplicates you may have created by importing your Apple contacts.

    More > Find & merge duplicates…

  8. If you’re being selective about your contacts list, sift through the new ‘cards’ group, adding any desired contacts to your “Sync with iCloud” group.
  9. Once your “Sync with iCloud” group is pruned and ready to go, export it to vCard format.

    More > Export…

    Make sure you select your iCloud group from the drop-down menu under “Which contacts do you want to export?” or else you will be exporting every contact in your Google Contacts list. Of course, if you’re going to export all contacts, go ahead and select the entire “My Contacts” list.

  10. On your Mac, drag the vCard onto Address Book.app to import the contacts. They will sync with iCloud almost instantly.

That’s it! You’re done. It isn’t a perfect process by any means, but it enabled me to merge my Apple Address Book with my Google Contacts, then import only the important contacts back to iCloud.

Drawbacks

1. This is a manual process, so any changes to iCloud will not be reflected in Google and vice versa.

2. The creation of the three new Google Contacts groups is a bit messy. Once I was done with step 10 above, I deleted the two groups created by the vCard import. This isn’t necessary, but I found them a little annoying to look at.

Using the object element to dynamically embed Flash SWFs in Internet Explorer

This is a journey into the madness of Internet Explorer.

Yes, there is a happy ending. Jump to the end of the post if you just want the solution and don’t care about how we got there.

The Scenario

You want to embed a Flash SWF into your HTML document using the object element, and you need to be able to do it using JavaScript. (Let’s pretend SWFObject doesn’t exist, ok?)

If you were using HTML markup without JavaScript, embedding a Flash SWF using an object element would be pretty straightforward:


<object id="mySWF" width="550" height="400" 
        data="mymovie.swf" 
        type="application/x-shockwave-flash">
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

Microsoft designed Internet Explorer’s object to work a bit differently, so the markup for Internet Explorer becomes:


<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf" />
   <param name="flashvars" value="dog=woof&cat=meow" />
</object>

The key differences are:

  • Internet Explorer requires the classid attribute instead of type
  • Internet Explorer requires the param name="movie" child node instead of the data attribute in the object

Annoying, perhaps, but workable. If you’re working with hard-coded markup — what SWFObject refers to as static publishing — you can drop in some conditional comments and be done:


<!--[if IE]>
<object id="mySWF" width="550" height="400"
        classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000">
   <param name="movie" value="mymovie.swf">
<![endif]-->

<!--[if !IE]>-->
<object id="mySWF" width="550" height="400"
        data="mymovie.swf"
        type="application/x-shockwave-flash">
<!--<![endif]-->

   <param name="flashvars" value="dog=woof&cat=meow" />

<p>Fallback content for people without Flash Player</p>

</object>

That’s nice, but I need to use JavaScript

On the surface, using JavaScript to recreate the HTML markup appears to be a trivial task; we just need to add conditional logic to handle the two small differences between Internet Explorer’s object element and the object element used by the other browsers:


var target_element = document.getElementById("replaceMe"),
   obj = document.createElement("object"),
   isMSIE = /*@cc_on!@*/false;

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

if (isMSIE) {            

   //IE requires the 'classid' attribute
   obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
   
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);

} else {

   //Non-IE browsers require the 'type' attribute
   obj.setAttribute("type", "application/x-shockwave-flash");
   
   //Non-IE browsers require the 'data' attribute
   obj.setAttribute("data", "test.swf");

}

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Seems simple enough, right? Not so fast. If you try running this in Internet Explorer (6-9), you’ll notice the SWF fails to load, and IE will behave as though it’s stuck trying to load a file — the “loading” icon never goes away.

Issues with Microsoft’s proprietary classid attribute

It turns out Internet Explorer’s object does not like having the classid appended after the object has been created. This is very similar to the well-known IE bug for adding the name attribute to certain form elements. For the form element bug, Microsoft’s solution is to include the name attribute in the createElement string:

Attributes can be included with the sTag as long as the entire string is valid HTML. To include the NAME attribute at run time on objects created with the createElement method, use the sTag.

Microsoft provides the following example:


var newRadioButton = document.createElement("<INPUT TYPE='RADIO' NAME='RADIOTEST' VALUE='First Choice'>")

What happens if we use this solution for the classid issue?


var node_name = (isMSIE) ? "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />" : "object";
var obj = document.createElement(node_name);

This works like a charm… in IE 8 and lower. Unfortunately, IE 9 chucks a wobbly. Microsoft, in their wisdom, decided that angled brackets should no longer be valid inside the createElement method. This is actually excellent news: Internet Explorer 9 is behaving like other browsers, so let’s just use a try/catch to target IE versions prior to IE9, and use standard W3C code for IE9:


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");

      //IE requires the 'movie' <param>
      var param_movie = document.createElement("param");
      param_movie.setAttribute("name", "movie");
      param_movie.setAttribute("value", "test.swf");
      obj.appendChild(param_movie);
      
   } catch (e) {
      
      //Let IE9 and higher fall through and use the standard browser markup
            
   }
   
}

if (!obj) {

   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Still works in Internet Explorer 8 and lower, but fails in IE 9. Okay, perhaps IE9 still requires the classid attribute and ‘movie’ param. Let’s fork the code some more and try it out.


var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj;

if (isMSIE) {

   try {
      
      //For IE 8 and lower
      obj = document.createElement("<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000' />");
      
   } catch (e) {
      
      //IE9 doesn't support classid in createElement, so let's add it afterward
      obj = document.createElement("object");
      obj.setAttribute("classid", "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000");
      
   }

   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
   
} else {

   //Standard browsers
   obj = document.createElement("object");
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
   
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

Are you ready for the surprise? This doesn’t work in IE9, either. Adding classid to the object after it has been created simply will not work in any version of Internet Explorer. Microsoft removed their own recommended workaround — adding the attribute in the createElement string — to standardize their browser, yet they failed to provide full support for the related W3C standards being used by their competitors.

In their announcement of the createElement change for IE9, Microsoft recommended using the W3C standard setAttribute method, yet it fails for classid. *grumble*

To their credit, Microsoft also provided a second workaround consisting of strings and innerHTML:


var parent=document.createElement("div");
parent.innerHTML="<div id='myDiv'></div>";
var elm=parent.firstChild;

For our purposes, it would look like this:


function createIeObject(){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject() : document.createElement("object");

if (isMSIE) {
   //IE requires the 'movie' <param>
   var param_movie = document.createElement("param");
   param_movie.setAttribute("name", "movie");
   param_movie.setAttribute("value", "test.swf");
   obj.appendChild(param_movie);
} else {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

This feels like it’s getting us somewhere. Unfortunately, the SWF still doesn’t load. What else can we try? What if the ‘movie’ param were added at the same time the object is created and classid attribute is specified in Internet Explorer?


function createIeObject(url){
   var div = document.createElement("div");
   div.innerHTML = "<object classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'><param name='movie' value='" +url + "'></object>";
   return div.firstChild;
}

var target_element = document.getElementById("replaceMe"),
   isMSIE = /*@cc_on!@*/false,
   obj = (isMSIE) ? createIeObject("test.swf") : document.createElement("object");

if (!isMSIE) {
   obj.setAttribute("type", "application/x-shockwave-flash");
   obj.setAttribute("data", "test.swf");
}

//Add attributes to <object>
obj.setAttribute("id", "myObjID");
obj.setAttribute("width", "550");
obj.setAttribute("height", "400");

//Add <param> node(s) to <object>
var param_flashvars = document.createElement("param");
param_flashvars.setAttribute("name", "flashvars");
param_flashvars.setAttribute("value", "cat=meow&dog=woof");
obj.appendChild(param_flashvars);

//Replace targeted DOM element with our new <object>
target_element.parentNode.replaceChild(obj, target_element);

EUREKA! The SWF now displays as it should in all versions of Internet Explorer, and IE no longer behaves as if it’s stuck trying to load something. The takeaway is that Internet Explorer’s proprietary classid attribute and ‘movie’ param need to be created together or all is lost.

Why not use innerHTML all the way?

Many developers will say: Why not use innerHTML for the whole thing? After all, SWFObject 2.0 through 2.2 uses string building and innerHTML for the entire object creation in Internet Explorer:


el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';

In SWFObject, the attributes (att) and parameters (par) are added to the string via string concatenation. This is functional, but means that a completely different workflow — one devoid of standard W3C techniques — is required for Internet Explorer versus all other browsers.

For example, if there are many param nodes to add to the object, they will have to be generated as a long string of params in IE, while the fork for the non-IE browsers will all use a createElement/appendChild combination. In my opinion, it would be best to keep the code simple and use the same code for handling attributes and parameters in all browsers. This is ideal for maintenance, security, and file size.

Using the proposed IE solution allows us to modify the object via W3C techniques — we can add parameters and attributes and they all function as expected. The only difference between IE and other browsers is the creation of the initial object, but once it’s set up, IE’s object behaves the same in all browsers.

The obligatory poke at Microsoft

This is a perfect example of why there is not much love for Internet Explorer (and by extension, Microsoft) in many web developer circles. A task that should be trivial is utterly complicated, and requires hours of troubleshooting and experimentation just to achieve some basic functionality.

It’s my sincere hope that Internet Explorer 10+ will use the standard object code, but it probably won’t, since IE will probably still rely on ActiveX for the Flash Plugin. Falling short of using the standard object element, I hope that IE 10+ will at least allow us to add the classid attribute using setAttribute, and also allow us to add the movie param using createElement and appendChild.

Sniffing Internet Explorer via JavaScript

I’ve been reviewing bug submissions for the SWFObject project and was reminded of a big problem with SWFObject 2.2: the JavaScript technique it uses for detecting Internet Explorer does not work in Internet Explorer 9.

SWFObject 2.2 currently uses an IE detection technique proposed by Andrea Giammarchi in 2009:


var isIE = !+"v1";

It’s a hack that relied on Internet Explorer’s unique handling of vertical spaces (v). Now that it has stopped working, I decided to take a quick look at what others are doing.

One of my favorite approaches is Dean Edwards’ “sniff” technique, which takes advantage of Microsoft’s conditional compilation.


var isIE = /*@cc_on!@*/!1;

It’s 4 years old, but still works like a charm. The only problem is that some JavaScript compressors and optimizers (including YUI Compressor and Google Closure) have a hard time with the inline comment and strip it out. It makes the code difficult to maintain, because it requires editing post-compression.

One way to get around the compressor issue (as pointed out by a commenter on Dean Edwards’ post) is to wrap the conditional compilation statement in an eval() function:


var isIE = eval("/*@cc_on!@*/!1");

Since eval() is evil, I won’t use this approach.

One of the most long-standing methods of detecting Internet Explorer is to examine the userAgent string:


var isIE = /msie/gi.test(navigator.userAgent);

However, this isn’t foolproof, as most browsers allow you to change the userAgent string at will. For example, I changed Safari’s userAgent string to report itself as Internet Explorer 8, and /msie/gi.test(navigator.userAgent); returned true!

So far, the only test I’ve found that seems to fit the bill is the old navigator.appName test:


var isIE = navigator.appName === 'Microsoft Internet Explorer';

Is it perfect? Not a chance, but it has a lot of upside: it works in IE9, doesn’t get damaged when compressed, doesn’t rely on any hacky tricks (as fun as they might be), and isn’t affected by spoofed userAgent strings.

What are the Frameworks using?

jQuery uses userAgent sniffing, and includes a warning that it’s unreliable. MooTools uses a combination of properties from the navigator object, including navigator.userAgent and navigator.platform. It’s interesting to note that MooTools previously used feature detection to infer the browser brand, but changes introduced by Firefox 3.6 prompted the MooTools team to switch to a userAgent-based detection method instead. (Nicholas Zakas wrote an interesting blog post about MooTools’ prior detection technique.)
Dustin Diaz’s Bowser detection script uses userAgent exclusively.

Why sniff in the first place?

Before you get down on me for even talking about sniffing, relax, I agree with you. I much prefer feature detection to browser sniffing; this is especially important with our quickly changing browser landscape: HTML5, web storage, geolocation, etc. (Modernizr is a great tool for modern feature detection.)

But Internet Explorer always makes us do things we’re not quite comfortable doing. Sometimes it’s not very easy to detect IE’s support for a given issue. For example, IE is notorious for not allowing JavaScript developers to set the name attribute of form elements via setAttribute; it requires including the name in the createElement invocation. Craziness!

So we carry on. I use feature detection for just about everything, but once in a while I need a global “are you Internet Explorer?”, and browser sniffing fits the bill.

Multiple Macs, One iTunes Library

The problem

50GB+ iTunes library.
Multiple Macs.
How can you share the iTunes library between Macs?

Solution #1: Home Sharing

The answer Apple provides is “Home Sharing,” which allows you to share the contents of your iTunes library with up to 5 Macs.

However, there are major shortcomings with Home Sharing. The first is the inability to edit the shared library. For example, if you want to edit a playlist, you have to use the Mac that controls the iTunes library. If you’re on a different machine and use Home Sharing to listen to your library, you will not be able to make any edits to the playlist (or add any new music).

A second major shortcoming is the inability to sync your iDevices (iPhone, iPad, etc.) using your other Macs. Apple wants you to keep your iDevice registered on a single Mac. If you sync your iPhone at home on your iMac, then take your iPhone and MacBook Pro with you on a trip, you won’t be able to sync your iPhone using your MacBook. Very annoying.

Solution #2: Dropbox

If your iTunes library is small enough, you can move your entire iTunes library onto the cloud. For example, a service like Dropbox will let you store up to 5GB online for free. If you use the Dropbox app on each of your Macs, Dropbox will copy all of your iTunes files to each computer, and will keep all of the items in sync for you automatically. If you add MP3s to iTunes using you MacBook, they’ll show up on your iMac shortly afterward.

There are two serious shortcomings to this system. The first is synchronization of the iTunes database file. You can run into synchronization problems if you edit the iTunes library on two Macs with a short period of time. Dropbox needs time to synchronize the files; if you edit the iTunes library on one machine, then edit the iTunes library on a second machine before Dropbox has synchronized the changes from the first machine, your iTunes database files will get out of sync and you’ll wind up losing some of your changes.

Let’s say you edit a playlist on your iMac, then import a CD using your MacBook. If the database file on the iMac isn’t synced before importing the CD on your MacBook, Dropbox will only sync the newest item. In this example, the imported CD would be retained but the edited playlist would be lost.

(It should be noted that this limitation applies to all cloud sync tools, not just Dropbox.)

The second drawback to syncing via Dropbox is the size constraints: Dropbox’s free account is limited to 5GB. As of this writing, their largest account is 50GB and requires a substantial monthly fee. My iTunes library is well over 80GB, so I’m out of luck.

Solution #3: Network Drive

If you have an Apple Time Capsule at home, you can use its file sharing features on your home network. (Attaching an external hard drive to an Airport Extreme provides the same functionality.) In theory, you can place your entire iTunes library on this network drive and let all your Macs connect to it.

Of course, nothing is ever that simple! When a Mac opens the iTunes library file, it becomes locked and no one else can use it. The other Macs will have to wait until the file is unlocked before they can connect to it. This means only one Mac can use iTunes at a time. Major drag.

Solution #4: Dropbox and Network Drive Hybrid

The Dropbox solution is certainly the nicest (and easiest) so far, but due to Dropbox’s account size limitations, it won’t work for me. However, since I have a Time Capsule with plenty of space, I’ve decided to combine the Dropbox and network drive approaches. Here’s how it works:

1. Move your entire iTunes folder to your home network drive.

2. Copy the iTunes library (database file, XML files) to your Dropbox folder (I placed them in a subfolder named iTunes).

3. Create an alias of the network drive’s iTunes Music folder and place it in your Dropbox folder. Be sure to rename it to iTunes Music (remove the word “alias”). Create an alias of the Album Artwork folder, too. If you skip this step, whenever you import music to your library, it will be copied to your Dropbox folder instead of your network drive. The aliases ensure iTunes places all new library items on the network drive.

4. On each Mac, configure iTunes to use the library file located in the Dropbox folder. Do this by holding option on your keyboard when clicking the iTunes icon in your dock. You will be prompted to choose an existing library or create a new one. Click “Choose Library” then navigate to your iTunes database file in your Dropbox folder.

That’s it! You can now access your iTunes library on each Mac without using Home Sharing. iTunes will behave identically on each Mac; you can add music, edit playlists, and sync your iDevices without Apple’s usual restrictions.

Caveats and Gotchas

I’ve been using this system for about a month with no major issues. Of course, no solution is perfect, and there are some gotchas to keep in mind with this setup:

  • Since Dropbox is handling the synchronization of the iTunes database file, only one Mac can edit the library at a time. Be sure to synchronize before using a second Mac to make updates.
  • If you update your iTunes software, you may need to recreate your aliases in the Dropbox folder.
  • You need to ensure you’re connected to your network drive before you start playing any files in iTunes, or else you’ll get the ‘missing file’ icon.

pipwerks SCORM Wrappers now available on GitHub

I’ve been considering adding my pipwerks SCORM wrappers to GitHub for a very long time, but my n00bness and general lack of free time were major obstacles. However, the time has finally come to buckle down and get these puppies OUT! So without further ado, I present:

https://github.com/pipwerks/scorm-api-wrapper

I should note that these are my original SCORM wrappers (JavaScript, ActionScript 2 and ActionScript 3); I’ve been (very slowly) working on a completely new SCORM support codebase that I plan to release separately. However, these oldies are still running like a champ, so don’t be afraid to use them.

Many people have contacted me over the last few years with suggestions for improvements, but due to my general busy-ness I haven’t really made any modifications for a long time; I recently made a few small tweaks, including updating the StringToBoolean function that caused problems in Plateau, but the bulk of the code remains largely the same.

If you’re one of those die-hards eager to tweak the source code, you’re in luck! This is a public repository, which means anyone and everyone is now free to fork and edit the code as they see fit. If you have any suggestions or ideas, please go to GitHub and show us what ya got!

And for the curious, I’m using Tower to handle my Git commits — command lines are not my cup of tea. Tower is a very nice Git GUI that integrates seamlessly with GitHub. I’m new to Tower, but so far I like it very much.

IFrames and cross-domain security, part 3

In 2008 I posted a quick writeup on how I dealt with cross-domain security issues for some of my e-learning courseware. Since then, I’ve had a lot of people contact me with various questions and requests for a working example.

Tonight I decided to revisit the topic and whip up some quick example files. To my chagrin, I discovered a load of typos in my original post. Yikes! No wonder people wanted to see some working examples.

Well, ask no more, here they are:

The explanation provided in the previous post is still valid, just refer to these new sample files instead of the code in that post’s examples. (I will update that post with new code examples soon.)

Remember, the proxy.html file must reside on the same domain as the parent frame. This solution will not work if you don’t have FTP access to both domains, since you need to place support scripts on both domains.