The lower 48

While doing some online shopping, I received a notice that said

Estimated Economy Delivery (delivered via USPS): 6 to 12 business days within the lower 48 United States. All P.O. Boxes, APO/FPO/DPO addresses and shipments to Alaska, Hawaii, Puerto Rico and the Virgin Islands will ship via Economy Delivery.

I always get peeved when I see people use the phrase “lower 48 states”. Why? ¬†Well, having grown up in the State of Hawaii, I can tell you with certainty that the Hawaiian islands are farther south than any other state in the United States of America, even the southernmost point of Florida. Taken literally, “lower 48 states” actually means all states except Alaska and Minnesota (due to Lake of the Woods).

Somehow I don’t think these merchants were referring to Minnesota.

Meh.

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.

Lion Server compared to Snow Leopard Server

Update 10/25/2011: I’ve discovered that Apple provides Lion-ready versions of the older server management tools via a separate download from Apple, called Server Admin Tools 10.7 [link no longer available]. I have amended my post to reflect the capabilities of the Server Admin Tools 10.7 suite. Short version: Server Admin Tools 10.7 looks a lot like Server Admin Tools 10.6, but it has been stripped down, and has reduced functionality compared to Server Admin Tools 10.6 that ships with Snow Leopard. On the bright side, Server Admin Tools 10.7 doesn’t conflict with the new Server.app in Lion.

As I discussed in a previous post, I’ve been hosting this website on a Mac Mini server running Snow Leopard Server. The initial setup was a bit convoluted and not the most pleasant experience. It could have been much worse, but it was nowhere near what I expected from an Apple product.

At work, I recently had the opportunity to set up a Mac Mini running Lion Server. Now that I’ve had a chance to use it a bit, I thought I’d jot down a few comments. If you’ve done any research into Lion Server, you probably won’t be surprised by my comments.

First, the disclaimer: I have used Snow Leopard Server and Lion Server for slightly different tasks, so it’s isn’t a straight-up apples-to-apples comparison.

Web hosting

My Snow Leopard Server is confined strictly to web hosting: Apache, PHP, MySQL, and a dab of FTP. I host multiple sites and use a combination of the Server Preferences app and the Server Admin app to manage everything. Setting up websites in Snow Leopard is really easy via the Server Preferences app, and you have a pretty good assortment of settings to tweak in the Server Admin app if you want to fine-tune things.

Lion Server discarded both the Server Preferences app and the Server Admin app in favor of a new, streamlined “Server” app. It’s even easier to set up a website in Lion than it was in Snow Leopard. BUT… Server doesn’t provide ANY of the fine-tuning options that Server Admin provided in Snow Leopard. If you’re a total server admin n00b who only wants bare-minimum web hosting, this is probably good. But if you’d like any level of real control, you’ll be forced to use the command line. For me — not a n00b anymore but certainly not a command line guru — I’m left in the lurch a bit. I expect to be doing a ton of Google searches in the near future.

One example of how this can affect you: most domains should be set up with an alias to enable the domain to work without the “www” subdomain. For example, http://www.pipwerks.com is an alias that points visitors to http://pipwerks.com. If the www alias isn’t created, anyone who attempts to visit pipwerks.com by typing http://www.pipwerks.com will get a browser error. In Snow Leopard, it’s super easy to set up an alias in the Server Admin app. Lion does not provide this option, so you’ll have to do it via Terminal. More Google searches and wasted time.

Score (5 highest, 1 lowest): Lion: 2, Snow Leopard: 4

MySQL

Apple discarded MySQL in favor of PostgreSQL. This will cause problems for many who depend on MySQL, such as WordPress users. The good news is that you can install MySQL using an installer available for free from mysql.com. The bad news is you’re on your own when it comes to security patches and updates… Apple’s system update will no longer handle patches for you. (To be fair, Apple is notoriously slow to release security updates for server components anyway, so you probably shouldn’t rely on them to keep your system secure.) Not a showstopper, but definitely a stumbling block.

Score (5 highest, 1 lowest): Lion 1, Snow Leopard: 4

File Sharing

Snow Leopard’s file sharing GUI was decent, but I frequently had to tweak settings in multiple places. It was a bit disjointed and I was never quite able to get my FTP access set up the way I preferred. In Lion, the file sharing GUI is extremely simple and much easier to use. Maybe it’s because Apple removed the FTP option in the file sharing preferences! In Lion, you must use the Terminal to control your FTP services (or maybe find a 3rd party app). This seriously sucks. On the bright side, AFP is a cinch to set up. Since I’m only using AFP at the moment, I can be generous in my grading. If I depended on FTP, I’d grade much lower.

