This codelab uses Polymer to build a mobile-friendly, Web Component-based Google Drive client. Web Components are a new set of web platform features that enable developers to build applications in a declarative, composable way. They build on new web platform technologies like Custom Elements and Shadow DOM to allow you to create encapsulated, reusable elements.

Polymer allows you to use Web Components today, with a layer of sugaring that offers features like data-binding, layout and expressions. You’ll get a chance to play with some pre-made Google Web Components and build something fun and functional in under 30 minutes.

Here’s what the final product will look like:

What you’ll learn

What you’ll need

How will you use this tutorial?

Read it through onlyRead it and complete the exercises

What is your current level of experience working with Polymer?

NoviceIntermediateProficient

Next up

Let’s get setup to build the app!

While you’re free to use your own editor, this codelab will probably be easier to follow if you use the Chrome Dev Editor, a Chrome app IDE. If you don't have it installed yet, you can install it from the Chrome Web Store.

Install Chrome Dev Editor

After installing the Chrome Dev Editor, let’s use it to clone a Git repo.

Start the Chrome Dev Editor

→ Open the Chrome App Launcher:  

→ In the Chrome App Launcher window, click on the Chrome Dev Editor icon:

→ In Chrome Dev Editor, click the “hamburger menu” , then select Git Clone…

→ Enter this URL for the repo associated with this codelab: https://github.com/googlecodelabs/polymer-drive-client.git

→ Then click the Clone button.

Note:  If this is your first Chrome Dev Editor project, you may be asked to choose a folder to save projects to. You can generally accept the default and proceed or create and select a new directory.

Chrome Dev Editor uses Bower to download and install a list of dependencies (including Polymer) into the bower_components/ folder.

After cloning this repo, your project’s directory structure should look like this:

The steps folder contains one folder for each step of this codelab, with contents corresponding to the desired end state of each step. The steps folders are there for your reference. We’ll be doing all our coding work in the main project.

Right click the bower.json file and select Bower Install, to install the required dependencies.

You’ll see a little widget like this at the bottom of the screen, which tells you that bower is working:. When this is done, you’re ready to preview your app.

Preview the app

Now select index.html and click the  button in the top toolbar to run the app. Chrome Dev Editor fires up a web server and navigates to this page. This is a great way to preview changes to the app as you make them.

The title shown in this initial version of our app refers to the fact that we used the Polymer Starter Kit to generate the scaffolding and initial structure of this app.

Summary

In this step, you learned how to:

Next up

At this point our app doesn’t do much, so let’s create our first element: <drive-app>.

In this step you'll create a Polymer element to contain the UI and logic for uploading files to Google Drive.

Create a new element

→ Right-click the top level project label in the Chrome Dev Editor sidebar and choose New File from the dropdown menu.

→ Call your new file drive-app.html.

→ Inside this file, paste in the following boilerplate for starting off your element:

<link rel="import" href="bower_components/polymer/polymer.html">
<dom-module id="drive-app">
  <style></style>
  <template>

    I'm a shiny new element.

  </template>
  <script>
    Polymer({ is: 'drive-app' });
  </script>
</dom-module>

This code defines the <drive-app> element as a tag that your app can use. The import lets you use Polymer's sugar while you’re authoring. Chrome Dev Editor automatically saves your changes to the element after you paste the code in.

Import your element

To use your new <drive-app> element, you need to:

  1. Add an HTML Import to load it into index.html.
  2. Declare an instance of the element on the page.

→ Click on index.html to begin editing that file.

→ In the <head> section of index.html, add the following line, right after the import of elements.html:

  <link rel="import" href="drive-app.html">

→ In the <body> section, replace all existing content inside the <section data-route=”home”> element with an instance of your new <drive-app> element:

               <drive-app id="drive-app"></drive-app>

→ Find the <title> element and change its contents from Polymer Starter Kit to Polymer Drive Client. Make the same change to the <div class="app-name"> element.

→ Find the application sub-title and change it from The future of the web today to  Polymer-powered Google Drive client.

→ Find and remove the menus and sections for users, user-info, and contact (since we won’t be using those options in this app). For example, for users, remove these elements:

<a data-route="users" href="/users">
  <iron-icon icon="info"></iron-icon>
  <span>Users</span>
</a>
...
<section data-route="users">
  <paper-material elevation="1">
     <h2 class="paper-font-display2">Users</h2>
     <p>This is the users section</p>
     <a href="/users/Rob">Rob</a>
  </paper-material>
</section>

Remove the corresponding elements for user-info and contact.

Preview the app

When you select index.html and click the  button, you should see something like the following in your browser:

Excellent. Your first Polymer element is working, and you can begin fleshing it out!

Next up

Next, let's scaffold out what the app looks like for a signed-in user with everything they need to upload files.

