Wednesday, March 11, 2015

Using open source libraries in Apps Script

JavaScript has long been the de facto choice for client-side web development, but lately its been catching on server-side as well. While we like to think that Apps Script has contributed to the trend, projects such as Mozillas Rhino and Node.js have also done a great deal to popularize the concept. As a result, developers have created a wealth of new open-source JavaScript libraries, and in this post well talk about how you can leverage them in your Apps Script projects.

Underscore
One library I wanted to use in my scripts was Underscore, which describes itself as "a utility-belt library for JavaScript." It provides a wealth of helper functions that make coding in JavaScript cleaner and more enjoyable. Take, for example, the simple situation where you want to log each value in a range.

// Using plain JavaScript.
for (var i = 0; i < values.length; i++) {
for (var j = 0; j < values[i].length; j++) {
Logger.log(values[i][j]);
}
}

Although writing for loops like this is a common pattern, its a fair amount of typing and you need to keep track of counter variables that serve little purpose. Underscore provides an each() method that makes the process much simpler.

// Using Underscore.
_.each(values, function(row) {
_.each(row, function(cell) {
Logger.log(cell);
});
});

Passing anonymous functions as parameters takes a little getting used to, but if youve worked with jQuery, the pattern feels familiar.

Underscore also has some great extensions, and Underscore.string provides some useful string manipulation features. My favorite is the ability to use sprintf() notation in JavaScript, which can simplify the process of building complex strings.

// Using plain JavaScript.
var message = "Hello, " + firstName + " " + lastName + ". Your wait time is " + wait + " minutes.";

// Using Underscore.string.
var message = _.sprintf("Hello, %s %s. Your wait time is %d minutes.", firstName, lastName, wait);

Integrating with Apps Script
The simplest way to include the Underscore library in a project would be to paste its source code directly into your script, but this would lead to a lot of duplication if you end up using it in multiple projects. Earlier this year, we released a feature in Apps Script called libraries that allows you to share scripts and include them in other projects. Packaging a JavaScript library like Underscore as an Apps Script library is possible, but requires some helper functions to work correctly.

When Underscore loads, it creates a global variable named "_" that you use to access its functionality. Apps Script specifically prevents the global scope of a library from interfering with the global scope of the script that includes it, so I built a helper function into the library to pass the variable around.

// In the library.
function load() {
return _;
}

In my script that includes the library, I simply make a call to that function and use the result to set up my own "_" variable.

// In the script that includes the library.
var _ = Underscore.load();

To try my copy of the Underscore library in your own project, use the project key "MGwgKN2Th03tJ5OdmlzB8KPxhMjh3Sh48" and the code snippet above. You can browse the full source code here.

Using it with the HtmlService
Using the code above, I could easily include the library in my server-side Apps Script code, but I also wanted to use these functions client-side in my web app served by the HtmlService. To accomplish this, I created a copy of the Underscore source code, wrapped it in <script> tags, and stored them in Html files (instead of Script files). These snippet files could then be included in my web apps HtmlTemplates using the helper function below.

// In the library.
var FILES = [Underscore.js, Underscore.string.js, Init.js];
function include(output) {
for (var i = 0; i < FILES.length; i++) {
var file = FILES[i];
output.append(
HtmlService.createHtmlOutputFromFile(file).getContent());
}
}

This function was called in the web apps HtmlTemplate using the simple code below.

<!-- In the web app that includes the library. -->
<html>
<head>
<? Underscore.include(output) ?>
</head>
...

Other libraries
Integrating with Underscore was fairly easy, but trying the same approach with other open-source libraries may be a bit more complicated. Some libraries wont run correctly in the Apps Script environment if they rely on certain capabilities within the browser or Node.js runtime. Additionally, to be served by the HtmlService, the code must pass the Caja engines strict validation, which many popular libraries dont meet. In some cases, you may be able to manually patch the library to work around these issue, but this usually requires a deep understanding of how the library works.

We hope youre inspired to use Underscore and other open-source libraries in your own work. If you find a library that works great with Apps Script, share it with me on Google+ and Ill help get the word out.


Eric Koleda profile

Eric is a Developer Programs Engineer based in NYC on the Google Apps Script team. Hes previously worked with the AdWords API and enterprise content management software.

