Getting tired of all the EVO/Android/iPhone love Jake’s been spewing lately? How about something a bit more dry and developery? As promised in a prior post, here’s a more technical writeup of how I built the WebCenter sharing bookmarklet. I was hesitant to write about this because it’s not like writing a bookmarklet is new and interesting… there are tons of tutorials on the web after all. But as with everything, writing about it will make me appreciate the work I put into it more, I suppose.
Building a bookmarklet is pretty simple. The basic idea is that a user clicks on the bookmark to execute some javascript to do what you want. Creating a sharing bookmarklet can be a bit of a challenge depending on the implementation path you take. You can take a look at the various approaches by testing out a service called Shareaholic, a service that aggregates a bunch of sharing tools together as a Chrome extension or Firefox addon. The simplest strategy for a sharing bookmarklet is to launch a new window/tab like Facebook or Digg, but personally, I’m not a fan of launching new windows so I decided to take a different approach. Other sharing bookmarklets are “inserted” inside of the page you’re currently browsing. Google Reader‘s and Posterous‘ bookmarklets are good examples of this. There are a few simple tricks necessary in order to build a bookmarklet like this. Let’s use the following diagram for reference:
There are a few pieces to that make this bookmarklet possible:
- Javascript to execute on the foreign page (bookmarklet.js) — this is injected by the bookmarklet itself through a script tag insertion.
- Content iframe originating from the domain you’re sharing to (bookmarklet.html, #3 in the diagram above) — this is necessary because our bookmarklet needs to interact with a REST API on the domain where the user will be sharing to (in this case, the host/domain where WebCenter).
- Proxy iframe (#4 in the diagram above) — this iframe is hidden off of the screen and is used simply to trigger events that happen on the parent. I’ll go into more detail as to why this is needed later.
Here’s the flow:
- User triggers the bookmarklet (an inline javascript in an anchor link that a user adds to their bookmarks bar). Upon execution, a new script tag is inserted into the foreign page and does the following:
- Inserts a stylesheet to the page defining the style of the dialog box we’re going to create (#2).
- Defines a div container for us to insert our DOM elements into which makes it easier for cleanup when after the bookmarklet is used.
- Inserts the content iframe (#3) which points to bookmarklet.html on the WebCenter host.
- Inserts the proxy iframe (#4) which points to proxy_frame.html on the WebCenter host.
- At this point, the sharing dialog window has been initialized and the iframes have been rendered. Our bookmarklet communicates with the WebCenter REST API directly (#3). Before presenting the user with the “publisher” (sharing textarea), we need to determine if the user can access the REST API. Since our bookmarklet app is hosted on the same domain as the WebCenter app, we don’t can trigger the SSO system to authenticate the user if it’s needed (no fancy OAuth needed — good thing because WebCenter doesn’t support OAuth yet). Upon a successful SSO authentication, the iframe in #3 is reloaded and the REST API is called in order to paint the rest of the form.
- The only interaction left is when the user clicks the “Share” button. The Share button is purposefully inserted on the host page (not the iframe in #3). The reason for this is the share button will need to be able to trigger the dialog to close and also clean up all of the HTML that was inserted onto the page. You can’t do that if the button is inside the #3 iframe. However, this causes us another issue. How do we trigger the form submission in iFrame #3? Because of cross domain rules, you can’t trigger an event in another frame that’s from a separate domain. Enter iFrame #4, the proxy_frame.html. Before I explain how this works, go read Michael Mahemoff‘s excellent article about Cross-Domain Communication with iFrames — the technique I use is the “Window Size Monitoring” hack. The hack works like this…
- The main page (i.e., main parent) creates two iframes (#3 and #4), both of which reside on the same domain, but not necessarily the same domain as its parent.
- The first iframe contains the actual iframe you want the user to interact with (#3). The second iframe is the “proxy” iframe (#4). The proxy iframe’s purpose is to respond to events that the main parent triggers.
- When these trigger is executed, the proxy iframe will execute some javascript in it’s sibling iframe (#3). But how does the main parent trigger an event on the proxy iframe which resides on a different domain, you ask? iframes are like little windows within a larger window. They can be resized. One of the events a window can respond to is a “resize” event. So, the trick is to hide this proxy iframe out of view from the user (position:absolute;top:-9999px;width:0;height:0). In the proxy_frame.html page, add an event listener on the window that listens for “resize” events:
So, that’s the gist of it. Questions… ask below.
Thanks for the post. Out of curiosity, how are you managing the closing of the bookmarklet. I have two external buttons adjacent to the content iframe: save and close. Close is easy because it can just remove the bookmarklet container div from the dom and nothing else needs to happen. But If I want to go ahead and process the action, which in this case is a form post within the content iframe, I have to trigger the resize of the proxy which sends the action to the content iFrame, and then still follow up and remove the container. If I immediately call remove() on the container, the iframe action never happens because it was closed too fast.
Functionally, when we added the ability to post without the link of the page you're current on, we also had to add a confirmation message with a close function. Initially, once you clicked Share, the bookmarklet window disappeared on its own.
So, I guess the guys can answer your question, even though we went away from that approach.
Hoping Rich will see this and add relevant technical details.
We opted for the first option you mentioned. Because our bookmarklet can be injected to any site on any domain, there's no way to set the proxy on the parent. So, we've opted for the simple “close” button approach on the parent and detached any events from the submit button… this forces the user to “close” the bookmarklet after they've submitted. This seems to be the popular choice.
Thanks for the replies. Ideally, I'd like to have the bookmarklet window close after the action has completed, without requiring a second click after the action has completed. I've seen it done but haven't got a handle on how yet. Cheers.
The way to do that is to put your submit button on the parent. When the submit button is pressed, send a message to the child frame to submit the content (through the proxy), then you can close the window after the callback has returned.