Users must be signed in to their Google account before they can upload files to Google Drive. This means that the app needs to show an authentication widget before the user is signed in and another UI after sign-in.

Create a place for the signed-in UI

→ In the drive-app.html file, replace the “I’m a shiny new element” text with the following <div> element to contain the signed-in UI.

    <div id="loggedin">
    
    </div>

Create a file selection area

The "loggedin" div needs an area where users can choose files to upload. The user can drag and drop files if the app is on a desktop computer. When the app is running on a mobile device, the user must be able to tap a button to select files.

→ Create a drop zone with a file selection button inside of the "loggedin" <div>:

      <div id="dropzone" on-tap="tapSelect"
        on-dragover="handleDragOver"
        on-drop="handleFileSelect">
          Drag files to upload
        <input type="file" id="files" name="files[]" 
          on-change="handleFilePick" multiple="">
      </div>

When you select index.html and click  to preview the app, it should now look like this:

Unfortunately, the app doesn't provide much space to drag and drop files just yet.

→ Add the following code inside the <style> tag in the styles/main.css file to improve how the drag and drop area looks:

#dropzone {
  border-radius: 5px;
  padding: 90px 10px;
  text-align: center;
  font: 20pt bold 'sans-serif';
  color: #bbb;
  background: white;
}

#files {
  display: block;
  margin: 0 auto;;
}

#files::-webkit-file-upload-button {
  visibility: hidden;
}

#files::before {
  content: 'or click to select files';
  background: white;
  padding: 5px;
  outline: none;
  white-space: nowrap;
  -webkit-user-select: none;
  cursor: pointer;
  font-size: 16pt;
  margin: 0 auto;
}

#files:hover::before {
  border-color: black;
}

#files:active::before {
  background: -webkit-linear-gradient(top, #e3e3e3, #f9f9f9);
}

The app should now look like this:

Don't worry yet about hooking up the events that actually perform the upload and drag-drop logic. You'll do that a little bit later.

Add buttons to the toolbar

You may have noticed the <paper-toolbar> element in our index.html file. Let's use that element to add a second <paper-toolbar> to the app. This toolbar will sit below the dropzone in the "loggedin" view. It will be used for toggling whether the app should upload selected files automatically (as soon as each file is selected), or only after the user clicks an Upload button. All of the icons and content inside the toolbar should flex to fit their layout.

You can use a few elements to achieve this UI. Namely, you'll use some layout helpers that come with polymer.html, and you'll use <paper-toolbar> and <paper-toggle-button>. You'll also add a label to the toolbar to display the current upload status so the user knows what’s going on.

→ In drive-app.html add the following code directly after the closing </div> tag for our dropzone <div> but before the closing </div> tag for <div id=”logged”>:

      <paper-toolbar id="dropzone-bar">
        <div class="flex">
          <span>Auto-upload</span>
          <paper-toggle-button oncaption="On" offcaption="Off" 
            checked="{{autoUpload}}">
          </paper-toggle-button>
        </div>
        
        <div class="flex">
          <button id="upload" on-click="manualUpload" 
            style$="{{computeStyle(autoUpload)}}">
              Upload files
          </button>
        </div>
        
        <div class="flex"></div>
        <div>
          <div id="status">Ready</div>
        </div>
      </paper-toolbar>

You may notice we’ve added a new element in this step, one we haven’t used before: <paper-toggle-button>. In order to gain access to this element, we need to import it. You could import it directly in the drive-app.html file but we have a file dedicated to import all our elements in one place, called elements/elements.html. In that file, add the following line to import the <paper-toggle-button> element, right after the import of <paper-toolbar>:

<link rel="import" href="../bower_components/paper-styles/classes/shadow-layout.html">
<link rel="import" href="../bower_components/paper-toggle-button/paper-toggle-button.html">

Finally, add the following CSS styling to our styles/main.css file, right after the #dropzone selector, to give our toolbar a nice appearance:

#dropzone-bar {
  border-bottom: 1px solid white;
  background: rgb(229, 229, 229);
  color: black;
  box-shadow: rgba(0, 0, 0, 0.298039) 0px 1px 8px;
  font-size: 1em;
}

Run the app

Select index.html and click the  button. At this point you should see a fleshed out <paper-toolbar>. The app is coming together!

Next up

Next, you'll add an area to display which files the user has uploaded.

Add panels

The app needs an area to display the names of files that the user has selected for uploading, as well as those that have already been uploaded. The approach we'll take is to have two panels, Queue and Uploaded areas, each containing a toolbar as part of the heading.

The proposed UI

