Playing with Gmail API and Polymer

In this tutorial we will build a simple app using Polymer and Gmail API.

Live demoSource code

Polymer #

Polymer lets you extend your HTML with your own components.
Using it is as simple as:

<!-- Importing an element... -->
<link rel="import" href="google-map.html">

<!-- ...and using it -->
<google-map lat="37.790" long="-122.390"></google-map>

Polymer extends standard Web Components with useful stuff and comes with a collection (among others) of predefined components for Google APIs.

Gmail API #

The new Gmail API let’s you do a few simple things like read messages or send them. It’s not a full-fledged replacement for IMAP and we can easily use it with a bit of JavaScript.

What are we building? #

gmail_conversions_screenshot.png
User replies to online leads via Gmail and labels them as won or lost. We will ask user for his label names, grab the data from Gmail and calculate his conversion rates. Nothing fancy.

Step 0: Grab some code from Github #

You can clone step-0 branch from Github. You will find there predefined styles for your project (which I won’t discuss in this tutorial) and basic project structure.

Step 1: Google authentication #

This is how easy it is to implement Google OAuth2 (I assume you know how to, and already did create a new app in Google Developers Console):

Go to the project directory and install google-signin component with Bower:

bower install GoogleWebComponents/google-signin --save

And import component in <head>:

<link rel="import" href="bower_components/google-signin/google-signin.html">

You can already use your new component in browsers that support Web Components, but Polymer goes step further and provides polyfills for other browsers. Bower automatically installed all of the required dependancies. You can load platform.js with mentioned polyfills.

Your <head> should look something like this:

<head>

  (...)

  <!-- Polymer polyfills -->
  <script src="bower_components/platform/platform.js"></script>

  <!-- Import components -->
  <link rel="import" href="bower_components/google-signin/google-signin.html">

</head>

Step 2: Use your component #

Let’s place our component (along with dumb page title) inside #one div:

<h1>Wanna know your email conversion rates?</h1>
<google-signin class="google-button"
      labelSignin="Sign in with Google"
      clientId="YOUR_CLIENT_ID"
      scopes="https://www.googleapis.com/auth/gmail.readonly"></google-signin>

Pages are shown on hash change, so to make #one visible you will have to add the following inside js/app.js. We also add a reference to all of the other pages to make our life a little easier later. Adding a class of ‘active’ to the body will hide the preloader:

// stores page hashes
var pages = {
  signin: "one",
  labelOne: "two",
  labelTwo: "three",
  app: "four"
}

// hide preloader, goto signin page
document.querySelector('body').classList.add('active')
window.location.hash = pages.signin;

Run a python -m SimpleHTTPServer from the console and go to http://localhost:8000. You should see a fully functional Google sign in button.

Step 3: Collect label names #

Let’s now react on user signing in and ask him about label names. We will start by adding the following html:

<!-- ask about lost label -->
<div id="two" class="page">
  <h1>How do you label your <span class="text-red">lost</span> leads?</h1>
  <input type="text" id="lost" placeholder='e.g. "leads-lost"'>
</div>

 <!-- ask about won label -->
 <div id="three" class="page">
   <h1>How do you label your <span class="text-green">won</span> leads?</h1>
   <input type="text" id="won" placeholder='e.g. "leads-won"'>
 </div>

Our google-signin component gives us a handy event - google-signin-success. Let’s add an event listener in app.js:

// On sign in
addEventListener('google-signin-success', function(e) {
})

We will keep our labels in localStorage so lets first make sure they aren’t already there. If they are, redirect user directly to app page. If not, go to first label page and focus on the input field:

var inputLost = document.getElementById('lost');
var inputWon  = document.getElementById('won');

(...)

// On sign in
addEventListener('google-signin-success', function(e) {
    var lostLabel = localStorage.getItem('lost-label');
    var wonLabel = localStorage.getItem('won-label');

  if (wonLabel && lostLabel) {
    window.location.hash = pages.app;
  } else {
    window.location.hash = pages.labelOne;
    inputLost.focus();
  }
})

Now all we have to do is save labels when enter key is pressed and move the screens forward, like so:

// Listen for a keydown event on label input fields.
// When enter is pressed, save label name in localStorage
// and move to the next screen. Focus on next input field.
// After collecting label names move to app page.
inputLost.addEventListener('keydown', function(e) {
  if (e.keyCode === 13) {
    localStorage.setItem('lost-label', e.target.value)
    window.location.hash = pages.labelTwo; // goto second label page
    inputWon.focus() // set focus on second label input
  };
})

inputWon.addEventListener('keydown', function(e) {
  if (e.keyCode === 13) {
    localStorage.setItem('won-label', e.target.value)
    window.location.hash = pages.app;
  };
})

Step 4: The Polymer Element #

