read

Intro

In the previous article, we made some social media counters in vanilla JavaScript. They looked and worked a little like this:

In this article, we'll look at how to:

  • Display Share/Tweet Counts
  • Animate an Incrementing Count

Again, all in vanilla JavaScript so it can work without additional library support.

Setup

Let's continue where we left off. I've added a <span>0</span> counter number to each button with a corresponding class name to target later.

<section class="social-icons">  
  <div class="social-icon-group">
    <a href="//www.facebook.com/sharer/sharer.php"
       onClick="callFacebookAPI(); return false;">
      <i class="fa fa-facebook"></i>
    </a>
    <span class="facebook-count">0</span>
  </div>
  <div class="social-icon-group">
    <a href="twitter.com/share" rel="canonical"
       onClick="callTwitterAPI(); return false;">
      <i class="fa fa-twitter"></i>
    </a>
    <span class="twitter-count">0</span>
  </div>
</section>  

Let's add some style and spacing to the new digit as well.

.social-icon-group span {
  margin-left: 5px;
  color: grey;
  font-size: 0.8em;
}

Beautiful. Not really, but it's functional. You be the artist.

Twitter & Facebook APIs.

Different social buttons have different paths to access their data. We'll continue with Twitter & Facebook for now.

Twitter API: http://cdn.api.twitter.com/1/urls/count.json?url=http://www.twitter.com

Clicking on the above link demonstrates that 'http://www.twitter.com' has been tweeted on Twitter over 57 thousand times. Say that 10 times fast.

{
  count: 57548,
  url: "http://www.twitter.com/"
}

Facebook Graph: http://graph.facebook.com/?id=http://www.facebook.com

Clicking on the above link shows that 'http://www.facebook.com' has been shared on Facebook over 37 million times.

{
  id: "http://www.facebook.com",
  shares: 37680762,
  comments: 5177
}

Fetching Data

Time to grab the JSON.

function getJSON(url) {  
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest();
    xhr.open('get', url, true);
    xhr.responseType = 'json';
    xhr.onload = function() {
      var status = xhr.status;
      if (status == 200) {
        resolve(xhr.response);
      } else {
        reject(status);
      }
    };
    xhr.send();
  });
};

Uh oh, this doesn't work. Fetching data in this way can lead to a CORS Access-Control-Origin warning. Your browser is trying to protect you against possible cross-site scripting attacks. Thanks but no thanks, browser.

There is, however, a clever hack to get around this.

function getJSON(url, success) {

  var ud = '_' + new Date,
    script = document.createElement('script'),
    head = document.getElementsByTagName('head')[0]
      || document.documentElement;

  window[ud] = function (data) {
    head.removeChild(script);
    success && success(data);
  };

  script.src = url.replace('callback=?', 'callback=' + ud);
  head.appendChild(script);
}

Essentially what's happening here is the following:

  1. you make a JSON request at the target url
  2. if successful, wrap the JSON in a script tag
  3. append the JSON script file to the head

This method could also be used to make your own XSS attacks. But you wouldn't/shouldn't do that.

Alternatively, you could just use the most popular solution: jQuery's $.getJSON. But sadly, I'm dealing with my own self-set vanilla JS only restrictions here.

Fetch Counts

In your script file, call the APIs and collect the values. Make sure everything is working.

/* get twitter count */
getJSON("http://cdn.api.twitter.com/1/urls/count.json?url=" + url + "&callback=?", function (json) {  
  console.log('twitter', json.count);
});

/* get facebook shares */
getJSON("http://graph.facebook.com/?id=http://" + url + "&callback=?", function (json) {  
  console.log('facebook', json.shares);
});

In the browser console, you should see that the data is loading.

facebook 37680762

twitter 57548

This is by no means an optimal solution. It's not a good idea to rely on multiple different JSON requests every time a page is loaded. It's better to handle these requests on the server and update them over regular intervals, passing the end results to the client. We'll look at how to do that with Meteor in the next article.

Add Counts to the DOM

It's time to target our <span class="facebook-count">0</span> using the class selector, and replace the 0 with our received JSON data.

 if (json.shares) {
    return document.getElementsByClassName('facebook-count')[0].innerHTML = json.shares;
  }

We're doing a few things here.

  1. if(json.shares) { ... } helps prevent a possible error returning undefined. This way our count would stay at 0.
  2. Find the first instance of the selector 'fbCount'
  3. Make the information between the <span class="fbCount"></span> become the new count.

The result should look like the following example.

Increment the Values

We can increment the values quite simply. We know all the necessary values.

Start: 0

End: the received count value

However, if you add values one by one, your count may never finish. It's better to divide the count into 10 or so parts then increment between those values.

For example, 150 counts might look like this:

    0ms   : 0
    150ms : 15
    300ms : 30
    // etc.
    1000ms : 150

Each step delivering an additional 10% of the counts.

In order to run multiple setInterval statements, and then clear them both when finished, we need to give each it's own namespace. You can use the same provided selector name.

  var incrementer = {};
  incrementer.facebook = setInterval(function () {...}, 100);
  incrementer.twitter = setInterval(function () {...}, 100);

See a solution below.

function incrementCount(end, name) {  
  var times = 0;
  var incrementer = {};
  incrementer[name] = setInterval(function() {
    ++times;
    var newValue = Math.ceil(end * (times / 10));
    document.getElementsByClassName(name + '-count')[0].innerHTML = newValue;
    if (times === 10) {
      document.getElementsByClassName(name + '-count')[0].innerHTML = end;
      window.clearInterval(incrementer[name]);
    }
  }, 100);
}

Note that the final innerHTML call acts as an extra count check.

Now call the 'incrementer' function when the JSON data arrives.

/* get twitter count */
getJSON("http://cdn.api.twitter.com/1/urls/count.json?url=" + url + "&callback=?", function(json) {  
  return incrementCount(json.count, 'twitter');
});

/* get facebook shares */
getJSON("http://graph.facebook.com/?id=http://" + url + "&callback=?", function(json) {  
  return incrementCount(json.shares, 'facebook');
});

That's all it takes. Checkout a working example below.

Conclusion

Working with Vanilla JavaScript can at times be a bit hacky, but it can also be a lot of fun. Occasionally it forces you to get creative, as in the CORS workaround.

You'll notice that something as simple as an incrementing social media button was a lot of work. There should be an easier way to add this to a page. The solution is web components, or the different ways frameworks such as Meteor, Angular or React can create their own components. In the next part we'll look at how to turn our buttons into configurable components that can be shared across package managers. Pay it forward.

Blog Logo

Shawn McKay

Published

Image

ShMcK

JavaScript Web Dev

Back Home