→ In drive-app.html, after the <paper-toolbar> element you just added in the previous step, add the following markup:

      <div id="file-splitter" class="flex horizontal layout">  
        <div class="flex panel">
          <paper-toolbar><div class="layout horizontal">Queue</div></paper-toolbar>
          <ul class="filelist">
            <template is="dom-repeat" items="{{uploadList}}" as="file">
            <li>
              <strong>{{file.name}}</strong> <span>{{file.type}}</span>
            </li>
            </template>
          </ul>
        </div>
        
        <div class="flex panel">
          <paper-toolbar><div class="layout horizontal">Uploaded</div></paper-toolbar>
        
          <ul class="filelist">
            <template is="dom-repeat" items="{{uploadedList}}" as="file">
              <li>
                <img src="{{file.iconLink}}"> 
                <strong>{{file.title}}</strong>
                <p>
                  <a href$="{{file.selfLink}}">Drive URL</a>
                  <a href$="{{file.webContentLink}}">Web URL</a>
                </p>
              </li>
            </template>
          </ul>
        </div>
      </div>

Add the following CSS styling to our styles/main.css file to add some visual separation betwen our two panels:

.panel {
  border-style: solid;
  border-width: 1px;
}

→ Select the index.html file and click the  button.

You should now see the following:

The app now has most of the UI it needs. It's time to add the logic to get it working!

Next up

Next, you'll add the event handling logic to make your app come to life.

Now let’s add the JavaScript that brings the markup to life.

→ In drive-app.html, in the <script> element (after the closing </template> tag), replace the Polymer({ is: 'drive-app' }); line with the following code, which defines some arrays and properties you'll use to work with upload lists, adds logic for selecting files the arrays when files are chosen, and supports the manual upload button:

    Polymer({
      is: 'drive-app',
      properties: {
        uploadList: {    // selected files
          type: Array,
          value: [],
          notify: true
        }, 
        uploadedList: {  // successfully uploaded files
          type: Array,
          value: [],
          notify: true
        },
        autoUpload: { // automatically upload?
          type: Boolean,
          value: false,
          notify: true
        }
      },

      manualUpload: function () { 
        this.uploadFiles(this.queue); 
      },
  
      selectFiles: function (files) {
        this.queue = files;
        this.$.status.textContent = 'Files selected';
        var f;
        for(var i=0; f = files[i]; i++) {
          this.push('uploadList', f);
        }
    
        if(this.autoUpload) {
          this.uploadFiles(files);
        }
      }
    });

So, you’ve now wired up some of the UI to functions, but the app is still missing the uploadFiles function.

Implement file uploading

uploadFiles can use a little library written by the Google Drive team that makes working with file uploads easier. You can use Bower to install this library.

→ Edit bower.json to add a dependency on cors-update-sample by adding this line to the dependencies section (if you add it at the end of the dependencies block, make sure to add a comma to the end of the preceding line!):

    "cors-upload-sample": "googledrive/cors-upload-sample#master"

→ In Chrome Dev Editor, right-click the filename bower.json.

→ Run Bower Update from the dropdown.

→ Make sure that cors-upload-sample is in the bower_components directory.

Excellent. You can now reference the upload.js script that comes with this package.

→ Add the following import after the polymer.html import at the top of drive-app.html:

<script src="bower_components/cors-upload-sample/upload.js"></script>


→ Add the uploadFiles function before the manualUpload function in drive-app.html:

      uploadFiles: function (files) {
        var uploadedList = this.uploadedList;
        this.$.status.innerHTML = 'Uploading...';
        
        var f;
        for(var i=0; f = files[i]; i++) {
          var uploader = new MediaUploader({
            file: f,
            token: this.accessToken,
            onComplete: function (data) {
              this.push('uploadedList', JSON.parse(data));
              this.$.status.innerHTML = 'Upload successful';
              this.uploadList = [];
            }.bind(this)
          });
          uploader.upload();
        }
      },

→ Just above that, add helpers for handling drag and drop, file picking, and clearing the upload

      computeStyle: function(val) {
        if (val) {
          return 'display: none';
        } else {
          return 'display: block';
        }
      },

      tapSelect: function (e) {
        if (e.target.id !== 'files') {
          this.$.files.click();
        }
      },

      handleDragOver: function (e) {
        e.stopPropagation();
        e.preventDefault();
        e.dataTransfer.dropEffect = 'copy';
      },
        
      handleFilePick: function (e) {
        e.stopPropagation();
        e.preventDefault();
          this.selectFiles(e.target.files);
      },
        
      handleFileSelect: function (e) {
        e.stopPropagation();
        e.preventDefault();
        this.selectFiles(e.dataTransfer.files);
      },
        
      clearUploadList: function () {
        this.uploadedList = [];
        this.uploadList = [];
      },

Next up

You almost have enough in place to upload files. The last thing you need is a way to sign users into their Google Drive account so that uploads work.

In this step you’ll add support for authenticating with a Google account.

