Pothibo

Twitter and Facebook buttons with Turbolinks

Personally, I think that turbolinks is a transparent to any project. I never think about it, it just sits there and does its job. But there are a few situation where turbolinks doesn't make things simpler. One of those is social media widgets. They inject themselves on DOMContentReady and expect to not change until you load a new page.

However, turbolinks doesn't flush the page: it flushes the body's content. That means two things.

  1. The global JavaScript object are never reset and,
  2. DOMContentReady is only fired once.

When the social buttons are injected into your page, it inserts HTML in the page (usually iframes) and also store informations in global JS objects.

So what happens, usually, is that when you load a page directly, the widgets are loaded as expected. But if you access the same page from elsewhere in your app, the widgets don't update themselves and there are many warnings in the web console. Then how did I make those widgets work?

I started using ObserveJS to handle my widgets. Instead of hacking code that might change (third party widgets), I realized that I could just wrap those inside their respective classes and look at the current state of the page. If the global JS object doesn't exist, I presume it's new and inject the widget in the page as recommended by the vendors.

If their global states are already defined, I simply call the widget's update function and let it update itself. Here's how I did it.

Facebook

Following Facebook's tutorial, I knew I would have to include an HTML element and a script element on the page. Adding a tag for ObserveJS to find the HTML element, I would then be able to decide if I inject the script or not.

When you reach a post on my blog list this one, I have this ERB snippet:

# URL is the absolute path for the post i'm rendering <div as='FB' class="fb-share-button" data-href="<%= url %>" data-type="button_count"></div>

Now, when this piece of code will make it to your browser, ObserveJS will notice the as attribute and will try to instantiate such a class.

facebook.js.coffee
ObserveJS.bind 'FB', class root: document.createElement('div') @::root.id = 'fb-root' loaded: => if !document.body.contains(@root) document.body.appendChild(@root) if FB? FB.XFBML.parse() else @initialize() initialize: => js = document.createElement('script') script = document.getElementsByTagName('script')[0] js = document.createElement('script') js.id = 'facebook-jssdk' js.src = "//connect.facebook.net/en_US/all.js#xfbml=1&appId=YOUR_APP_ID" script.parentNode.insertBefore(js, script)

Facebook, for some reason, was the hardest to get right. It requires so many things and the documentation didn't help solving those issues. With this being said, I finally implemented a version of it that would work. First, I am creating my own root element. If I don't, Facebook will create it for me but then I'll have to track it down and store it.

Because when you change the page, that root element will be flushed, but the Facebook widget will still hold a reference to that object. And then the button won't appear anymore. Bottom line, I created my own root element and kept a reference to it.

When the Facebook widget is loaded on the page, I look if the current body contains the root, if it doesn't I just append it to make Facebook happy.

After that, it's just a matter of checking if the global object that Facebook set exists. If it does, I update the widget. If it doesn't I inject the script that Facebook wants.

Twitter

Twitter is much easier. My wrapper class is just a subset of what it was for Facebook. Twitter also needs an HTML element and a script element so the logic is pretty similar in that regard.

# the url is the absolute url for the post I'm rendering <a href="https://twitter.com/share" as='Twitter' class="twitter-share-button" data-via="pothibo" data-url='<%= url %>'>Tweet</a>

And the coffeescript object that will be loaded with ObserveJS resolve that element:

twitter.js.coffee
ObserveJS.bind 'Twitter', class loaded: => if twttr? twttr.widgets.load() else @initialize() initialize: => `!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');`

Since twitter's widget was easy to implement, I didn't feel the need to convert the JavaScript function that it wants me to run. Win for me!

I don't often talk about how easy things are because usually, they aren't. But in this case, I believe that using ObserveJS makes it easier for you. ~15 lines of code to have your widget working with Turbolinks.

Happy coding!

Get more ideas like this to your inbox

You will never receive spam, ever.