Read more »

Google Groups and Google Apps Script

Google Groups is a great way to foster communication over email and on the web, connecting people and allowing them to participate in and read archived discussions. Today, we are introducing the Google Groups Service in Google Apps Script. Groups Service will allow a script to check if a user belongs to a certain group, or to enumerate the members of a particular group. The Google Groups Service works with groups created through the Google Groups web interface as well as groups created by enterprise customers with their own domain using the control panel and the Google Apps Provisioning API.

This opens a wide range of possibilities, such as allowing a script with Ui Services to show additional buttons to the members of a particular group - for example teachers or managers - and sending customized emails to all the members of a group.

Here are a few sample scripts to help you get started with the new API. To try out these samples, select Create > New Spreadsheet and then Tools > Script Editor from the menu. You can then copy the code into the script editor. The scripts’ output will appear back in the spreadsheet.

List Your Google Groups

The Groups Services can be used to fetch a list of the Google Groups of which you’re a member.

Below is a function which returns all the groups of which you’re a member. Copy and paste it into the script editor and run it. The editor will prompt you to grant READ access to the Google Groups Service before the script can run successfully.

If you receive a message stating that you’re not a member of any group, open up Google Groups and join any of the thousands of groups there.

function showMyGroups() {
var groups = GroupsApp.getGroups();
var s;
if (groups.length > 0) {
s = "You belong to " + groups.length + " groups: ";
for (var i = 0; i < groups.length; i++) {
var group = groups[i];
if (i > 0) {
s += ", ";
}
s += group.getEmail();
}
} else {
s = "You are not a member of any group!";
}
Browser.msgBox(s);
}

Test Group Membership

Brendan plays trumpet in a band. He also runs the band’s website and updates its Google+ page. He’s created a web application with Google Apps Script and now he wants to add to it some additional features for members of the band. Being a model Google user, he’s already subscribed each band member to a Google Group. Although building a complete UI with Google Apps Script is beyond the scope of this article, Brendan could adapt the following function to help make additional features available only to members of that Google Group.

Of course, this is not just useful for tech-savvy trumpet players: schools may wish to make certain features available just to teachers or others just to students; businesses may need to offer certain functionality to people managers or simply to show on a page or in a UI operations of interest to those in a particular department. Before running this example yourself, replace test@example.com with the email address of any group of which you’re a member.

Note: the group’s member list must be visible to the user running the script. Generally, this means you must yourself be a member of a group to successfully test if another user is a member of that same group. Additionally, group owners and managers can restrict member list access to group owners and managers. For such groups, you must be an owner or manager of the group to query membership.

function testGroupMembership() {
var groupEmail = "test@example.com";
var group = GroupsApp.getGroupByName(groupEmail);
if (group.hasUser(Session.getActiveUser().getEmail())) {
Browser.msgBox("You are a member of " + groupEmail);
} else {
Browser.msgBox("You are not a member of " + groupEmail);
}
}

Get a List of Group Members

Sending an email to the group’s email address forwards that message to all the members of the group. Specifically, that message is forwarded to all those members who subscribe by email. Indeed, for many users, discussion over email is the principal feature of Google Groups.

Suppose, however, that you want to send a customised message to those same people. Provided you have permission to view a group’s member list, the Google Groups Service can be used to fetch the usernames of all the members of a group. The following script demonstrates how to fetch this list and then send an email to each member.

Before running this script, consider if you actually want to send a very silly message to all the members of the group. It may be advisable just to examine how the script works!

function sendCustomizedEmail() {
var groupEmail = "test@example.com";
var group = GroupsApp.getGroupByEmail(groupEmail);
var users = group.getUsers();
for (var i = 0; i < users.length; i++) {
var user = users[i];
MailApp.sendEmail(user.getEmail(), "Thank you!",
"Hello " + user.getEmail() + ", thank you for joining this group!");
}
}

Find Group Managers

The Google Groups Service lets you query a user’s role within a group. One possible role is MANAGER (the other roles are described in detail in the Google Groups Service’s documentation): these users can perform administrative tasks on the group, such as renaming the group and accepting membership requests. Any user’s Role can be queried with the help of the Group class’ getRole() method.