Install

To support authentication, you can use two elements: <google-signin> and <google-signin-aware>. The former provides an authentication button. The latter provides event handling and all the machinery you need to authenticate with Google. So, let's add those elements!

→ Edit bower.json once again, adding a reference to the google-signin element at the end of the dependencies block (taking care to add a comma before the line you insert):

"dependencies": {
    . . .
    "google-signin": "GoogleWebComponents/google-signin"
}

→ In Chrome Dev Editor, right-click the filename bower.json.

→ Run Bower Update from the dropdown.

→ In drive-app.html, after the polymer.html import, add an HTML import for <google-signin> and <google-signin-aware>:

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

→ In drive-app.html, just after the opening <template> element, add an instance of the <google-signin> element and the <google-signin-aware> element:

    <google-signin
      client-id="YOUR_CLIENT_ID_GOES_HERE"
      scopes="https://www.googleapis.com/auth/drive" >
    </google-signin>
    <google-signin-aware 
      scopes="https://www.googleapis.com/auth/drive"
      on-google-signin-aware-success="signedIn"
      on-google-signin-aware-signed-out="signedOut">
    </google-signin-aware>

The https://www.googleapis.com/auth/drive scope above ensures that when the user signs in, they're prompted for permission to upload files to their Drive account.

Note: Replace the placeholder text marked YOUR_CLIENT_ID_GOES_HERE below with a valid client ID. To get a valid client ID, visit the Google Developer Console and create a new project or open an existing project.

Select APIs (under APIs & auth) and enable the Google Drive API for your project:

 

Then, select Credentials (under APIs & auth):

From the Add Credentials pull-down menu, select OAuth 2.0 client ID:

On the subsequent dialog, choose Web Application, give it a name (e.g. “Polymer Drive Client”), add http://localhost:PORT to the Authorized JavaScript origins and the Authorized redirect URIs fields (where PORT is the port number you see in the address bar when you preview your app locally):

Use the resulting client ID to populate the client-id attribute in your <google-signin> element. 

Hook up events

This <google-signin-aware> element fires a unique set of events, informing the user when they successfully authenticated and are able to upload files to their Drive accounts.

The events that are of interest here are google-signin-aware-success and google-signing-aware-signed-out.

→ Add the following event handling functions just before the tapSelect method in the drive-app.html file to field the signin and signout events:

      signedIn: function (e) {
        this.accessToken = e.detail.access_token;
        this.$.loggedin.style.display = 'block';
      },
        
      signedOut: function (e) {
        this.$.loggedin.style.display = 'none';
      },

Run the app

You now have an application that looks like this:

It includes a Sign-in/out button (which changes its label as  function of signed-in state) that, when clicked, opens a window allowing the user to authenticate with Google Drive:

When the user signs in, they are prompted to allow the app to upload files to their Drive account.

Dragging a file to the main window or clicking the click to select files link adds the file to the queue. When the user selects files, the Queue is populated with a list of files to be uploaded:

The user can then click the Upload files button to upload the selected files to Drive.

If successful, uploaded files appear in the Uploaded panel with both the name of the file and links to view it on the web.

Hooray. Our Google Drive app using Polymer is now fully functional.

Add some final styling

You’re almost at the finish line. Now add some final styling to wrap up the app.

→ Copy and paste the following styles into the styles/main.css file.

.filelist {
  padding-left: 0px;
  list-style-type: none;
}

.filelist li {
  background: #F5F5F5;
  padding: 10px 10px;
  border: 1px solid #ccc
}

#loggedin {
  display: none;
  right: 0;
}

#dropzone-bar {
  right: 0;
  position: relative;
}

.container {
  width: 400px;
  height: 500px;
}

#file-splitter {
  width: 100%;
  overflow: visible;
}

#upload {
  border: 1px solid rgba(0, 0, 0, 0);
  color: rgb(255, 255, 255);
  background-color: rgb(65, 132, 243);
  height: 36px;
  line-height:27px;
  font-size: 0.8em;
}

@media (max-width: 500px) {
  .panel { width: 100% }
}

With these styles, the signed in view is now hidden until the user is authenticated. For signed-out users, the app looks like this:

These styles also include little, subtle fixes for making the app look and behave better on devices with smaller screens. For example, the Upload files button now has a slightly larger touch target:

Fantastic.

Also be sure to check out Auto-upload mode. Toggle the button, select some files, and whoooosh—watch the files go onto Drive!

Summary

In this step you learned how to:

Hooray! You built an entire Google Drive client using Polymer! This just goes to show the power of Web Components and Polymer. They're reusable, composable, scoped, and can be used to build powerful web apps that work great everywhere.

Additional resources

We hope you've found this taste of Polymer useful. To learn more, take a look at the following resources.

Polymer

Other