Score (5 highest, 1 lowest): Lion: 2.5, Snow Leopard: 2.5

User management

Configuring the directory in Snow Leopard Server was the single most painful experience I’ve had with Apple products (well, at least since the emergence of OS X — Mac OS 9 caused quite a bit of pain back in the day!). Configuring the directory seriously sucked and required hours and hours of troubleshooting, including multiple calls to Apple support. One little mistake when you first set up your system and it’s hosed, requiring a clean install.

Lion Server simplifies the process, and was astoundingly easy to set up. In fact, one of the key differences between the two systems is what happens when you first install the Server system: Snow Leopard forces you to configure your directory right away (even if you only intend to have a single user), while Lion doesn’t even mention the directory until you try to configure a service that relies on it. In this event, the configuration screen pops up and the process is so quick you’re done before you realize it. I can’t begin to tell you how happy that made me.

Snow Leopard has an app dedicated to user management (Workgroup Manager). Lion took the most basic elements of Workgroup Manager and turned them into a pane inside Server.app.

Update: Workgroup Manager is still available as part of the Server Admin Tools 10.7 [link no longer available] package, available as a separate download from Apple.

Since neither of my servers are used by clients (I only have a handful of accounts), I’m much happier with the slimmer approach used by Lion. Of course if you have many clients, you may prefer Snow Leopard’s far-reaching Workgroup Manager app.

Score (5 highest, 1 lowest): Lion 4, Snow Leopard: 1

Firewall

Snow Leopard Server includes a Firewall pane in Server Admin app, which makes it very easy to configure your firewall settings. This is gone in Lion.

Update: The new version of the Server Admin app (part of the Server Admin Tools 10.7 [link no longer available] package) provides a GUI for Firewall configuration. It’s a no-frills management panel, but it’s there if you need it.

Score (5 highest, 1 lowest): Lion 1 3, Snow Leopard: 4

Managed Clients

A “Managed Client” is a Mac that is associated with your server, allowing you to perform remote updates, enforce security options, etc. This is basically the feature that allows you to be an IT department and lock down computers registered on your system.

I never used the Managed Client feature in Snow Leopard, so I can’t make any comments. In Lion, the Managed Client feature (accessed via Profile Manager) is so easy to use, it makes me giggle. Granted, I’m not a “real” IT professional when it comes to managed clients, so take my comments with a grain of salt. All I know is, I needed to manage about a dozen Macs at work (think: computer lab-style shared workstations), and Profile Manager has been very easy for me to use, without having any real technical experience in the area.

One downside: all the Mac clients must have the Lion OS if they’re to be managed by Lion Server.

Update: From what I’ve researched, Snow Leopard clients can be managed from a Lion Server, but there are certain incompatibilities to watch out for. Clearly Apple would prefer everyone to use Lion.

Score (5 highest, 1 lowest): Lion 4.5, Snow Leopard: n/a

Summary

Lion Server really trimmed the fat, and Apple appears to have gotten so carried away they trimmed some of the good meat as well. If you’re looking for a GUI with lots of options, go with Snow Leopard Server. If you want the most streamlined experience you can get and only need to do basic web hosting, go with Lion. If you’re comfortable with Terminal and the command line, I’d suggest going with Lion because the GUI won’t get in your way as much and you probably already know what you’re doing.

I will be experimenting with Lion server for a while before I try and upgrade from Snow Leopard Server (if I upgrade). And if I decide to upgrade from Snow Leopard to Lion, I will definitely do a clean install… so many things have changed, I can foresee a lot of configuration problems arising from upgrades.

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.

PDFObject Updated, Moved to GitHub

PDFObject is a JavaScript utility I created in 2008 to embed PDFs in HTML documents. It was modeled on SWFObject.

Three years have passed since PDFObject 1.0 was released, and the browser landscape has changed dramatically. I figured it’s time to dust off PDFObject and see if it can be improved and/or updated for today’s browsers.

I’ve placed a modified edition of PDFObject (version 1.1) on GitHub as an open-source project, allowing anyone to create a fork and make modifications. There are also a handful of example/test HTML files there, too. I could use some help testing the examples in different OS/browser combinations, and with different PDF plugins.

If you have suggestions for improvements, or have a few minutes to test PDFObject examples in your browser/OS of choice, I’d be grateful to hear from you. Just leave a comment below, or send me a message on twitter. So far I’ve only performed some cursory tests in Firefox 3.6 in OSX 10.6.6, but everything seems to work as expected.

https://github.com/pipwerks/PDFObject

I plan to update PDFObject.com soon, too.

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.