In an earlier blog post, we announced the Election Info sample app. We briefly talked about how we were able to use Apps Script to easily create a comprehensive sample application that provided timely voting information. This post shows just how easy it is to use Apps Script to get information from an external API and integrate with various Google services to create a rich web application and provide a meaningful user experience.
First, we use UrlFetchApp to get JSON from the Google Civic Information API and use Utilities.jsonParse to convert it to a useful javascript object.
var url = 'https://www.googleapis.com/civicinfo/us_v1/voterinfo/2000/lookup'; var address = { "address" : "1263 Pacific Ave. Kansas City KS" }; var options = { method : "post", contentType : "application/json", payload: Utilities.jsonStringify(address) }; var responseText = UrlFetchApp.fetch(url, options).getContentText(); var response = Utilities.jsonParse(responseText);
After getting the response object, we can simply drill into it to access various data provided by the API.
One of the things the response object provides us with is the election date. Using Apps Script's Calendar service, it is really easy to create an event on voting day in the user's calendar with the polling address. First, we create a Date object from the date string. We then create an all-day event on the default calendar on this date, passing along the polling address we get from the response object.
// create a Date object from the response date string // ("2012-11-6" --> Date object) var [year, month, day] = response.election.electionDay.split('-'); // javascript months are zero-indexed var electionDate = new Date(year, month-1, day); // get the first polling location's address var pollAddress = response.pollingLocations[0].address; var cal = CalendarApp.getDefaultCalendar(); cal.createAllDayEvent("Go Vote!", electionDate, {location:pollAddress});
Using the Maps service, we can generate static maps with the user's home or polling address as shown in the following code snippet. We display these maps on the web app page, then embed them in the reminder email and bring-along document as we will show in the following sections.
var userAddress = response.normalizedInput; var normalizedAddress = userAddress.line1 + ' ' + userAddress.city + ', ' + userAddress.state + ' ' + userAddress.zip; // normalizedAddress looks like "501 Kildaire Rd Chapel Hill, NC 27516" var staticMapUrl = Maps.newStaticMap().setSize(600, 300) .addMarker(normalizedAddress) .getMapUrl();
We also provide a simple method for users to email themselves all of this information. Using the Gmail service, we can send an HTML email that embeds the voting information and the static maps we generated above. The Apps Script documentation contains great tutorials such as the Maps tutorial we used to generate the directions below.
var email = Session.getActiveUser().getEmail(); var body = 'Election Date: ' + electionDate + '<br/>' + 'Your polling address: ' + pollAddress + '<br/>' + 'Polling Hours: ' + pollingHours + '<br/>' + '<img src="' + directions.mapUrl + '"/> <br/>' + 'Directions: ' + dirList; MailApp.sendEmail(email, 'Upcoming Election Voting Information', 'Voting Info', {htmlBody: body});
Using the Document service, we were able to easily generate a bring-along document with polling address, hours, and directions. The follow code excerpt shows how easy it is to add different elements like headers, tables, and paragraphs to a document. Apps Script also provides an extensive list of methods to programmatically control the look and presentation of the various elements.
var title = "Voting Information"; var doc = DocumentApp.create(title + " for " + homeAddress); var reportTitle = doc.appendParagraph(title); reportTitle.setFontFamily(DocumentApp.FontFamily.ARIAL) .setFontSize(22).setForegroundColor('#4A86E8') .setBold(true) .setAlignment(DocumentApp.HorizontalAlignment.CENTER); var header = doc.addHeader(); header.appendParagraph('Generated by the Election Info application ' + 'built on Google Apps Script') .setAlignment(DocumentApp.HorizontalAlignment.CENTER) .setAttributes({ITALIC : true}); var tableStyle = {}; tableStyle[DocumentApp.Attribute.PADDING_BOTTOM] = 0; tableStyle[DocumentApp.Attribute.PADDING_TOP] = 0; tableStyle[DocumentApp.Attribute.PADDING_LEFT] = 0; tableStyle[DocumentApp.Attribute.PADDING_RIGHT] = 0; var addressTable = doc.appendTable([ ['Your address: ' + homeAddress], ['Your Polling Location: ' + pollAddress], [''] ]).setAttributes(tableStyle); // add appropriately sized poll location image addressTable.getCell(1,0).appendImage(pollImg.getBlob()) .setHeight(300).setWidth(600); // populate last row of the table with polling hours addressTable.getCell(2,0).clear().appendParagraph("Polling Hours: "); addressTable.getCell(2,0).appendParagraph( UserProperties.getProperty(Keys.POLLING_HOURS));
Here is an image which shows the generated bring-along document embedded with static map images from the Maps service.
Apps Script allowed us to easily take information from an external API and tie it into various Google services to provide a great user experience. Stay tuned for an upcoming blog post showing how we created the front end!
"No file is an island." John Donne said something a bit like this in 1620 A. D., hundreds of years before the internet was ever invented, and it just gets more and more true as time goes on. In our online world of information, entertainment, and socializing, everyone is connected -- and everyone wants to collaborate.
Would you like to open a collaborative space in your Drive app by injecting comments and discussion threads in your users' files? This is now easily done with the Drive API. Using the new comments and replies resources together with a simple anchoring scheme to nail down the location of comments in your document, you can provide discussion threads much like the ones found in Google Docs.
Our new commenting model has two layers:
In a typical scenario, an app gets the head revision of a file, lists the existing discussions, and inserts or deletes comments and replies as needed. It’s recommended that apps should also perform user permission checks and make sure commenters are authorized. These best practices, along with a complete reference for anchoring comments in files, are detailed in Managing Comment and Discussions in the Drive SDK.
For a great example of commenting best practices, you won’t need to look any further than the Google docs in your Drive. The features you see in our own implementation -- highlighted anchoring, UI options to reply, resolve, edit and delete -- are all available for you to add to your own app.
We look forward to seeing how you integrate comments and discussions in to your Drive app! Do a better job than Google docs, and we promise to be more pleased than surprised. If you have questions or feedback about comments and discussions, don’t hesitate to let us know on our Stack Overflow tag, google-drive-sdk.
Some enterprise applications need to programmatically access their users’ data without any manual authorization on their part. For example, you might want to use the Tasks API to add a task to all of your employees’ Google Tasks lists during the holiday season to remind them of something like, “Come pick up your holiday gift at the front desk!” Or, you might want to run some company-wide analysis of the content of your employees’ Google Drive.
In Google Apps domains, the domain administrator can grant applications domain-wide access to its users' data — this is referred as domain-wide delegation of authority. This basically allows applications to act on behalf of Google Apps domain users when using APIs.
Until recently this technique was mostly performed using 2-Legged OAuth 1.0a (2-LO). However, with the deprecation of the OAuth 1.0 protocol and the resulting programmed shutdown of 2-LO, the recommended authorization mechanism is now to use OAuth 2.0 and service accounts.
Unlike regular Google accounts that belong to an end user, service accounts are owned by your application and therefore identify your application. They can be created in the Google APIs Console and come with their own OAuth 2.0 credentials.
Google Apps domain administrators can delegate domain-wide authority to the service account’s credentials for a set of APIs. This results in allowing the application, by using the service account’s credentials, to act on behalf of the Google Apps domain’s users.
If you’d like to learn more, have a look at the recently published Google Drive SDK documentation on using OAuth 2.0 and service accounts for domain-wide delegation of authority.. These documents provide a step by step process and code samples to help you get started with service accounts.
From nations choosing presidents to offices selecting which coffee to brew, we often find ourselves involved in election systems designed to choose the best option. This spring my alma mater's solar vehicle team, CalSol, needed to elect new leaders. Our previous system was painfully slow, involved "raising hands" in a room, and excluded any team members who could not attend a specific meeting. I set out to solve these problems and the result was an easy method for running fair elections in a matter of minutes.
I was able to build the system completely on Google products and technologies:
I used a lesser known voting system called instant-runoff voting (IRV), or the alternative vote, which asks voters to rank candidates rather than cast a single vote. These votes, along with a secret voting key which I provided to each member, are recorded with a Google Form that automatically populates a spreadsheet. The code in Apps Script looks through the spreadsheet to count the votes while ensuring that each voting key is only used once. The secret keys not only prevent voters from casting multiple votes, but they also allow voters to change their vote by submitting the form again.
Below is a simplified snippet of code that shows the general process used to calculate the winner.
/* Some code omitted for clarity */ /* candidates is a list of names (strings) */ var candidates = get_all_candidates(results_range); /* votes is an object mapping candidate names -> number of votes */ var votes = get_votes(results_range, candidates, keys_range, valid_keys); /* winner is candidate name (string) or null */ var winner = get_winner(votes, candidates); while (winner == null) { /* Modify candidates to only include remaining candidates */ get_remaining_candidates(votes, candidates); if (candidates.length == 0) { Browser.msgBox("Tie"); return; } votes = get_votes(results_range, candidates, keys_range, valid_keys); winner = get_winner(votes, candidates); } Browser.msgBox("Winner: " + winner);
I learned that putting a little effort into Apps Script can make people happy and save a lot of time. The team feedback was outstanding. One CalSol member said the process was an "Excellent, clean, and professional voting process. This should become a standard [for the team]." I was elated when I was able to close the polls during a meeting and announce the winners of twelve independent elections in just a matter of minutes.
If you like, you can watch a video demonstrating how to create and run your own election using this script:
Try the script yourself to make sure your coffee preferences are heard!
It’s time for the 2012 General Election in the United States and along with it comes the tedious process of finding your voter registration, polling sites, times, directions, etc. The previously announced Google Civic Information API provides a great service to programmatically obtain much of this information based on the your home address. Google Apps Script makes it really quick and easy to build a web application that queries this information and uses various Google services to organize and track your information.
Election Info is a sample application built using Apps Script that can:
HtmlService
UrlFetch
MapsService
UserProperties
As you can see, this is a comprehensive sample app that is useful while also highlighting key Apps Script capabilities.
Install the app from the Chrome Web Store. Check back soon as we will be writing a blog post with details and sample code on how the sample was built.
Since we released version 2 of the Google Drive SDK at Google I/O, we’ve been quietly updating the DrEdit sample application to use the new API. As part of the update, the UI for DrEdit has been rewritten to use AngularJS, a modern web application toolset developed by Google and used in apps at DoubleClick. You might be wondering -- why go through the trouble of rewriting the UI for a basic sample app just to show off some new API features? Turns out it was more of a happy coincidence, but a valuable one and great learning experience!
I had the pleasure of co-presenting a session on building great apps for Google Drive, and a big focus of the talk was on all the little things that go into making an app intuitive and user-friendly. This is particularly important for Google Drive, where many users are already familiar with the built-in apps like Docs, Presentations, and Spreadsheets.
The first version of DrEdit was a good demo app, but didn’t follow all of our recommendations. I didn’t want to tell developers all the things they should be doing without having tried them myself. I decided to write a separate sample for the talk and needed a solid base to build on. It was the perfect opportunity to learn a new tool!
Angular doesn’t aim to abstract away HTML, Javascript & CSS. Rather, it enhances HTML to make building dynamic apps easier. One benefit, besides a nice short learning curve, is the positive interaction with other tools. To give the app some structure, I used Bootstrap. For example, the HTML for displaying the authenticated user’s info and a small dropdown to link to their profile in the navigation bar only required a few minor changes from typical Bootstrap usage (shown in bold) to wire up to a controller.
<ul class="nav pull-right" ng-controller="UserCtrl"> <li class="dropdown"> <a class="dropdown-toggle" data-toggle="dropdown" href="#"> {{user.email}} </a> <ul class="dropdown-menu"> <li><a href="{{user.link}}" target="_blank">Profile</a></li> </ul> </li> </ul>
Even models are plain javascript objects. Anything reachable through a scope (the binding between a view and controller) is considered part of the model. These can be primitives, hashes, or objects. No need to extend a base class or access properties through special properties. Rather than use change listeners that require special instrumentation, Angular uses dirty checking to detect model changes and update views.
The one catch with this approach is it requires any changes to the model to be made inside the scope of a scope.$apply(fn) call. In most cases, this is done automatically. When working with external libraries or raw XMLHttpRequests that can fire asynchronous callbacks, calling $apply yourself is necessary to make sure mutations are tracked correctly.
scope.$apply(fn)
$apply
Speaking of asynchronous tasks…
No, I’m not talking about the hit song by 80’s band Naked Eyes, rather Angular’s $q service based on one of the proposed CommonJS Promises APIs. If you’re already familiar with JQuery’s deferred object or any of the other related implementations, this is familiar territory. If not, time to learn. Working with deferred objects can be a lot easier than the traditional callback approach. You can compose async tasks either serially or in parallel, chain callbacks, and return deferred objects from functions like normal results.
Where this mostly comes into play is Angular’s $http service. If you’ve used jQuery, you’ll find it similar to jQuery.ajax() & the jqXHR result. It is based on the deferred/promises API and also ensures callbacks are executed correctly inside $apply for safe & efficient model mutations. This combination makes it easy to work with remote services in Angular.
$http
Trying to learn some new frameworks while preparing for Google I/O and helping developers to launch apps on our updated API all within a few weeks was a lot to take on. A few corners were cut and there are a few things I’d like to revisit when time permits:
<editor content=”myModel.text”/>
I know I’ve only scratched the surface and have a lot more to learn. Even so, it was incredibly fun diving head first into AngularJS, and I highly recommend considering it if you’re dissatisfied with your current framework or just want to learn something new!
Do you like to store photos in Google Drive? You are not alone! Photographs are one of the most common file types stored in Google Drive. The Google Drive API now exposes Exif data for photos, so that Google Drive Apps can use it. The Exif data contains information about camera settings and photo attributes.
Despite being an awful photographer, I love photographing benches, and here is one I took while at the beach. Let’s have a look at some of these new fields for this photo.
When I examine the metadata for this image using a drive.files.get call, there is now a field, imageMediaMetadata, containing the detailed photo information:
drive.files.get
imageMediaMetadata
"imageMediaMetadata": { "width": 2888, "height": 1000, "rotation": 0, "date": "2012:07:08 15:22:25", "cameraMake": "NIKON CORPORATION", "cameraModel": "NIKON D90", "exposureTime": 8.0E-4, "aperture": 5.6, "flashUsed": false, "focalLength": 105.0, "isoSpeed": 200 }
So whether you are just storing your amateur snaps like me, or using Google Drive to store serious photographs, we hope this will be useful information for Drive apps. For example, a photo organizing application will be able to create thumbnail and information views for photos without ever having to download them.
For more information, please visit our documentation, and if you have any technical questions, please ask them on StackOverflow. Our team are waiting to hear from you.