One of my latest projects is PlaceWidget, a service that lets you embed a Foursquare widget into any website (now with a WordPress plugin and a Facebook App). When I kicked off development of PlaceWidget, I had three goals in mind:
- Make it as easy as possible to configure
- Make it as easy as possible to add to a site
- Make it look exactly the same regardless of the user’s website
Task one was, I believe, achieved with the simple three-step configuration process on the PlaceWidget website, but task two and three seemed to be at odds with each other. The best way to isolate the widget from the user’s website CSS and Javascript is to have it be embedded via an iFrame, but this presented a problem since the widget content is not a fixed height. I needed a way to resize the iFrame on the user’s site based on the content inside of it, but the browser security model gets in the way of this – Javascript loaded from different domains cannot communicate with one another. Facebook gets around this problem my having the user upload a cross domain receiver file to their site, but this conflicts with my task two (I wanted adding the widget to involve adding just a single line of code where the user wanted the widget to appear).
What I came up with was a solution to trick the browser into letting Javascript inside the iFrame communicate with Javascript on the user’s site, so the iFrame could report it’s loaded height, and the parent page could resize the iFrame to fit.
Instead of the user adding an iFrame to their page, I have the user embed a Javascript link tag pointing to a location on http://placewidget.com. Inside this Javascipt file, which is dynamically created on request, is the content that would have been inside the iFrame as a string value. The rest of the code in this JS file then creates an iFrame on the user’s page without an HREF, grabs a handle to the iFrame’s document and builds the content inside. Once that’s complete, the Javascript then reads the height of the content and resizes the iFrame – it can do this because an iFrame with no HREF is deemed as being on the same domain as the parent page.
There are a few caveats to this approach:
- There are three different ways to get a handle to the iFrame’s document object depending on what browser you’re in
- You have to open and close the iFrame’s document once before you can write to it (see below)
- If you include a style tag in your dynamically generated content, you must insert a ‘standard’ element like a BR or a DIV before it, otherwise Internet Explorer will not apply the styles
The code below shows how you can create an iFrame and get a valid, writable handle to its document object.
-
// Create an iFrame and add it to the document
-
// You’d likely place it somewhere other than the bottom of the body
-
var iframe = document.createElement(‘iframe’);
-
document.body.appendChild(iframe);
-
-
// Find the iFrame’s document object depending on browser
-
iframe.doc = null;
-
if(iframe.contentDocument)
-
// Firefox, Opera
-
iframe.doc = iframe.contentDocument;
-
else if(iframe.contentWindow)
-
// Internet Explorer
-
iframe.doc = iframe.contentWindow.document;
-
else if(iframe.document)
-
// Safari, maybe others?
-
iframe.doc = iframe.document;
-
-
if(iframe.doc == null)
-
// Something sucky has happened and you can’t see the document
-
-
// Open and close the document to make it valid and writable
-
iframe.doc.open();
-
iframe.doc.close();
-
-
// Now you can add content as normal
-
// Make sure you create your elements in the context of the iFrame
-
var div = iframe.doc.createElement(‘div’);
-
div.innerText = "This is pretty neat";
-
-
// Body is implicit, but you should probably create it properly anyway
-
iframe.doc.body.appendChild(div);