Let’s now create our app. Start by creating a new file gmail-conversions.html inside components directory. This will be our new Polymer component. Add this inside:

<polymer-element name="gmail-conversions" 
    attributes="won, lost, wonLabel, lostLabel">
  <template>
    <style>
      .result {
        font-size: 1.4rem;
        line-height: 1.5;
        color: #5E5E5E;
      }

      .text-large {
        font-size: 2.4rem;
        color: #373737;
      }

      .text-medium {
        font-size: 1.8rem;
        color: #373737;
      }

      .summary div:first-child { border-right: 1px solid #f1f1f1; }
      .summary div {
        display: inline-block;
        margin-top: 30px;
        padding: 20px;
        border-top: 1px solid #f1f1f1;
      }
      .summary div span { 
        display: block; 
        color: #7ED321;
        text-transform: uppercase;
        font-size: .6rem;
        font-weight: 900;
      }
      .summary div:nth-child(2) span { color: #E94A2F;}
    </style>
    <div class="result">
      You win <span class="text-large">{{conversion_rate}}%</span> of your leads on average.
    </div>
    <div class="result">
      That's based on <span class="text-medium">{{won + lost}}</span> total leads in your inbox.
    </div>
    <div class="summary">
      <div class="left">
        <span>{{lostLabel}}</span>
        {{lost}}
      </div><div class="right">
        <span>{{wonLabel}}</span>
        {{won}}
      </div>
    </div>
  </template>
  <script>
    Polymer({
      // defaults:
      conversion_rate: 0,
      won: 0,
      lost: 0,
      wonLabel: 'won',
      lostLabel: 'lost'
    })
  </script>
</polymer-element>

As you can see, creating polymer elements is fairly straight forward - you define a handlebars-like template and call Polymer with the data. All there’s to do now is to import your new element inside index.html and use it:

<head>
(...)

  <link rel="import" href="components/gmail-conversions.html">
</head>

(...)

<div id="four" class="page">
  <gmail-conversions></gmail-conversions>
</div>

If you go to your browser you should see the template you just created filled with the default values. Filling it with our own data would be as easy as selecting the DOM node and setting its attributes to the new values. Go ahead, try that in developer console:

var app = document.querySelector('gmail-conversions');
app.won = 100;

You will see that your data is automatically updated, except the conversion rate. For that, we will have to use a computed property. Go back to gmail-conversions.html and update Polymer() call:

  <script>
    Polymer({
      // defaults:
      get conversion_rate() {
        var rate = this.won * 100/ (this.won + this.lost);
        rate = Math.round(rate * 100)/100;
        return rate ? rate : 0 // in case of NaN
      },
      won: 0,
      lost: 0,
      wonLabel: 'won',
      lostLabel: 'lost'
    })
  </script>

Now when you update won or lost, your computed property conversion_rate will also stay up to date.

Step 5: The Gmail API #

Up until now, we didn’t even touch Gmail API. Let’s update our polymer element with data collected from user’s inbox. We want to fire our API calls when we have all the necessary label names, so add the following in app.js:

var lostCount;
var wonCount;

(...)

// When on App page get Gmail data
// When on App page get Gmail data
var getGmailData = function() {

    // show page preloader 
  document.body.classList.remove('active');

    gapi.client.load('gmail', 'v1', function(e) {
        var gmail = gapi.client.gmail;

        // get lost leads
        var requestLostEmails = gmail.users.threads.list({
            userId: 'me',
            q: 'label:' + localStorage.getItem('lost-label')
        })

        requestLostEmails.execute(function(response) {
            // set counter
            lostCount = response.threads ? response.threads : 0;

            // if other request is already finished
            // then update view
            if (wonCount !== undefined) {
                updateView();
            };
        })

        // get won leads
        var requestWonEmails = gmail.users.threads.list({
            userId: 'me',
            q: 'label:' + localStorage.getItem('won-label')
        })

        requestWonEmails.execute(function(response) {
            // set counter
            wonCount = response.threads ? response.threads : 0;

            if (lostCount !== undefined) {
                updateView();
            };
        })

    })
}

Remember to call getGmailData in the same place you change document hash to pages.app. You will notice that we call updateView() after all the data from Gmail is collected. Let’s write that function now:

// All the data is ready, update app view
var updateView = function() {
    // get app component
    var app = document.querySelector('gmail-conversions');

    // update component view
    app.won = wonCount;
    app.lost = lostCount;
    app.wonLabel = localStorage.getItem('won-label');
    app.lostLabel = localStorage.getItem('lost-label');

    // hide preloader
    document.body.classList.add('active');
}

And here you go. Your first simple app. You can go to Github to download full source code or see the live demo here.

What kind of ideas do you have for using Gmail APIs and/or Polymer? I’m especially excited about the latter one - Web Components are really handy and feel very intuitive.

 
59
Kudos
 
59
Kudos