This sample function may be used to fetch a list of a group’s managers. Once again, you must have access to the group’s member list for this function to run successfully:

function getGroupOwners(group) {
var users = group.getUsers();
var managers = [];
for (var i = 0; i < users.length; i++) {
var user = users[i];
if (group.getRole(user) == GroupsApp.Role.MANAGER) {
managers.push(user);
}
}
return managers;
}

Let us know what you end up building, or if you have any questions about this new functionality, by posting in the Apps Script forum.

Trevor Johnston  

Trevor is a software engineer at Google. Before joining the Google Apps Script team in New York, he developed travel products in Switzerland and supported Google’s help centers in Dublin. Prior to joining Google, he worked on Lotus branded products at IBM’s Dublin Software Lab.

Read more »

Important Changes to Google Apps Marketplace Billing Policies

When we launched the Google Apps Marketplace in March last year, one of our goals was to make it easy for developers to build, integrate, and sell their apps to Google Apps users. Since then, each vendor in the Google Apps Marketplace has handled their own billing and keeps the revenue they generate. Today, we are making that the official Marketplace policy moving forward: Google will not require vendors to adopt a Google billing API or share any portion of their revenue with us. This will keep more revenue in developers’ pockets, and brings the Google Apps Marketplace policy in line with the Chrome Web Store.

So it’s business as usual -- developers can continue to “bring their own billing” to the Marketplace. Developers retain full control over application pricing and billing, and continue to keep all revenue from Google Apps Marketplace customers.

If you’re in need of a billing solution, we encourage you to try Google Checkout and In-App Payments. Or use one of the many other commercial billing and subscription services available online.

Please join us at our next Google Apps Developer Office Hours on Google+ Hangouts on December 19th at 12 PM PST time to discuss the Google Apps Marketplace and any questions you may have about these changes.


Scott McMullan   profile | twitter

Scott is a Product Manager at Google, where he runs the Apps Marketplace and is extremely lucky to be able to work with amazing developers and companies revolutionizing the way businesses use the web.

Read more »

Tuesday, March 10, 2015

Calendar API v3 Best Practices Recurring Events

We recently launched a new version of the Google Calendar API. In addition to the advantages it gains from Googles new infrastructure for APIs, Google Calendar API v3 has a number of improvements that are specific to Google Calendar. In this blog post we’ll highlight a topic that often causes confusion for developers using the Google Calendar API: recurring events.

A recurring event is a template for a series of events that usually happen with some regularity, for example daily or bi-weekly. To create a recurring event, the client specifies the first instance of the event and includes one or more rules that describe when future events should occur. Google Calendar will then expand the event into the specified occurrences. Individual events in a series may be changed, or even deleted. Such events become exceptions: they are still part of the series, but changes are preserved even if the recurring event itself is updated.

Lets create a daily recurring event that will occur every weekday of the current week (as specified by the recurrence rule on the last line):

POST https://www.googleapis.com/calendar/v3/calendars/primary/events

{
"summary": "Daily project sync",
"start": {
"dateTime": "2011-12-12T10:00:00",
"timeZone": "Europe/Zurich"
},
"end": {
"dateTime": "2011-12-12T10:15:00",
"timeZone": "Europe/Zurich"
},
"recurrence": [
"RRULE:FREQ=DAILY;COUNT=5"
]
}

When added to a calendar, this will turn into five different events. The recurrence rule is specified according to the iCalendar format (see RFC 5545). Note, however, that, in contrast to the previous versions of the Google Calendar API, the start and end times are specified the same way as for single instance events, and not with iCalendar syntax. Further, note that a timezone identifier for both the start and end time is always required for recurring events, so that expansion happens correctly if part of a series occurs during daylight savings time.

By default, when listing events on a calendar, recurring events and all exceptions (including canceled events) are returned. To avoid having to expand recurring events, a client can set the singleEvents query parameter to true, like in the previous versions of the API. Doing so excludes the recurring events, but includes all expanded instances.

Another way to get instances of a recurring event is to use the instances collection, which is a new feature of this API version. To list all instances of the daily event that we just created, we can use a query like this:

GET https://www.googleapis.com/calendar/v3/calendars/primary/events/7n6f7a9g8a483r95t8en23rfs4/instances

