widgets for third-party sites

Mon, 04 Jul 2011 19:15:52 +0000

For the past couple weeks or so I've been driving myself crazy researching the ways people are developing widgets to run on other people's sites. I've been writing and rewriting the same piece of JavaScript on the daily as I find new information or start to doubt the information I'd decided previously was The Right Way. Though a big part of the problem has been the 90 gazillion blog posts and presentations tackling the subject in whole or in part, I thought I would write down what I came up with. Mostly to get some feedback on it, but also just to kind of collect that research and its outcome.

General structure

This was the part that I found the least information on, and I don't really get why that is. If you have this piece of code running on someone else's site and you don't have concerns about where the HTML or the CSS or the data is going to come from, my understanding of modern terminology leads me to believe that what you have, sir or madam, is a plugin. Anything bigger than a plugin needs other stuff to support it, and unless you want to manage all that stuff as long-ass strings in JavaScript, I'd assume you'd keep it in a different file.

However, your buddy with the third-party site probably did not ask you to add as many extra requests as possible to the loading of the page housing your widget. It seems like minimizing the number of times your widget goes and gets other files or objects from your own servers would be a primary goal. The way I've decided to handle this is to err on the side of efficient requests where possible and to make a bunch of requests where I don't see a good way around it. Specifically, the static data - HTML and CSS - lives in its own files on a node server, but when the third-party site requests the widget code, the contents of those files get added to the JS as variables. Data has to be collected separately, but I'll get to that..

The HTMLs

I've seen a lot of people who seem to be suggesting that when your widget calls for data, it should get it already wrapped up in its HTML, which I hate. It's just a little more processing to run pure data through a template, and it allows the consumer of the widget the option of easily switching out the markup if you expose a config property that provides a path to a customized template. Because creating a view model on top of the pure data seemed like a giant pain in the ass, I used John Resig's Micro Templating. It's small and efficient, bakes in caching of the templates, and allows enough logical flexibility to cover pretty much any need.

Configs

The widget I'm working on needs a good deal of setup info. Not only does it need data corresponding to anywhere from 1-n different objects, it needs to be able to find the places where those objects are rendered and be able to choose among a few options for how the widget itself will get rendered. It also needs normal widget stuff like an API key and all that. This adds up to one big-ass config. Asking the consumer to append all that data to the querystring that loads the widget code is possible, but seems awfully fragile to me. Better that they just keep it in a big object literal and the widget goes and finds it once it's loaded. However, because all the config data isn't part of the additional request, we have to deal with complicated cross-domain messaging issues when we do get the data, instead of the data being pre-loaded like the HTML and CSS.

Cross-domain stuff

By far this seems like the piece of the equation with the most information and the least clear answer. There are a few simple, modern ways around the cross-domain problem, but they only work with newer browsers, and IE, in a move surprising 100% of no one, has decided to implement their own version of the standard in their newer offerings. I actually made a flow chart trying to describe the choices to be made in picking a solution stack (and a stack is what you need, unless you somehow can manage to only support WebKit) that seemed to get some lulz, so I assume it's close enough to accurate to at least be funny.

Because I need to send and receive a decent-sized chunk of data, but had the option to break it up into smaller requests/responses, I ultimately decided to use CORS and JSONP where CORS was unsupported. Previously, I'd ultimately decided to try and send/receive all the data for however many objects the widget needed to be rendered for at once, but I was skeptical about fitting all that in the URL of an iframe. So now I make one request per object and that seems like the best solution for making good use of resources and maintaining my sanity. I looked into easyXDM, but it seemed to me in reading the docs that I was going to need to manually manage all the same fallbacks, just accessed through a nice clean abstraction. I may have misunderstood that, but once I figured out how I could accomplish my goals without reading the documentation necessary to understand how that tool works, I decided to just try to get it done DIY-style.

External libraries

I had a fun debate with some friends on twitter about the merits of using jQuery vs. using a microlibrary or something completely hand-rolled. My point about jQuery was that there are decent odds that the third party site would be using it or that the end user would have it cached. I still think that's true, but I needed so damned little of it that it seemed better to just write a couple of custom functions to do things like get elements from selectors. The totally efficient thing to do would be to make the signatures of those custom functions match jQuery or some other common library so that I could easily switch between them, but the code has to get loaded either way and now I have no external dependencies. Although I did copy and paste other code, like Micro Templating, into my widget code. That feels icky, but also efficient.

Lots of thinking for very little processing

I still feel weird that so much frustration went into this project, because it actually does so little. The external site calls the node.js server, which responds with the widget code and the static files embedded within that. It iterates through the objects defined in the config, makes its request to get the data for each, runs it through a template, and inserts it into container defined in the config by either a class name or an ID. The init() portion of the code may be the smallest piece. Did I do that right? I have my doubts, but I'm also not sure what I'd change. If you have comments, I'd love to hear 'em.