Development, as straightforward as it may seem, is never as straightforward as you would like it. Often times, you have an idea in your head. You know where you want to go. You even have the general gist of how to get from point A to point B (if you’re lucky). But the road to get there is never quite as straight as planned. The URL Stats Extractor was definitely one of those experiences.
Chrome Extension
So, I wanted to do a simple chrome extension that showed me the stats for goo.gl based shortened URLs. Mainly for my own want in keeping track of links I’ve created, but also to see how popular other links can be. Plus the main http://goo.gl site is, how can I say it nicely, “lacking” in any modern website features. It’s pretty crappy and you cant do a search nor reorder the links nor even actually delete anything, just hide it. But, it does have a robust API.
Almost forgivable.
I chose a Chrome extension simply because that’s my current browser of choice. It’s robust, the tools are there, and lots of good guides. So why not.
The Idea
Back to the extension. The plan was to create an extension that would find all the shortened URLs on a page and give you back some information about them. I didn’t want to permanently visually disrupt the page since there’s a lot of sensitivity with layouts and structure and widths and heights and so. So I opted to go with a popup. The plan was on a mouseover, the popup would trigger, hit the API, fetch the information, render the data, and show you a nice bubble. Simple. Yes… simple.
Preparation
First things first. I needed to remind myself how to develop an extension. I often find examples to be invaluable to get a kickstart in the right direction.
Of course, I needed the reference for the goo.gl API and do all the signup needed to get developer keys and so on. Pretty easy. Wasn’t even tedious.
I chose to just shove jQuery into the extension so that I can leverage it for my DOM manipulation and that would then allow me to plugin the various popup libraries people write against jQuery, which I was hoping would help move things along. More on that in a bit.
I’ll mention this now even though I ended up retrofitting this after I wrote the extension, but Alex Wolkov has a nice site called Extensionizr (found via this thread). It’s a quickstart way to get a Chrome extension bootstrap setup and configured nicely, especially for future expansion.
Research
Two simple things left to figure out:
- Find out how to embed a mouseover trigger where I need.
- Find a nice popup library.
Mouseover
For the mouseover, I could either find all the anchors and see if the URL is one I can analyze, or I could continuously figure out where the mouse is, find the word under there, and determine if that’s a URL I can show stats for in that spot. The first method will find all the hyperlinks on the page and allow me to analyze them so that I can embed trigger points. But it will miss non hyperlinked URLs that appear in plain text. The second method can be made to work with any kind of text, but the complexity required to figure out the underlying code and to see if an anchor surrounds it all with a suitable link was a bit daunting.
For this project, the first method was fine. Something from stackoverflow pointed me in the right direction. I ended up simply finding all the <a> tags, grabbing the href from them, and analyzing it to see if it’s something the extension understands. If it did, I would embed a class on that anchor, which I would later use as the trigger point for the popup.
I only mention the second method since something like that could be really interesting to use for some other word-on-a-page analysis type thing. I started down the road of dissecting the Rikai-kun extension [sourc code, but it was actually far more complex than I imagined so I put that on the back burner. It was overly complex for what I needed for this extension anyway, and it actually didn’t quite solve the problem nicely, so tucked away for a future project.
Popup
There’s dime a dozen of these out there. So it shouldn’t have been hard to find one that works right? right? Turns out, it’s was a touch difficult.
Akita
I started out by asking for any recommendations and came back with something from Paul Yaun call Akita.
It worked fine with a few tweaks. Namely I had to catch the hover event myself then force open and close the tooltip based on entering and exiting the hover.
$('a.' + MARKER_CLASS).hover( function() { triggerExpand($(this)); }, function() { clearExpand($(this)); } )
I then triggered the popup in triggerExpand:
var speechBubble = $.akita.show({element: $(link)[0], content: output});
After that I went and ajax fetched the dynamic content. Just like planned… except it didn’t work inside of Google+, which is my primary target since the goo.gl shortener is used well in there (well, at least by me). Normally these extensions are usually run in a clean environment that you can control when building, you can manually resolve CSS conflicts and issues by adjusting your styles in interfering classes. But seeing that an extension is an injection, that would mean I’d have to analyze how and what Google+ was doing that was interfering with the extension. As fun as that could be (sarcasm?), I figured these popup libraries are dime a dozen, so time to move to the next one.
Tiptip
Up next was tiptip. Although there hasn’t been a new version since 2010, it seemed stable and light enough. I plugged it into my test env, and it worked fine. I even plugged it into the extension, and it actually worked on Google+, awesome.
But… it didn’t have the ability to fetch dynamic content… which was a deal breaker.
However, in the forum for the plugin, someone provided a patch that allowed the passed in content variable to either be a string or a function. Which meant that I can now pass in dynamic content via a delayed function call. Yay.
I applied the patch and now I could generate my own content on the fly. And I could now place a “fetching…” wait marker as the dynamic content got loaded.
Almost there. The next hurdle was with acquiring the context of the anchor point. Since I hacked on an async call to get data, once that data returned, it needed to know the context of the location, which is something the original plugin didnt really need. The patch didn’t take that into account, but it was easy enough to muck with the source to allow it to pass in the reference to the original element (already in a variable called orig_elem).
Ok, more problems, but more solutions.
That actually worked fine. The tooltip worked and showed the content properly, except that the default location was on bottom. A browse of the config for tiptip and got it showing on the top… then things broke… of course.
The triangle anchoring the content to the anchor element was shoved in the middle of the multilined popup. Ugh. So this popup only works well if the anchor was on the bottom. Great. Nix that. Onto the next.
Bootstrap
Fun. So I figured, hey, maybe a well established system like Bootstrap could be the solution.
Awesome, this should be good. Docs looked fine. Supported dynamic content right off the bat (though wasn’t sure about the proper reference passing). However, it failed the Google+ injection test (seriously Google+, what the hell is in your css/javascript mojo for the site…). It would only do an awkward partial render, obviously the elements of Google+ interfering. I tried both tooltip and popover, and neither went well.
Back to Tiptip
So back to Tiptip. I guess a bottom tooltip will be good enough for now. I was getting tired of over exploring and just wanted to see something working that I could actually use.
As I was writing the notes for this post, it became clear that the location of the anchor bit was actually correct, but the content of the tooltip had expanded too low due to the addition of the new dynamic content, which was a multiline thing as compared to the initial placeholder text (“fetching…”) on which all the calculations were made.
The top of the bubble was calculated based on the initial content, not the new content. Sigh.
All these weird little issues may explain why the async patch pushed to the git repo of tiptip never got integrated because the rendering mechanism just wasn’t compatible with dynamic content.
So the question now was to see if I could make the container re-render upon return of the new content, or if I could fix the calculations to make them truly dynamic, or if I should switch to syncronous ajax calls and return the full data right then and there so that the system can get that back right away.
For now, I’m switching from $jQuery.get to $jQuery.ajax and passing in an “async: false” flag to force the fetch to complete first. I figured the call should be pretty quick so not much lockup to worry about.
That finally did it. All the parts working well enough to warrant actual use. Not pretty, but working at least.
Presenting, the URL Stats Extractor. Complete, with GitHub source.
Todo
Things to do? Import in the pretty graphs from the goo.gl page as part of the display (or just a new display interpretation other than just text). And I need to dynamically detect how much space is available above the tooltip and switch it from top to bottom as needed. Though I think that should really be in the realm of the plugin code so I shouldn’t have to muck with it. That and perhaps exploring the tooltip landscape a bit more and finding something I can use without any modifications.
Also I need to figure out how to detect when new content gets loaded onto a page, like in the case of Google+, so that the extension can rescan for links and do the proper insertions.
Oh, and the extension is using the new 2.0 manifest version, so there’s a Content Security Policy that’s being reported about a base64 image being loaded… except that I don’t use base64 images and I can’t seem to find what base64 image is being loaded and what’s loading it… I’m suspecting it’s somewhere in the jQuery code, but I just cant seem to figure it out. For now it’s not a show stopper, just an annoyance present in the console, so not too bad. But I’d like to see it go away for good.
Eventually, I could probably extend this to include bit.ly, is.gd or whatever other shorteners are out there, but perhaps that could be something anyone willing enough can pull from the git repository and contribute :)
this is one of those projects where you did it quickly enough over a few days to get it functional 80% of the way, not sure if I have the dedicated energy to take it the rest of the 20%.