which returns something like this:

{
...
"items": [
{
"kind": "calendar#event",
"id": "7n6f7a9g8a483r95t8en23rfs4_20111212T090000Z",
"summary": "Daily project sync",
"start": {
"dateTime": "2011-12-12T10:00:00+01:00"
},
"end": {
"dateTime": "2011-12-12T10:15:00+01:00"
},
"recurringEventId": "7n6f7a9g8a483r95t8en23rfs4",
"originalStartTime": {
"dateTime": "2011-12-12T10:00:00+01:00",
"timeZone": "Europe/Zurich"
},
...
},
… (4 more instances) ...

Now, we could turn one instance into an exception by updating that event on the server. For example, we could move one meeting in the series to one hour later as usual and change the title. The original start date in the event is kept, and serves as an identifier of the instance within the series.

If you have a client that does its own recurrence rule expansion and knows the original start date of an instance that you want to change, the best way to get the instance is to use the originalStart parameter like so:

GET https://www.googleapis.com/calendar/v3/calendars/primary/events/7n6f7a9g8a483r95t8en23rfs4/instances?originalStart=2011-12-16T10:00:00%2B01:00

This would return a collection with either zero or one item, depending on whether the instance with the exact original start date exists. If it does, just update or delete the event as above.

We hope you’ll find value in these changes to recurring events. Keep in mind, too, that these are not the only improvements in Google Calendar API v3. Look for an upcoming post describing best practices for another key area of improvement: reminders.

If you have any questions about handling recurring events or other features of the new Calendar API, post them on the Calendar API forum.


Editors note:: 2/20/2012 - Removed references to API call which reverted changes made to an individual instance. This feature was deprecated.


Peter Lundblad   profile

Peter joined Google in 2006. Hes been leading the Calendar API team for the last 2 years. Hes previously worked on video uploads for YouTube.


Fabian Schlup   profile

Fabian is a Software Engineer at Google in Zürich, working on Calendar and Tasks, with a focus on APIs.


Read more »

Sharing and previewing Google Docs in Socialwok Google Data APIs

Editors note:Navin Kumar is CTO and co-founder of Socialwok, a feed-based group collaboration application for enterprises that integrates with Google Apps. With Socialwok, Google Apps users can create their own private social network to share Google Docs, Calendars and Spreadsheets with their coworkers. Navin and his team built Socialwok on Google App Engine for Java, Google Web Toolkit and Google GData APIs. Socialwok will be at the Google IO sandbox May 19 - 20, 2010.

At Socialwok (http://www.socialwok.com/tour), we work extensively with a wide variety of Google technologies (Google App Engine, Google Web Toolkit), OpenID, OAuth, and Google Data APIs (Google Docs, Google Calendar, Google Spreadsheets, etc...) while building our product.

Sharing and Previewing Google Docs in feeds

Most enterprise microblogging or collaboration solutions exist in their separate walled garden. Users have to use separate logins and post status updates independently of their existing workflow. From the start, we designed Socialwok to be integrated with the workflow of businesses who use Google Apps. Users can use their existing Google Apps or Gmail accounts to login into Socialwok. They can access all features of Socialwok from right inside Gmail. They can also create and share their existing Google Docs (Documents, Presentations, Spreadsheets) and Google Calendars with other coworkers who are following them in a feed.

In the below example, I upload a Microsoft Powerpoint (ppt) to Google Docs for sharing in a feed.

Uploading a Microsoft Powerpoint file for sharing using Google Docs
Google Docs automatically converts the Microsoft Powerpoint to a Google Presentation that can be previewed from right inside Socialwok. By clicking the preview option, we preview the Google Presentation in a new tab.

Successful posting of the Google Presentation shared in a feed
Previewing the Google Presentation from the Socialwok web using the Google Data API

Using Google Docs viewer to preview Google Docs, PDF, PPT, XLS in Socialwok Desktop and Mobile Web

Another really cool feature introduced in our recent release is the ability to preview Google Docs, PDF, PPT, or XLS files directly in the Socialwok web interface with no download required. Using the Google Documents Viewer, our users can preview any Google Document, pdf, ppt or XLS directly from their desktop web browser or their HTML5-capable WebKit mobile browser (iPhone and Android).

Preview of a shared pdf document from Socialwoks desktop web application using the Google Docs Viewer
The same applies to other common file formats like Adode Portable File (pdf), Microsoft Excel (xls) and other Google Docs supported formats. Socialwok provides businesses with cloud based access to their office documents from both the web and the mobile (HTML 5).

Preview of a shared pdf document using Google Docs viewer from Socialwoks HTML 5 mobile web page

Sharing Google Docs using the Google Data APIs

We use the Google GData APIs to upload the document, and then proceed to attach it to a new Socialwok status update. The latest version of the Google GData Java APIs work great on Google App Engine Java, with almost no configuration. Now, before accessing and manipulating Google Docs or Calendar from the GData APIs, we must make sure that we have authentication credentials for the current user. Our application stores the existing user in the session. Heres how we do that using the low-level Java datastore APIs.
Entity currentUser = (Entity) httpRequest.getSession()
.getAttribute("userObject");
Each Google Apps or Google Accounts user actually logs in to Socialwok using their own Google authentication credentials. We accomplish this using the Hybrid Step2 OpenID/OAuth authentication flow. You can learn more about this flow here. From this flow, we gain the users credentials via OAuth, and store the OAuth token and token secret in another child object of our user:
Query query = new Query("GoogleAccount", currentUser.getKey());
Entity googleAccount = dataService.prepare(query).asSingleEntity();
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(
oauthProps.getProperty("google.consumerKey"));
oauthParameters.setOAuthConsumerSecret(
oauthProps.getProperty("google.consumerSecret"));
oauthParameters.setOAuthToken(
(String) googleAccount.getProperty("oauthToken"));
oauthParameters.setOAuthTokenSecret(
(String) googleAccount.getProperty("oauthSecret"));
We now use the Google Docs GData APIs to upload the document. Google GData APIs use Atom Feeds to retrieve and upload new information into existing Google Services like Google Docs. For Google Docs, this involves using the MediaStreamSource API.
DocsService service = new DocsService("Socialwok-v1");
service.setOAuthCredentials(oauthParameters,signer);

MediaStreamSource streamSource = new MediaStreamSource(fileInputStream,
contentType);
streamSource.setName(filename);
DocumentListEntry inserted = service.insert( new URL(
"http://docs.google.com/feeds/default/private/full/?convert="+
doConvert), DocumentListEntry.class, streamSource);
Notice the URL used for uploading the Google Doc. This is a universal URL for uploading new files to Google Docs. The convert parameter is actually very important; it determines whether the file will be converted to a new Google Doc (Document, Presentation, Spreadsheet), or will be uploaded as an arbitrary file without any conversion.

Previewing Google Docs using the Google Data APIs

After uploading the presentation to Google Docs, sometimes another user who is following you would like to preview that presentation quickly without having to fully open Google Docs in order to see the file. To do this, we examine how the actual Google Docs interface previews documents. Each of the 3 different types of Google Docs has its own specific viewer or previewing mode url to simply view the files.

For documents, when you are in the editor, if you click "View," and then click "Webpage preview." You will then get your Google Doc displayed as a webpage. The URL of this "viewer" is:
https://docs.google.com/a/socialwok.com/View?docid=0Adnk...dkZmNoOTIyNQ
For reference, the editor link for this document is:
https://docs.google.com/a/socialwok.com/Doc?docid=0Adnk...dkZmNoOTIyNQ
As you can see the transformation between these 2 URLs is quite simple. Therefore we can perform the following to actually bring up a viewable Google Doc in Socialwok.
  1. Get the Google Docs entry from the datastore.
    Entity fileAttachment = datastoreService.get(attachmentKey);
    String entryId = fileAttachment.getProperty("fileId");
  2. Get the Google Docs entry from the Google Docs GData Service.
    URL entryUrl = new URL(entryId);
    DocumentListEntry docEntry = service.getEntry(entryUrl,
    DocumentListEntry.class);
  3. Get the document link from the Google Docs Entry
    String docLink = docEntry.getDocumentLink().getHref();
  4. Convert the link to the viewer.
    if (docEntry instanceof DocumentEntry) {
    docLink = docLink.replace("/Doc","/View");
    }
Its important to note that since Socialwok uses your Google Apps or Gmail account to login through OpenID, you are already logged into Google services. In Socialwok, you can click the Mail, Calendar, or Docs links to access each of those Google products. Because of this authentication, its very easy for us to place this link into an <iframe> tag and embed this in our Socialwok interface. Heres the GWT code to do just that:
import com.google.gwt.user.client.ui.Frame;
...
Frame viewerFrame = new Frame();
viewerFrame.setUrl(docLink);
viewerFrame.setHeight("500px");
viewerFrame.setWidth("600px");
containerWidget.add(viewerFrame);
Heres are the different Google Docs editor to viewer mappings, with the changes highlighted:



Document TypeActionURL
DocumentEdithttps://docs.google.com/a/<domain>/Doc?docid=...
DcumentViewhttps://docs.google.com/a/<domain>/View?docid=...
PresentationEdithttps://docs.google.com/a/<domain>/present/edit?id=...
PresentationViewhttps://docs.google.com/a/<domain>/present/view?id=...
SpreadsheetEdithttps://spreadsheets.google.com/a/<domain>/ccc?key=...
SpreadsheetViewhttps://spreadsheets.google.com/a/<domain>/lv?key=...

Previewing PDF, PPT, and XLS files using the Google Document Viewer

Google Docs has a fantastic feature that lets users preview any PDF,PPT, XLS, or TIFF file via a HTTP link. You can try the viewer out at http://docs.google.com/viewer. If you look at the bottom of this page, there is information on how to construct your own URLs so that you can either link or embed the viewer in your own web sites. Heres the rundown on the parameters:

Technical Documentation - Instructions for building your own URLs
All viewer URLs should use the path http://docs.google.com/viewer . This path accepts two parameters:
url : The URL of the document to view. This should be URL-encoded.
embedded : If set to true , the viewer will use an embedded mode interface.

For example, if you wanted to view the PDF at
http://labs.google.com/papers/bigtable-osdi06.pdf,
you would use the URL:
http://docs.google.com/viewer?url=http%3A%2F%2Flabs.google.com%2Fpapers%2Fbigtable-osdi06.pdf

We will use the embedded parameter to display the viewer in our own interface. To construct the URL to the embedded viewer with the example given by Google, we get the following:
http://docs.google.com/viewer?url=http%3A%2F%2Flabs.google.com%2Fpapers%2Fbigtable-osdi06.pdf&embedded=true
We can then place this URL in an <IFRAME> element to embed the viewer in our application.
<iframe src="">http://docs.google.com/viewer?url=http%3A%2F%2Flabs.google.com%2Fpapers%2Fbigtable-osdi06.pdf&embedded=true" style="width:600px; height:500px;" frameborder="0"></iframe>
Heres how to create the same using the GWT Frame widget in Java.
import com.google.gwt.user.client.ui.Frame;
...
Frame viewerFrame = new Frame();
viewerFrame.setUrl("http://docs.google.com/viewer?url=http%3A%2F%2Flabs.google.com%2Fpapers%2Fbigtable-osdi06.pdf&embedded=true");
viewerFrame.setHeight("500px");
viewerFrame.setWidth("600px");
containerWidget.add(viewerFrame);
As you can see, its really quite easy to use the embedded Google Document Viewer. You can check out more information on the Google Document Viewer on the Google Docs blog.

Extending Socialwok using our APIs. Meet Socialwok at Google IO

The above example of sharing and previewing Google Docs using the Google Docs viewer is the result of developers pushing the envelope using Google App Engine with the ever expanding suite of Google APIs. Here at Socialwok, we continue to challenge ourselves in creating innovative web services that leverage on all that cloud computing has to offer. We will continue to have more features and integrations in the future, and we will be opening up Socialwoks APIs for developers in the next few months. You can follow all these developments at our official blog http://blog.socialwok.com.

We would love to hear from fellow developers who would like to create applications to extend Socialwok; please email us at info@socialwok.com.

Socialwok will be at Google I/O in the Developer Sandbox in May where we will be giving demos of a new major add-on service to Socialwok. If you would like a demo, email us at info@socialwok.com and drop by the Socialwok demo station at Google I/O.

Read more »