<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" media="screen" href="/~d/styles/atom10full.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:openSearch="http://a9.com/-/spec/opensearch/1.1/" xmlns:georss="http://www.georss.org/georss" xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:feedburner="http://rssnamespace.org/feedburner/ext/1.0" gd:etag="W/&quot;CEEGRH4-cSp7ImA9WhVUGEQ.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227</id><updated>2012-05-24T13:50:25.059-07:00</updated><category term="Apps Script" /><category term="Resellers" /><category term="Google Sites API" /><category term="Freemium" /><category term="analytics" /><category term="Gmail APIs" /><category term="Google Data Protocol" /><category term="Charts" /><category term="Community" /><category term="SaaS" /><category term="Staff Picks" /><category term="python" /><category term="Cloud Storage API" /><category term="Administrative APIs" /><category term="Guest Post" /><category term="oauth" /><category term="Android" /><category term="Google I/O" /><category term="Google Contacts API" /><category term="Google Forms" /><category term="Mobile" /><category term="mpstaffpick" /><category term="java" /><category term="Google Prediction API" /><category term="Chrome OS" /><category term="webinar" /><category term="Gadgets" /><category term="Auth" /><category term="Google Docs API" /><category term="OpenID" /><category term="Groups" /><category term="App Engine" /><category term="billing" /><category term="ISVs" /><category term="PHP" /><category term="Developers" /><category term="AdSense" /><category term="googlenew" /><category term="Google Talk" /><category term="Ruby" /><category term="Google Calendar API" /><category term="Marketplace" /><category term="Google Profiles API" /><category term="marketing" /><category term="Google Tasks API" /><category term="Google Spreadsheets API" /><category term="Fusion Tables" /><category term="JavaScript" /><category term="Drive SDK" /><category term="Marketplace ISV Guest" /><category term=".NET" /><title>Google Apps Developer Blog</title><subtitle type="html" /><link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/posts/default" /><link rel="alternate" type="text/html" href="http://googleappsdeveloper.blogspot.com/" /><link rel="next" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default?start-index=26&amp;max-results=25&amp;redirect=false&amp;v=2" /><author><name>Google PR</name><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><generator version="7.00" uri="http://www.blogger.com">Blogger</generator><openSearch:totalResults>193</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="self" type="application/atom+xml" href="http://feeds.feedburner.com/GoogleAppsDeveloperBlog" /><feedburner:info uri="googleappsdeveloperblog" /><atom10:link xmlns:atom10="http://www.w3.org/2005/Atom" rel="hub" href="http://pubsubhubbub.appspot.com/" /><feedburner:emailServiceId>GoogleAppsDeveloperBlog</feedburner:emailServiceId><feedburner:feedburnerHostname>http://feedburner.google.com</feedburner:feedburnerHostname><entry gd:etag="W/&quot;DkMNRnY_fCp7ImA9WhVUFkQ.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-1192951905840907368</id><published>2012-05-22T06:48:00.000-07:00</published><updated>2012-05-22T06:48:17.844-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-22T06:48:17.844-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Introducing Versions and Libraries in Apps Script</title><content type="html">&lt;p&gt;Have you ever written a particular piece of code over and over again?  Or
  used scripts to do something that you thought others might want to do as well?
   Starting today, you’ll be able to share and reuse those scripts as libraries,
  right from inside Google Apps Script.&lt;/p&gt;
&lt;h2&gt;Why use a Script Library&lt;/h2&gt;
&lt;p&gt;I often write scripts which check the National Weather Service for
  relevant weather-related information.  This allows me to send myself an email
  if it’s going to rain, reminding me to bring an umbrella to work, or to
  annotate my spreadsheet of running workouts with the temperature of the day.&lt;/p&gt;
&lt;p&gt;Remembering how to query the National Weather Service every time I write
  a script is a daunting task, however.  They have a complicated XML format that
   is tricky to parse.  As a result, I end up just copying and pasting code each
  time.  This is not only error-prone, but also has the big disadvantage that I
  have to fix all of my scripts one by one whenever the Weather Service’s XML
  format changes.&lt;/p&gt;
&lt;p&gt;The code I use to query the National Weather Service is a perfect use
  case for a library.  By using a library, I no longer have to copy and paste
  code in my script project.  Since logic is centralized, updates need to be
  applied just once.  And now I am able to share my library with other
  developers who can benefit from the work I’ve already done.&lt;/p&gt;
&lt;h2&gt;Writing a Library&lt;/h2&gt;
&lt;p&gt;Libraries are written just like any other Apps Script project.  A good
  library has a clean API which is also well documented.  Here’s a code snippet
  from my WeatherService library:&lt;/p&gt;
&lt;br /&gt;
&lt;pre class="prettyprint"&gt;
/**
 * Queries the National Weather Service for the weather
 * forecast of the given address. Example:
 * 
 * &amp;lt;pre&amp;gt;
 * var chances = WeatherService
 *                 .getPrecipitation(&amp;quot;New York, NY&amp;quot;);
 * var fridayChance = chances[“Friday”];
 * Logger.log(fridayChance + “% chance of rain on Friday!”);
 * &amp;lt;/pre&amp;gt;
 * 
 * @param {String} address The address to query the
 *          temperature for, in any format accepted by
 *          Google Maps (can be a street address, zip
 *          code, city and state, etc)
 *          
 * @returns {JsonObject} The precipitation forecast, as
 *          map of period to percentage chance of
 *          precipitation. Example:
 * 
 * &amp;lt;pre&amp;gt;
 * { Tonight: 50, Friday: 30, Friday Night: 40, ... }
 * &amp;lt;/pre&amp;gt;
 */
function getPrecipitation(address) {
  // Code for querying weather goes
  // here...
}
&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;
  Notice how detailed the documentation is. We know that good documentation
  makes for a great library. So, for every library Apps Script will also
  auto-generate a documentation page based on the code comments using &lt;a
    href="https://developers.google.com/closure/compiler/docs/js-for-compiler"&gt;the
    JSDoc format&lt;/a&gt;.  If you want a method in your code to not be exposed to users,
  simply end its name with an underscore.
&lt;/p&gt;
&lt;h2&gt;Saving Versions&lt;/h2&gt;
&lt;p&gt;
  Before code can be used as a library, a version of it needs to be saved.
   Versions are a new concept in Apps Script, and they represent a snapshot of
  your project which won’t change even as changes are made to the script code.
  Versions are useful because they allow you to change your library code without
  breaking existing users.  Once you’re happy with the changes you’ve made, you
  can then save a new version. Please see the user guide for &lt;a
    href="https://developers.google.com/apps-script/guide_versions#createVersion"&gt;s&lt;/a&gt;&lt;a
    href="https://developers.google.com/apps-script/guide_versions#createVersion"&gt;aving&lt;/a&gt; a
  version and &lt;a
    href="https://developers.google.com/apps-script/guide_libraries#writingLibrary"&gt;sharing&lt;/a&gt; your
  code as a library is easy.
&lt;/p&gt;
&lt;h2&gt;Using Libraries&lt;/h2&gt;
&lt;p&gt;
  Using a library only takes a few steps. To be able to use a library, the owner
  of the library must share the library and its project key with you. You can
  follow &lt;a
    href="https://developers.google.com/apps-script/guide_libraries#useLibrary"&gt;these&lt;/a&gt; instructions
  to then use a library. To use this National Weather Service library, please
  visit this &lt;a
    href="https://developers.google.com/apps-script/notable-script-libraries"&gt;page&lt;/a&gt;
  for project key.
&lt;/p&gt;
&lt;p&gt;
  &lt;a
    href="http://1.bp.blogspot.com/-euvz02_rTXw/T7qVzTDj4XI/AAAAAAAAAXU/CxMR1D4YJr4/s1600/image02.png"
    imageanchor="1" style=""&gt;&lt;img border="0" height="298" width="500"
    src="http://1.bp.blogspot.com/-euvz02_rTXw/T7qVzTDj4XI/AAAAAAAAAXU/CxMR1D4YJr4/s1600/image02.png" /&gt;&lt;/a&gt;


&lt;/p&gt;
&lt;h2&gt;Useful Features of Libraries&lt;/h2&gt;
&lt;p&gt;Script Libraries come with three interesting features.&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Documentation - In the Script Libraries dialog, you can click on the
    title link to navigate to documentation page for the library. See &lt;a
    href="https://docs.google.com/a/macros/google.com/library/d/MboddKgXEUjdhPcUtXNjfyS5VdJBoJ-NW/2"&gt;example&lt;/a&gt;
    of a generated documentation.
  &lt;/li&gt;
  &lt;li&gt;Development Mode can be used to test changes to a library without
    saving a new version. See our&lt;a
    href="https://developers.google.com/apps-script/guide_libraries"&gt; &lt;/a&gt;&lt;a
    href="https://developers.google.com/apps-script/guide_libraries"&gt;User
      Guide&lt;/a&gt; for more details
  &lt;/li&gt;
  &lt;li&gt;Autocomplete in Script Editor - Typing in the editor will
    auto-complete your library function names.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
  &lt;a
    href="http://3.bp.blogspot.com/-va2dG81zKss/T7qSO5LfmyI/AAAAAAAAAXE/i6hNsHC7as8/s1600/image01.png"
    imageanchor="1" style=""&gt;&lt;img border="0" height="124" width="400"
    src="http://3.bp.blogspot.com/-va2dG81zKss/T7qSO5LfmyI/AAAAAAAAAXE/i6hNsHC7as8/s1600/image01.png" /&gt;&lt;/a&gt;
&lt;/div&gt;


&lt;/p&gt;
&lt;h2&gt;Interesting Libraries You Can Use&lt;/h2&gt;
&lt;p&gt;
  To get started on using Script Libraries, you can find a &lt;a
    href="https://developers.google.com/apps-script/notable-script-libraries"&gt;list&lt;/a&gt; of
  useful libraries contributed by two of our top contributors - James Ferreira
  and Romain Vialard. You can also find a detailed user guide on managing &lt;a
    href="https://developers.google.com/apps-script/guide_versions"&gt;versions&lt;/a&gt; and
  &lt;a href="https://developers.google.com/apps-script/guide_libraries"&gt;libraries&lt;/a&gt;.
  We hope you enjoy using libraries.
&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="5" cellspacing="0"&gt;
  &lt;tbody&gt;
    &lt;tr style="background-color: #f2f2f2;"&gt;
      &lt;td&gt;&lt;img
        src="http://1.bp.blogspot.com/-zT-eKHJjTWo/TmavsrZP_EI/AAAAAAAAANQ/9m3NBOzmNno/s320/image01.jpg"
        width="74" /&gt;&lt;/td&gt;
      &lt;td valign="top"&gt;&lt;span class="largefont"&gt;Gustavo Moura&lt;/span&gt;&lt;br /&gt;
        &lt;div class="bio"&gt;
          &lt;br /&gt; Gustavo has been a Software Engineer at Google since 2007. He
          has been part of the Google Docs team since 2009. Prior to that, he
          worked on AdWords. In his free time he plays soccer.
        &lt;/div&gt; &lt;br /&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-1192951905840907368?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/DozUSqNkx_8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/1192951905840907368/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=1192951905840907368&amp;isPopup=true" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1192951905840907368?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1192951905840907368?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/DozUSqNkx_8/introducing-versions-and-libraries-in.html" title="Introducing Versions and Libraries in Apps Script" /><author><name>Saurabh Gupta</name><uri>http://www.blogger.com/profile/02739612529569251578</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-euvz02_rTXw/T7qVzTDj4XI/AAAAAAAAAXU/CxMR1D4YJr4/s72-c/image02.png" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/introducing-versions-and-libraries-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CUYNQnY5eip7ImA9WhVUFkw.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-5118785405151254977</id><published>2012-05-21T08:13:00.000-07:00</published><updated>2012-05-21T08:13:13.822-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-21T08:13:13.822-07:00</app:edited><title>MindMeister mind mapping and Google Drive</title><content type="html">&lt;p&gt;&lt;i&gt;Editor’s note: This is a guest post by Laura Bârlădeanu, lead programmer at &lt;a href="http://www.mindmeister.com"&gt;MindMeister&lt;/a&gt;. &lt;br/&gt;-- Steve Bazyl&lt;/i&gt;&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="http://www.mindmeister.com/"&gt;MindMeister&lt;/a&gt; is a market innovator for providing collaborative online mind mapping solutions. Launched in May 2007, our site has since attracted hundreds of thousands of businesses, academic institutions and creative consumers who have mapped over 100 million ideas online. We were one of a few web applications invited to take part in the Google Drive launch earlier this year.&lt;/p&gt;

&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;The goal was to provide users with an intuitive &lt;a href="https://chrome.google.com/webstore/detail/bdehgigffdnkjpaindemkaniebfaepjm"&gt;integration&lt;/a&gt; between Google Drive and Mindmeister that would cover all the cases provided by the Google Drive guidelines at that time:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Create mind maps&lt;/em&gt; directly from Google Drive&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Open mind map&lt;/em&gt; files from Google Drive using MindMeister&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Create a mind map&lt;/em&gt; file on Google Drive from MindMeister&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aside from these main integration points, we wanted to make use of the SDK and provide many useful Google Drive features, so we added a few more requirements to the list:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Export&lt;/em&gt; all the user’s maps as a backup .zip file on Google Drive&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Import&lt;/em&gt; a file from Google Drive using the Google File Picker&lt;/li&gt;
  &lt;li&gt;Attach a file from Google Drive directly to a node in a mind map&lt;/li&gt;
  &lt;li&gt;Provide users the possibility to &lt;em&gt;share&lt;/em&gt; mind maps with their Google contacts&lt;/li&gt;
  &lt;li&gt;Provide users with an application setting that would allow them to &lt;em&gt;sync&lt;/em&gt; all their mind maps with Google Drive&lt;/li&gt;
  &lt;li&gt;Allow Google users opening the same file from Google Drive to &lt;em&gt;collaborate&lt;/em&gt; in real time on the mind map directly in MindMeister&lt;/li&gt;
  &lt;li&gt;Enable users to login with their Google account without providing any extra information&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;Authentication and authorization&lt;/h2&gt;

&lt;p&gt;Google Drive applications &lt;a href="https://developers.google.com/drive/about_auth"&gt;are required to use OAuth 2.0&lt;/a&gt; as an authorization mechanism, and are recommended to use &lt;a href="http://openid.net/connect/"&gt;OpenID Connect&lt;/a&gt; for &lt;a href="https://developers.google.com/accounts/docs/OAuth2Login"&gt;login&lt;/a&gt;. The authorization scope for Drive files is added by default for all registered drive apps. Additionally, the application can require extra scopes that would fit its needs. For our requirements, we needed the following scopes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;https://www.googleapis.com/auth/drive.file&lt;/code&gt; (Drive)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;https://www.google.com/m8/feeds/&lt;/code&gt; (Contacts)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;https://www.googleapis.com/auth/userinfo.profile&lt;/code&gt; (User information)&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;https://www.googleapis.com/auth/userinfo.email&lt;/code&gt; (User email)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, we didn’t want the user to turn away from our application by being asked for too many scopes straight from the beginning. Instead, we defined sets of actions that required a subset of these scopes:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;[‘drive’, ‘profile’, ‘email’]&lt;/code&gt; - any Google Drive action&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;[‘profile’, ‘email’]&lt;/code&gt; - login with a Google account&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;[‘contacts’, ‘profile’, ‘email’]&lt;/code&gt; - access the user’s Google contacts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Whenever the user wanted to execute an action that would require more scopes than they initially provided, we redirected them to a Google authorization dialog that requested the extra scope. Upon authorization, we stored the individual refresh tokens for each combination of scopes in a separate model (&lt;code&gt;UserAppTokens&lt;/code&gt;).&lt;/p&gt;
&lt;p/&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/--PeBUUD0na4/T7pWtOP9ntI/AAAAAAAAAkI/LIVHA1cQSuI/s1600/image01.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="78" width="400" src="http://2.bp.blogspot.com/--PeBUUD0na4/T7pWtOP9ntI/AAAAAAAAAkI/LIVHA1cQSuI/s400/image01.png" /&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Whenever the application needed the refresh token for a set of scopes (eg. for &lt;code&gt;[‘profile’, ‘email’]&lt;/code&gt;) it would fetch the refresh token from the database which corresponded to a superset of the required scopes (eg. &lt;code&gt;[‘drive’, ‘profile’, ‘email’]&lt;/code&gt; would fit for the required &lt;code&gt;[‘profile’, ‘email’]&lt;/code&gt;). The access token would then be obtained from Google and stored in the session for future requests.&lt;/p&gt;

&lt;h2&gt;Challenges&lt;/h2&gt;

&lt;p&gt;The main challenge we encountered during design and implementation was dealing with the special cases of multiple users (Google users or internal users) editing on the same map which is a Google Drive file, as well as dealing with the special cases of the map being edited in multiple editors. We also had to find a solution for mapping the Google Drive user’s permissions (&lt;code&gt;owner&lt;/code&gt;, &lt;code&gt;reader&lt;/code&gt;, or &lt;code&gt;writer&lt;/code&gt;) to the MindMeister’s existing permission mechanism.&lt;/p&gt;

&lt;p&gt;The MindMeister &lt;a href="https://chrome.google.com/webstore/detail/bdehgigffdnkjpaindemkaniebfaepjm"&gt;application&lt;/a&gt; is registered for opening four types of files: our own &lt;code&gt;.mind&lt;/code&gt; format, MindManager’s &lt;code&gt;.mmap&lt;/code&gt; format, Freemind’s &lt;code&gt;.mm&lt;/code&gt; format, as well as &lt;code&gt;.xmind&lt;/code&gt;. However, since these formats are not 100% compatible with each other, there is always a chance of losing more advanced features when opening a file in a format other than .mind. We wanted to provide the user with the possibility to chose whether the opened file would be saved in its original format, thus risking some features loss, or saving the file in MindMeister format. This option should be per user, per file and with the possibility to be remembered for future files.

&lt;h2&gt;Solutions&lt;/h2&gt;

&lt;p&gt;After analyzing the requirements and the use cases, we designed the following architecture:&lt;/p&gt;
&lt;p/&gt;
&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-Uw1Yj1hW25c/T7pUmKxhnjI/AAAAAAAAAj8/IdkEeigNYXs/s1600/image00.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0"  width="460" src="http://2.bp.blogspot.com/-Uw1Yj1hW25c/T7pUmKxhnjI/AAAAAAAAAj8/IdkEeigNYXs/s460/image00.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;h2&gt;Out of sync maps and files&lt;/h2&gt;

&lt;p&gt;Using the revision fields in both Map and DriveData we always know if the map has been edited on MindMeister’s side without it being synced with the corresponding file on Google Drive. On the other hand, the token field from DriveData represents the file’s MD5 checksum at the moment of the last update and is supplied via the Google Drive SDK. So if the file is edited externally using another application than MindMeister, we have a mechanism in place for detecting this issue and presenting the user with a few courses of action.&lt;/p&gt;

&lt;h2&gt;Handling 3rd party formats&lt;/h2&gt;

&lt;p&gt;Upon opening a file that has a different format than &lt;code&gt;.mind&lt;/code&gt;, the user is prompted with an option dialog where they can chose if they want the file to be saved back in the same format or in MindMeister’s own format. These options are then remembered in the current session and the per map settings are stored in the &lt;code&gt;extension&lt;/code&gt; (the original format) and &lt;code&gt;save_extension&lt;/code&gt; (the format to save back in) fields present in the &lt;code&gt;DriveData&lt;/code&gt; model.&lt;/p&gt;

&lt;h2&gt;Handling user’s permissions&lt;/h2&gt;

&lt;p&gt;A map on MindMeister can always be shared with other MindMeister users and the collaborators can have reading or writing access to the map. However, only some of these users will have a corresponding Google account with access to the MindMeister Google Drive application and furthermore, only some of them will have access to the same file on Google Drive with writing permission. This is why it is important for us to know which users can write back to the file and the solution for these access levels was achieved with the help of the permission field in the &lt;code&gt;DriveDataRight&lt;/code&gt; model.&lt;/p&gt;

&lt;h2&gt;Results&lt;/h2&gt;

&lt;p&gt;Now more than two weeks on from the Google Drive launch and we can confidently say that our integration was successful, with more than 14,000 new users using Google login and with over 7,000 users that have enabled the Google Drive integration. All in all, the &lt;a href="https://developers.google.com/drive/"&gt;Google Drive SDK&lt;/a&gt; was very easy to use and well documented. The developer support, especially, was always there to help and our contacts were open to our suggestions.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" style="width:92px;" width="92px" src="http://2.bp.blogspot.com/-N2U0pHN0vWs/T7pRl7imI7I/AAAAAAAAAjg/clcDSBG2kDY/s200/image02.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Laura Bârlădeanu&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Laura is the lead programmer at &lt;a href="http://www.mindmeister.com"&gt;MindMeister&lt;/a&gt;, an online mind mapping tool built in HTML5 that features real-time collaboration.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-5118785405151254977?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/BmbGSq4ShMM" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/5118785405151254977/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=5118785405151254977&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5118785405151254977?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5118785405151254977?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/BmbGSq4ShMM/mindmeister-mind-mapping-and-google.html" title="MindMeister mind mapping and Google Drive" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/--PeBUUD0na4/T7pWtOP9ntI/AAAAAAAAAkI/LIVHA1cQSuI/s72-c/image01.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/mindmeister-mind-mapping-and-google.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkINRXg7eSp7ImA9WhVVFk0.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-5561186652169622039</id><published>2012-05-09T14:56:00.001-07:00</published><updated>2012-05-09T14:56:34.601-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-09T14:56:34.601-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Insider Tips for Using Apps Script and Spreadsheets</title><content type="html">&lt;p&gt;My role in Google Docs is to help manage many projects across Google
  Docs/Drive. As a part of my job, I ask for a fair amount of data from all of
  those teams and generate reports on project/feature status. To make this much
  simpler for everyone involved, I have created a lot of simple tools using
  Google Spreadsheets and Apps Script (as well as a lot of complex tools) that
  make it easier for collaborators to enter data and for me to collect that data
  and create reports. Below is a pair of foundational techniques that I include
  in nearly every Spreadsheet/Apps Script tool I build.&lt;/p&gt;
&lt;h2&gt;Load Settings From a Configuration Sheet&lt;/h2&gt;
&lt;p&gt;I have a dozens of scripts generating reports. I use a technique where I
  set up a dedicated sheet for script configuration and read values from the
  sheet during script execution. A simple configuration sheet makes this much
  more straightforward.&lt;/p&gt;
&lt;p&gt;
&lt;a href="http://4.bp.blogspot.com/-z09U16nIp3I/T6rjYMiTdfI/AAAAAAAAAV4/KEPSHIhUqac/s1600/image00.png" imageanchor="1" style=""&gt;&lt;img border="0" height="116" width="550" src="http://4.bp.blogspot.com/-z09U16nIp3I/T6rjYMiTdfI/AAAAAAAAAV4/KEPSHIhUqac/s400/image00.png" /&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;With a globally accessible array, globals, you can then load the
  “settings” from the configuration (sheet SHT_CONFIG, here) at any entrypoint
  to the script.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// globally accessible variables
var SHT_CONFIG = 'Config';
var globals = new Array();

function entryPoint() {
  globals = (globals.length == 0) ? LoadGlobals(
      SpreadsheetApp.getActive(), SHT_CONFIG)
      : globals;
  // your code goes here
}
    &lt;/pre&gt;
&lt;p&gt;The LoadGlobals function, below, parses the data in the first three
  columns of the workbook and sheet name passed to it. You can even include a
  fourth column (or more!) explaining what the variables do, and they’ll just be
  ignored - though hopefully not by your users!&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// Generate gloabal variables to be loaded into globals array
function LoadGlobals_(wb, configSheet) {
  var configsheet = wb.getSheetByName(configSheet);
  var tGlobals = new Array();

  // Config data is structured as VARIABLE, ISARRAY, VALUE(S)
  // and includes that as the header row
  var cfgdata = configsheet.getDataRange().getValues();
  for (i = 1; i &lt; cfgdata.length; i++) {
    switch (cfgdata[i][1]) {
      case 'ARRAY':
        // treat as an array - javascript puts a null value in the
        // array if you split an empty string...
        if (cfgdata[i][2].length == 0) {
          tGlobals[cfgdata[i][0]] = new Array();
        } else {
          tGlobals[cfgdata[i][0]] = cfgdata[i][2].split(',');
        }
        break;
      // Define your own YOURDATATYPE using your customTreatment function (or
      // just perform the treatment here)
      case 'YOURDATATYPE':
        tGlobals[cfgdata[i][0]] = customTreatment(cfgdata[i][2]);
        break;
      default: // treat as generic data (string)
        tGlobals[cfgdata[i][0]] = cfgdata[i][2];
    }
  }
  return tGlobals
}
&lt;/pre&gt;
&lt;p&gt;As long as you’ve loaded the global values during the script execution,
  you can refer to any of the values with, for example, globals.toList. For
  instance:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
function getToList() {
  return globals.toList.join(‘,’);
  // or
  return globals[‘toList’].join(‘,’);
}     
  &lt;/pre&gt;
&lt;h2&gt;Stop Worrying About Column Numbers&lt;/h2&gt;
&lt;p&gt;Asking colleagues to enter tracking data so they don’t have to report
  their own statuses is one thing. Asking them to enter tracking data in a
  specific format, within a specific column layout, in a way that doesn’t mesh
  with their existing processes is entirely different. So, I use the following
  technique, where I rely on column names and not column ordering. The code
  below lets me do just that by fetching a key-value object for column headings
  and their position in a worksheet.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
// Returns key-value object for column headings and their column number.
// Note that these are retrieved based on the array index, which starts at 0
// the columns themselves start at 1...
// pass header row of data (array) and an array of variables/column names:
// eg: BUG_COL_ARRAY['id'] = 'Id';
function ColNumbers(hArray, colArray) {
  for (oname in colArray) {
    this[oname] = getColIndex(hArray, colArray[oname]);
  }
}

// -----------------------------------------------------------------------------
function getColIndex(arr, val) {
  for ( var i = 0; i &lt; arr.length; i++) {
    if (arr[i].toLowerCase() == val.toLowerCase()) {
      return i;
    }
  }
  return -1;
}    
  &lt;/pre&gt;
&lt;p&gt;With the associative array defined, below, I can ask Apps product
  managers to add (or rename) columns to their feature tracking sheets and then
  extract features from every apps product team in one fell swoop (a future
  post). Each product team can set their columns up in whatever order works best
  for them.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// key columns in the app feature sheets
var COLS_KEYAPPCOLS = new Array();
COLS_KEYAPPCOLS[‘feature’] = ‘Feature Title’;
COLS_KEYAPPCOLS[‘desc’] = ‘Description’;
COLS_KEYAPPCOLS[‘visible’] = ‘Visible’;
COLS_KEYAPPCOLS[‘corp’] = ‘Corp Date’;
COLS_KEYAPPCOLS[‘prod’] = ‘Prod Date’;    
    &lt;/pre&gt;
&lt;p&gt;What does this do for me, really? I reuse this code for every project of
  this sort. The steps to reuse are then:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Include the code&lt;/li&gt;
  &lt;li&gt;Build the associative array&lt;/li&gt;
  &lt;li&gt;Create a new ColNumbers object, as below&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="prettyprint"&gt;
var curFeatures = curSheet.getDataRange().getValues();
var curCols = new ColNumbers(curFeatures[0], COLS_KEYAPPCOLS);
  &lt;/pre&gt;
&lt;p&gt;I can, from now on, refer to the Description column using something like
  curCols.desc when referencing any of the products’ data. The Spreadsheets team
  may list new feature descriptions in the second column, and the Documents team
  may list new feature descriptions in the fourth column. I no longer worry
  about that.&lt;/p&gt;
&lt;p&gt;As a bonus, I can define the columns and ordering to be used in a report
  in my config sheet (see above). If I’ve defined reportcols as feature, desc,
  prod in my config sheet, I can generate a report very simply:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
// Iterate through the rows of data, beginning with 1 (0 is the header)
for ( var fnum = 1; fnum &lt; curFeatures.length; fnum++) {
  // Iterate through each of the fields defined in reportcols
  for ( var cnum = 0; cnum &lt; globals.reportcols.length; cnum++) {
    outputvalue = curFeatures[fnum][curCols[globals.reportcols[cnum]]];
    // outputvalue is what you want to put in your report.
  }
}    
    &lt;/pre&gt;
&lt;p&gt;You could do that a lot more simply, but if we want to use the ‘Corp
  Date’ I only need to change the value in the config sheet to feature, desc,
  corp and I’m done - you’d have to change the code.&lt;/p&gt;
&lt;p&gt;
  Collecting and crunching data in a Google Spreadsheet becomes a lot easier if
  you use Apps Script. Trust me, it makes your life a lot easier. Try it now by
  copying &lt;a
    href="https://docs.google.com/spreadsheet/ccc?key=0AoILX-vp-7t_dHFsVmFMYTRTSG9nNWNMX1QwNWtMWEE#gid=0"&gt;this
    spreadsheet&lt;/a&gt;
&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;

&lt;table cellpadding="5" cellspacing="0"&gt;
  &lt;tbody&gt;
    &lt;tr style="background-color: #f2f2f2;"&gt;
      &lt;td&gt;&lt;img border="0" height="100" width="82" src="http://4.bp.blogspot.com/-0Et3hZxm-FM/T6rklEiLsrI/AAAAAAAAAWE/76jgKflBR48/s200/IMG_1786%2B%25281%2529.JPG" /&gt;
      &lt;/td&gt;
      &lt;td valign="top"&gt;&lt;span class="largefont"&gt;Keith Howson&lt;/span&gt; &amp;nbsp;
        &lt;div class="bio"&gt;
          &lt;br /&gt;Editor’s Note: Keith is a Technical Program Manager with the
          Google Docs teams. He is a heavy user of Apps Scripts and
          Spreadsheets, and leverages the two to help the Docs teams run at full
          steam. He also just completed his first triathlon with Team in
          Training, to raise money for the &lt;a
            href="http://pages.teamintraining.org/nyc/anttry12/metaphoricallyandrew"&gt;Leukemia
            and Lymphoma Society&lt;/a&gt; - Go TEAM!
        &lt;/div&gt; &lt;br /&gt;&lt;/td&gt;
    &lt;/tr&gt;
    &lt;br /&gt;
  &lt;/tbody&gt;
&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-5561186652169622039?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/7x7Io5-4dEI" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/5561186652169622039/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=5561186652169622039&amp;isPopup=true" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5561186652169622039?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5561186652169622039?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/7x7Io5-4dEI/insider-tips-for-using-apps-script-and.html" title="Insider Tips for Using Apps Script and Spreadsheets" /><author><name>Saurabh Gupta</name><uri>http://www.blogger.com/profile/02739612529569251578</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-z09U16nIp3I/T6rjYMiTdfI/AAAAAAAAAV4/KEPSHIhUqac/s72-c/image00.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/insider-tips-for-using-apps-script-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEUASHk5cCp7ImA9WhVVFUw.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-6707117648348655392</id><published>2012-05-08T14:24:00.000-07:00</published><updated>2012-05-08T14:24:09.728-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-08T14:24:09.728-07:00</app:edited><title>Going beyond the basics: putting Drive features to work at Lucidchart</title><content type="html">&lt;p&gt;&lt;i&gt;Editor’s note: This is a guest post by Ben Dilts, CTO &amp; Co-founder of &lt;a href="http://www.lucidchart.com"&gt;Lucidchart&lt;/a&gt;. &lt;br/&gt;-- Steve Bazyl&lt;/i&gt;&lt;/p&gt;
&lt;/p&gt;

&lt;p&gt;The release of &lt;a href="http://developers.google.com/drive"&gt;Drive SDK&lt;/a&gt; allowing deep integration with &lt;a href="http://drive.google.com/start"&gt;Google Drive&lt;/a&gt; shows how serious Google is about making Drive a great platform for third parties to develop.&lt;/p&gt;

&lt;p&gt;There are a handful of obvious ways to use the SDK, such as allowing your users to open files from Drive in your application, edit them, and save them back.  Today, I'd like to quickly cover some less-obvious uses of the Drive API that we’re using at &lt;a href="http://www.lucidchart.com"&gt;Lucidchart&lt;/a&gt;.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Automated Backups&lt;/h3&gt;

&lt;p&gt;Applications have the ability to create new files on Google Drive. This is typically used for content created by applications. For example, an online painting application may save a new PNG or JPG to a user's Drive account for later editing.&lt;/p&gt;

&lt;p&gt;One feature that Lucidchart has long provided to its users is the ability to download their entire account's content in a ZIP file, in case they (or we!) later mess up that data in some way.  These backups can be restored quickly into a new folder by uploading the ZIP file back to our servers.  (Note: we’ve never yet had to restore a user account this way, but we provided it because customers said it was important to them.)&lt;/p&gt;

&lt;p&gt;The problem with this arrangement is that users have to remember to do regular backups, since there's no way for us to automatically force them to download a backup frequently and put it in a safe place. With Google Drive, we now have access to a reliable, redundant storage mechanism that we can push data to as often as we would like.&lt;/p&gt;

&lt;p&gt;Lucidchart now provides automated backups of these ZIP files to Google Drive on a daily or weekly basis, using the API for creating new files on Drive.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Publishing Content&lt;/h3&gt;

&lt;p&gt;Another use for the &lt;code&gt;files.create&lt;/code&gt; call is to publish finished content. Lucidchart, like most applications, stores its editable files in a custom format. When a user completes a diagram or drawing, they often download it as a vector PDF, image, or Microsoft Visio file to share with others.&lt;/p&gt;

&lt;p&gt;Lucidchart is now using the create file API to export content in any supported format directly to a user's Google Drive account, making it easy to sync to multiple devices and later share those files.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Indexable Text&lt;/h3&gt;

&lt;p&gt;Google Drive can't automatically index content created by Lucidchart, or any other application that saves data in a custom format, for full-text search. However, applications now have the ability to explicitly provide HTML content to Google Drive that it can then index.&lt;/p&gt;

&lt;p&gt;Indexable text provided to the Drive API is always interpreted as HTML, so it is important to escape HTML entities. And if your text is separated into distinct pieces (like the text in each shape in Lucidchart), you can improve full-text phrase searching by dividing your indexable text into one div or paragraph element per piece. Both the &lt;code&gt;files.create&lt;/code&gt; and &lt;code&gt;files.update&lt;/code&gt; calls provide the ability to set indexable text.&lt;/p&gt;

&lt;p&gt;We hope that this overview helps other developers implement better integrations into the Google Drive environment. Integrating with Drive lets us provide and improve a lot of functionality that users have asked for, and makes accessing and using Lucidchart easier overall. We think this is a great result both for users and web application developers and urge you to check it out.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" style="width:94px;" width="94px" src="http://3.bp.blogspot.com/-K6Mq8cfzCog/T6mOooSNuHI/AAAAAAAAAjQ/xgM5_qO3jYQ/s320/ben.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Ben Dilts&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Ben is the co-founder and CTO of &lt;a href="http://www.lucidchart.com/"&gt;Lucidchart&lt;/a&gt;, an online diagramming application built in HTML5 that features real-time collaboration. Previously, he was CTO of Zane Benefits where he led the development of an online health benefits administration platform which was featured on the front page of The Wall Street Journal. Ben holds a BS in Computer Science from Brigham Young University.
&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-6707117648348655392?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/F8wYXxvrgdE" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/6707117648348655392/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=6707117648348655392&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/6707117648348655392?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/6707117648348655392?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/F8wYXxvrgdE/going-beyond-basics-putting-drive.html" title="Going beyond the basics: putting Drive features to work at Lucidchart" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-K6Mq8cfzCog/T6mOooSNuHI/AAAAAAAAAjQ/xgM5_qO3jYQ/s72-c/ben.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/going-beyond-basics-putting-drive.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkAHQ3s9fyp7ImA9WhVVE0Q.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-9105599914884298183</id><published>2012-05-07T06:52:00.000-07:00</published><updated>2012-05-07T06:52:12.567-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-07T06:52:12.567-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Apps Script Dashboard and Quotas</title><content type="html">&lt;p&gt;Apps Script developers have consistently expressed the need to monitor the health of various Apps Script services. Additionally, at every forum, event, hackathon or hangout, we have heard you express a need to know and understand the quota limits in Apps Script.&lt;/p&gt;

&lt;h2&gt;Apps Script Dashboard is born!&lt;/h2&gt;

&lt;p&gt;We heard your message loud and clear, so we started working on a dashboard for Apps Script. Today, we are launching the &lt;a href="http://docs.google.com/macros/dashboard"&gt;Google Apps Script Dashboard&lt;/a&gt;. This experimental dashboard can be used to monitor the health of 10 major services. It also provides a detailed view into the quota restrictions in Apps Script.&lt;/p&gt;

&lt;p&gt;
&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-jrTS4sbHqsE/T6Luo6qZaJI/AAAAAAAAAis/Riq4jsBTsVk/s1600/dashboard-1.jpg" imageanchor="1" style=""&gt;&lt;img border="0" height="233" width="400" src="http://1.bp.blogspot.com/-jrTS4sbHqsE/T6Luo6qZaJI/AAAAAAAAAis/Riq4jsBTsVk/s400/dashboard-1.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;h2&gt;Features of the Apps Script Dashboard&lt;/h2&gt;

&lt;p&gt;
  &lt;ol&gt;
    &lt;li&gt;The dashboard offers a view into past and present states of 10 major Apps Script services. The past view goes back one week.&lt;/li&gt;
    &lt;li&gt;Each Apps Script service has three states on the dashboard: Normal Service, Known Issues and Investigating.&lt;/li&gt;
    &lt;li&gt;The Known Issues state signals that we know about the issues in that service and are working to fix them.&lt;/li&gt;
    &lt;li&gt;Quotas are displayed for three different types of user accounts: Consumer accounts (for example @gmail.com accounts), Google Apps (free) accounts, and Google Apps for Business, EDU and Government accounts.&lt;/li&gt;
  &lt;/ol&gt;
&lt;/p&gt;

&lt;h2&gt;Interesting Facts About Quotas&lt;/h2&gt;

&lt;p&gt;Did you know that consumer accounts (for example @gmail.com accounts) have quota of 1 hour of CPU time per day for executing triggers? Imagine the extent of automation that can happen for each user with triggers. And how about 20,000 calls to any external APIs. Now that  packs in a lot of 3rd party integration with the likes of Salesforce.com, Flickr, Twitter and other APIs. So, if you are thinking of building extensions in Google Apps for your product, then don’t forget to leverage the UrlFetch Service which has OAuth built-in. Event managers can create 5,000 calendar events per day and SQL aficionados get 10,000 JDBC calls a day.&lt;/p&gt;

&lt;p&gt;
&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-p6rvAYTKPqE/T6LuuWvOVQI/AAAAAAAAAi0/HyqhN_GuyxU/s1600/dashboard-2.jpg" imageanchor="1" style=""&gt;&lt;img border="0" height="308" width="400" src="http://4.bp.blogspot.com/-p6rvAYTKPqE/T6LuuWvOVQI/AAAAAAAAAi0/HyqhN_GuyxU/s400/dashboard-2.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="http://docs.google.com/macros/dashboard"&gt;Dashboard&lt;/a&gt; for more.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;br /&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://3.bp.blogspot.com/-AfmuFkbdBJQ/TjHzJVAWTXI/AAAAAAAAAMo/xVhok61rw7I/s400/Google%2BChromeScreenSnapz157.png"" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Saurabh Gupta&lt;/span&gt;   &lt;a class="alt" href="https://profiles.google.com/sg1705" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/#gluemesh" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://www.gluemesh.com/" rel="me" target="_blank"&gt;blog&lt;/a&gt;&lt;br /&gt;&lt;div class="bio"&gt;&lt;br /&gt;Saurabh is a Developer Programs Engineer at Google. He works closely with Google Apps Script developers to help them extend Google Apps. Over the last 10 years, he has worked in the financial services industry in different roles. His current mission is to bring automation and collaboration to Google Apps users.&lt;/div&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-9105599914884298183?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/g8VzIsM36I4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/9105599914884298183/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=9105599914884298183&amp;isPopup=true" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9105599914884298183?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9105599914884298183?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/g8VzIsM36I4/apps-script-dashboard-and-quotas.html" title="Apps Script Dashboard and Quotas" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-jrTS4sbHqsE/T6Luo6qZaJI/AAAAAAAAAis/Riq4jsBTsVk/s72-c/dashboard-1.jpg" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/apps-script-dashboard-and-quotas.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck4MSX44eSp7ImA9WhVWGUU.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-5392704898066187773</id><published>2012-05-02T10:36:00.003-07:00</published><updated>2012-05-02T10:49:48.031-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-05-02T10:49:48.031-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google Data Protocol" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Contacts API" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Calendar API" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Docs API" /><title>How we built a Social Network for Productivity on Top of Google Docs, Calendar, Contacts and Reader</title><content type="html">&lt;p&gt;&lt;i&gt;Editor’s note: This is a guest post by Martin Böhringer, Co-Founder and CEO of &lt;a href="http://www.hojoki.com"&gt;Hojoki&lt;/a&gt;. &lt;br/&gt;-- Steve Bazyl&lt;/i&gt;&lt;/p&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;div style="float: right;"&gt;
&lt;a href="http://3.bp.blogspot.com/-yuy-9vA1tDs/T6CDQBRsLbI/AAAAAAAAAhY/A-qWLTVm2YQ/s1600/image00.jpg" imageanchor="1"&gt;&lt;img border="0" style="border:none;" height="200" src="http://3.bp.blogspot.com/-yuy-9vA1tDs/T6CDQBRsLbI/AAAAAAAAAhY/A-qWLTVm2YQ/s400/image00.jpg" width="142" /&gt;&lt;/a&gt;&lt;/div&gt;
Honestly, we love Google’s productivity tools. Back in 2010, when I was a computer science researcher at university, I was a heavy user of Google Docs, Google Calendar, Google Contacts and Google Reader. So it was the most natural thing to put these tools to the front for integrations into our startup, Hojoki.&lt;/p&gt;
&lt;p&gt;Hojoki integrate productivity cloud apps into one newsfeed and enables sharing and discussions on top of the feed. We’ve integrated 17 apps now and counting, so it’s safe to say that we’re API addicts. Now it's Time to share with you what we learned about the Google Apps APIs!&lt;/div&gt;
&lt;p/&gt;
&lt;h3&gt;The goal: a newsfeed&lt;/h3&gt;
&lt;p&gt;Our initial reason for building Hojoki was because of the fragmentation we experience in all of our cloud apps. And all those emails. Still, there was this feeling of “I don’t know what’s going on” in our distributed teamwork. So we decided to build something like a Google+ where streams get automatically filled by activities in the apps you use.&lt;/p&gt;
&lt;p&gt;This leads to a comprehensive stream of everything that’s going on in your team combined with comments and microblogging. You can organize your stream into workspaces, which are basically places for discussions and collaboration with your team.&lt;/p&gt;
&lt;p/&gt;
&lt;div style="text-align:center;"&gt;&lt;a href="http://4.bp.blogspot.com/-heUP-W3Jhes/T6CDUc0s5XI/AAAAAAAAAhk/XemTt4b88PQ/s1600/image01.png" imageanchor="1"&gt;&lt;img border="0" height="252" src="http://4.bp.blogspot.com/-heUP-W3Jhes/T6CDUc0s5XI/AAAAAAAAAhk/XemTt4b88PQ/s400/image01.png" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p/&gt;
&lt;h3&gt;Information needs&lt;/h3&gt;
&lt;p&gt;To build this, we first need some kind of information on recent events. As we wanted to be able to aggregate similar activities and to provide a search, as well as splitting up the stream in workspaces, we also had to be able to sort events as unique objects like files, calendar entries and contacts.&lt;/p&gt;
&lt;p&gt;Further, it’s crucial to not only know what has changed, but who did it. So providing unique identities is important for building federated feeds.&lt;/p&gt;
&lt;p/&gt;

&lt;h3&gt;Google APIs&lt;/h3&gt;

&lt;p&gt;Google’s APIs share some basic architecture and structure, described in their &lt;a href="http://code.google.com/apis/gdata/"&gt;Google Data Protocol&lt;/a&gt;. Based on that, application-specific APIs provide access to the application’s data. What we use is the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google Docs: &lt;a href="https://developers.google.com/google-apps/documents-list/#detecting_changes_to_resources"&gt;Google Documents List API version 3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Google Calendar: &lt;a href="https://developers.google.com/google-apps/calendar/v2/developers_guide_protocol"&gt;Google Calendar API v2&lt;/a&gt;&lt;br/&gt;
Note: there is a new API v3 for Google Calendar&lt;/li&gt;
&lt;li&gt;Google Contacts: &lt;a href="https://developers.google.com/google-apps/contacts/v2/reference"&gt;Google Contacts API v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The basic call for Google Contacts for example looks like this:&lt;p&gt;
&lt;p&gt;&lt;code&gt;https://www.google.com/m8/feeds/contacts/default/full&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This responds with a complete list of your contacts. Once we have this list all we have to do is to ask for the delta to our existing knowledge. For such use cases, Google’s APIs support query parameters as well as sorting parameters. So we can set “orderby” to “lastmodified” as well as “updated-min” to the timestamp of our last call. This way we are able to keep the traffic low and get quick results by only asking for things we might have missed.&lt;/p&gt;

&lt;p&gt;If you want to develop using those APIs you should definitely have a look at the SDKs for them. We used the Google Docs SDK for an early prototype and loved it. Today, Hojoki uses its own generic connection handler for all our integrated systems so we don’t leverage the SDKs anymore.&lt;/p&gt;
 &lt;p/&gt;

&lt;h3&gt;Real world problems&lt;/h3&gt;

&lt;p&gt;If you’re into API development, you’ve probably already realized that our information needs don’t fit into many of the APIs out there. Most of the APIs are object centric. They can tell you what objects are included in a certain folder, but they can’t tell you which object in this folder has been changed recently. They just aren’t built with newsfeeds in mind.&lt;/p&gt;

&lt;p&gt;Google Apps APIs support most of our information needs. Complete support of OAuth and very responsive APIs definitely make our lives easier.&lt;/p&gt;

&lt;p&gt;However, the APIs are not built with Hojoki-like newsfeeds in mind. For example, ETags may change even if nothing happened to an object because of asynchronous processing on Google’s side (see &lt;a href="https://developers.google.com/google-apps/documents-list/#getting_a_resource_entry_again"&gt;Google’s comment&lt;/a&gt; on this). For us this means that, once we detect an altered ETag, in some cases we still have to check based on our existing data if there really have been relevant activities. Furthermore, we often have trouble with missing actors in our activities. For example, up to now we know when somebody changed a calendar event, but there is no way to find out who this was.&lt;/p&gt;

&lt;p&gt;Another issue is the classification of updates. Google’s APIs tell us that something changed with an object. But to build a nice newsfeed you also want to know what exactly has been changed. So you’re looking for a verb like created, updated, shared, commented, moved or deleted. While Hojoki calls itself an aggregator for activities, technically we’re primarily an activity detector.&lt;/p&gt;
 &lt;p/&gt;

&lt;h3&gt;Final solution&lt;/h3&gt;

&lt;p&gt;You can think of Hojoki as a multi-layer platform. First of all, we try to get a complete overview on your meta-data of the connected app. In Google Docs, this means files and collections, and we retrieve URI and name as well as some additional information (not the content itself). This information fills a graph-based data storage (we use RDF, &lt;a href="http://semanticweb.com/cloud-app-activities-unite-on-hojoki_b25710"&gt;read more about it here&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;At the moment, we subscribe to events in the integrated apps. If detected, they create a changeset for the existing data graph. This changeset is an activity for our newsfeed and related to the object representation. This allows us to provide a very flexible aggregation and filtering on the client side. See the following screenshot. You can filter the stream for a certain collection (“Analytics”) or only for the file history or for the Hojoki workspace where this file is added (“Hojoki Marketing”).&lt;/p&gt;
&lt;p/&gt;
&lt;div style="text-align:center;"&gt;&lt;a href="http://4.bp.blogspot.com/-t97c7pjViWg/T6CDXOOhTHI/AAAAAAAAAhw/h_x3m6Kg1bU/s1600/image02.jpg" imageanchor="1"&gt;&lt;img border="0" height="193" src="http://4.bp.blogspot.com/-t97c7pjViWg/T6CDXOOhTHI/AAAAAAAAAhw/h_x3m6Kg1bU/s400/image02.jpg" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;p/&gt;
&lt;p&gt;What’s really important in terms of such heavy API processing is to use asynchronous calls. We use the great Open Source project &lt;a href="https://github.com/sonatype/async-http-client"&gt;async-http-client &lt;/a&gt; for this task.&lt;/p&gt;
&lt;p/&gt;

&lt;h3&gt;A wishlist&lt;/h3&gt;

&lt;p&gt;When I wrote that “we subscribe to events” this is a very nice euphemism for “we’re polling every 30s to see if something changed”. This is not really optimal and we’d love to change it. If Google Apps APIs would support a feed of user events modelled in a common standard like ActivityStrea.ms, combined with reliable ETags and maybe even a push API (e.g. Webhooks) this would also make life easier for lots of developers syncing their local files with Google and help to reduce traffic on both sides.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" style="width:94px;" width="94px" src=http://2.bp.blogspot.com/-GnTqRUvRNA0/T6CH0pQBSfI/AAAAAAAAAiA/kh0qPKZIe8w/s400/martin.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Martin Böhringer&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Martin is Co-Founder and CEO of &lt;a href="http://www.hojoki.com"&gt;Hojoki&lt;/a&gt;. Hojoki integrates Google Docs, Calendar, Contacts and Reader next to other apps like Dropbox and Evernote into one newsfeed, showing changes by co-workers in one central place. Martin holds a PhD in information systems and likes to &lt;a href="http://www.youtube.com/watch?v=p_zbbcQz2T0"&gt;perform on stage at Tech Events&lt;/a&gt;.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-5392704898066187773?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/B9uwQ5NdcBc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/5392704898066187773/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=5392704898066187773&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5392704898066187773?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5392704898066187773?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/B9uwQ5NdcBc/how-we-built-social-network-for.html" title="How we built a Social Network for Productivity on Top of Google Docs, Calendar, Contacts and Reader" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-yuy-9vA1tDs/T6CDQBRsLbI/AAAAAAAAAhY/A-qWLTVm2YQ/s72-c/image00.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/05/how-we-built-social-network-for.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0ABSH0zeyp7ImA9WhVWEkU.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-7793564890984304568</id><published>2012-04-24T09:42:00.000-07:00</published><updated>2012-04-24T09:42:39.383-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-24T09:42:39.383-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Drive SDK" /><title>Introducing Google Drive and the Google Drive SDK</title><content type="html">&lt;p&gt;&lt;i&gt;Editor's note: This post is cross-posted from the &lt;a href="http://googledevelopers.blogspot.com/2012/04/introducing-google-drive-and-google.html"&gt;Google Developers Blog&lt;/a&gt;&lt;/i&gt;.&lt;/p&gt;
&lt;br /&gt;
Today, &lt;a href="http://googleblog.blogspot.com/"&gt;we're announcing Google Drive&lt;/a&gt;—a place where people can create, share, collaborate and keep all of their stuff. Drive is a natural step in the evolution of Google Docs. Drive is built to work seamlessly with other Google applications like Google+, Docs and Gmail, and your app can too. Joining the launch today are &lt;a href="https://chrome.google.com/webstore/category/collection/drive_apps"&gt;18 web apps&lt;/a&gt; that have integrated with Drive using the &lt;a href="https://developers.google.com/drive/"&gt;Google Drive SDK&lt;/a&gt;.
&lt;br /&gt;
&lt;div class="separator" style="text-align: center;"&gt;&lt;br /&gt;
&lt;a href="https://chrome.google.com/webstore/category/collection/drive_apps"&gt;&lt;img src="http://2.bp.blogspot.com/-AEUJG0xEI-o/T5XpFF7-psI/AAAAAAAABZk/_JM0V0tL_7M/s1600/works.png" alt="" style="border-bottom-style: none; border-color: initial; border-left-style: none; border-right-style: none; border-top-style: none; border-width: initial; text-align: center;" width="500" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
Integrating your application with Google Drive makes it available to millions of users. Drive apps are distributed from the &lt;a href="https://chrome.google.com/webstore/category/collection/drive_apps"&gt;Chrome Web Store&lt;/a&gt;, and can be used with any modern browser. Plus, your app can take advantage of Google's sharing, storage, and identity management features.&lt;br /&gt;
&lt;br /&gt;
&lt;iframe width="560" height="315" src="http://www.youtube.com/embed/0ee3R3tfdd4" frameborder="0" allowfullscreen&gt;&lt;/iframe&gt;&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Create and collaborate&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Google Drive allows for more than storage. Google Docs is built right into Drive, and your app can join the party. For example, &lt;a href="https://chrome.google.com/webstore/detail/apboafhkiegglekeafbckfjldecefkhn?utm_source=chrome-ntp-icon"&gt;Lucidchart&lt;/a&gt; is an online visual diagramming tool integrated with Google Drive. You can start a new Lucidchart or share your diagrams with friends or coworkers straight from Drive, just like a Google document or spreadsheet.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Store everything safely and access it everywhere&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
With Google Drive you can store all of your files and access them from anywhere. For example, &lt;a href="https://chrome.google.com/webstore/detail/bdehgigffdnkjpaindemkaniebfaepjm"&gt;MindMeister&lt;/a&gt;, an app for creating mind maps online, also lets you open files from popular desktop mind mapping applications. By integrating with Google Drive, MindMeister users can open their mind maps stored in Drive from any modern browser.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;Search everything&lt;/b&gt;&lt;br /&gt;
&lt;br /&gt;
Your app can also take advantage of Drive's storage, indexing, and document viewers. For example, &lt;a href="https://chrome.google.com/webstore/detail/bocmleclimfnadgmcdgecijlblfcmfnm"&gt;HelloFax&lt;/a&gt; is a web application that lets you sign and fax documents from your browser. HelloFax users can now store all their inbound and outbound faxes in Google Drive, making them easy to find later. Plus, with automatic OCR, users can even search and find text in faxed images. Your application can store files of any type up to 10 GB in size or create file-like shortcuts to your application's data.&lt;br /&gt;
&lt;br /&gt;

Want your application to work with Google Drive? Full documentation on the Google Drive SDK is available at &lt;a href="http://developers.google.com/drive"&gt;developers.google.com/drive&lt;/a&gt;, or if you're itching to start building, head to our &lt;a href="https://developers.google.com/drive/get_started"&gt;Getting Started&lt;/a&gt; guide. Our team will be on &lt;a href="http://stackoverflow.com/questions/tagged/google-drive-sdk"&gt;Stack Overflow&lt;/a&gt; to answer any questions you have when integrating your app with Google Drive. You can also bring your questions to our &lt;a href="https://developers.google.com/events/ahNzfmdvb2dsZS1kZXZlbG9wZXJzcg4LEgVFdmVudBjeh9YBDA/"&gt;Hangout&lt;/a&gt; this Thursday at &lt;a href=" http://timeanddate.com/worldclock/fixedtime.html?iso=20120426T1030&amp;p1=1241"&gt;10:30 AM PDT / 17:30 UTC&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
Look for more posts about working with the Drive SDK on the &lt;a href="http://googleappsdeveloper.blogspot.com/"&gt;Google Apps Developer Blog&lt;/a&gt; in the coming weeks.&lt;br /&gt;



&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://1.bp.blogspot.com/-hu412ztArbA/T5bXnJ6YFaI/AAAAAAAAAhM/pCH2KQEfygI/s400/mike.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Mike  Procopio&lt;/span&gt;   &lt;a class="alt" href="https://plus.google.com/106387596237285278373/about" rel="me" target="_blank"&gt;profile&lt;/a&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Mike is a Software Engineer for Google Drive, focusing on all things Drive apps. He gets to leverage his passion for the developer and user experience by working on the next-generation APIs that help unleash Google Drive. Before joining Google in 2010, he was a machine learning researcher, and enjoys engaging in illuminating statistical discussions at every opportunity.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-7793564890984304568?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/oDKcAg5_1VU" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/7793564890984304568/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=7793564890984304568&amp;isPopup=true" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/7793564890984304568?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/7793564890984304568?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/oDKcAg5_1VU/introducing-google-drive-and-google.html" title="Introducing Google Drive and the Google Drive SDK" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-AEUJG0xEI-o/T5XpFF7-psI/AAAAAAAABZk/_JM0V0tL_7M/s72-c/works.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/04/introducing-google-drive-and-google.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkIAR3s-fSp7ImA9WhVXF00.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-9160756818126853139</id><published>2012-04-17T15:08:00.000-07:00</published><updated>2012-04-17T15:09:06.555-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-17T15:09:06.555-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><title>Approval Workflow using Apps Script</title><content type="html">&lt;p&gt;
 &lt;i&gt;Editor’s Note: This blog post is authored by &lt;a
  href="http://www.ditoweb.com"&gt;Dito’s&lt;/a&gt; Steve Webster who is a Google
  Apps Script Top Contributor - &lt;a
  href="https://plus.google.com/105831704374179338116"&gt;Saurabh Gupta&lt;/a&gt;
 &lt;/i&gt;
&lt;/p&gt;

&lt;h2&gt;Ethics Disclosure Review Workflow&lt;/h2&gt;
&lt;p&gt;Recently a company who operates retail stores throughout a few
 states reached out to Dito. When their associates conduct business
 discussions with vendors or customers where monetary exchanges are
 involved, their Ethics Disclosure policy requires a log for each event,
 a supervisor approval, and committee review.&lt;/p&gt;
&lt;p&gt;
 The customer’s existing disclosure review process was cumbersome and
 time consuming. The employees would add a disclosure review request to
 a spreadsheet with 29 columns. The new review request would then be
 reviewed by their supervisor. The supervisor and the employee would go
 back and forth exchanging emails and making changes to a dense
 spreadsheet until an approval was granted. It was clear that the
 customer needed a workflow solution. They decided to hire &lt;a
  href="http://www.ditoweb.com"&gt;Dito&lt;/a&gt; to build a workflow solution
 based on &lt;a
  href="http://code.google.com/googleapps/appsscript/guide.html"&gt;Google
  Apps Script&lt;/a&gt;.
&lt;/p&gt;
&lt;h2&gt;Workflow Solution based on Google Apps Script&lt;/h2&gt;
&lt;p&gt;
 To make the process more user friendly and productive, Dito decided to
 build a user interface to collect ethics disclosure events, make
 updates, and automate the routing of email notifications. Writing a
 Google Apps Script &lt;a
  href="http://www.youtube.com/user/GoogleDocsCommunity#p/c/E630653EE5F67B37/0/5VmEPo6Rkq4"&gt;to
  create a user interface&lt;/a&gt; (UI), enabled associates to interact with
 their contacts to select their supervisor’s email address and simplify
 the data collection with list boxes. The script sends approval emails
 with HTML form radio buttons, text box, approve/decline buttons, and a
 “Post” command to invoke other workflow scripts. Below are some of the
 main design points for this Approval Workflow script.
&lt;/p&gt;
&lt;br /&gt;

&lt;h3&gt;1. Disclosure Review Workflow&lt;/h3&gt;
&lt;/p&gt;
&lt;p&gt;The Disclosure Review workflow requires (a) Associates to fill
 out the Ethics Disclosure form. (b) Supervisor to either approve or
 decline the submission. (c) If supervisor approves, the Ethics
 Disclosure Committee is notified. (d) If supervisor declines, the
 associate is notified to make corrections. (e) After improving the
 submission, the workflow repeats itself.&lt;/p&gt;
&lt;p&gt;
 &lt;a
  href="http://2.bp.blogspot.com/-C8XmEOdW9Ro/T42QnRK1A6I/AAAAAAAAAgo/4eCyLuL9oWQ/s1600/image03.png"
  imageanchor="1" style=""&gt;&lt;img border="0" height="329" width="400"
  src="http://2.bp.blogspot.com/-C8XmEOdW9Ro/T42QnRK1A6I/AAAAAAAAAgo/4eCyLuL9oWQ/s400/image03.png" /&gt;
 &lt;/a&gt;
&lt;/p&gt;
&lt;br /&gt;
&lt;h3&gt;2. Disclosure Review Request Form&lt;/h3&gt;
&lt;p&gt;
 Dito developed a custom review request form. A form was developed using
 Google Apps Script’s &lt;a
  href="http://code.google.com/googleapps/appsscript/service_ui.html"&gt;UI
  Services&lt;/a&gt;. The form provides the ability to look up data to populate
 such things as a drop-down list box. This form allowed real-time email
 look-ups by using the Apps Script’s Contacts service.  First efforts
 included per character look-ups in a list box, but since they have over
 1,000 users, it was best to enter the first and/or last name of their
 supervisor before initiating the look-up (see code snippet below).
&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
var byName = ContactsApp.getContactsByName(searchKey);
for (var i in byName) {
  var emailStr  = byName[i].getPrimaryEmail();
  // If there is no 'primary' email, try again for the next email
  if (emailStr == null)
     var emailStr  = byName[i].getEmails()[0].getAddress(); 
  // If emailStr is still null, try again by getting the next email
  if (emailStr == null) 
     var emailStr  = byName[i].getEmails()[1].getAddress();
}
&lt;/pre&gt;


&lt;p&gt;Another dynamic field was the “activity type”.  Depending on the
 selection more form fields are displayed. For example, if the activity
 type is “Meals”, display a list box to select lunch or dinner.&lt;/p&gt;
&lt;br /&gt;
&lt;h3&gt;3. Approve or Reject directly in Gmail&lt;/h3&gt;
&lt;p&gt;When an associate submits his/her review request by using the
 custom form within a spreadsheet, their supervisor receives an email
 with easy-to-read HTML formatted results.  The approval decision, as
 well as a comment field (e.g. decline reason), is made within the
 email. This is more productive and prevents unnecessary back and forth
 into the spreadsheet.&lt;/p&gt;

&lt;p&gt;
 &lt;a
  href="http://3.bp.blogspot.com/-dEz7ZFSy2Bc/T42RhbOOuVI/AAAAAAAAAg0/Ja0msF_TfXQ/s1600/image01.png"
  imageanchor="1" style=""&gt;&lt;img border="0" width="500"
  src="http://3.bp.blogspot.com/-dEz7ZFSy2Bc/T42RhbOOuVI/AAAAAAAAAg0/Ja0msF_TfXQ/s400/image01.png" /&gt;
 &lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;If the request is declined by the supervisor, the associate who
 submitted the review request receives an email and can review the
 details. The email also contains a “Continue” button which opens the
 form in a new browser tab. After corrections are submitted, the
 supervisor receives another email and the workflow repeats itself.&lt;/p&gt;
&lt;p&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
 &lt;a
  href="http://4.bp.blogspot.com/-NH-QHG7fGGU/T42R8eW3jSI/AAAAAAAAAhA/EGBe0RYzXRo/s1600/image02.png"
  imageanchor="1" style=""&gt;&lt;img border="0" height="358" width="300"
  src="http://4.bp.blogspot.com/-NH-QHG7fGGU/T42R8eW3jSI/AAAAAAAAAhA/EGBe0RYzXRo/s400/image02.png" /&gt;
 &lt;/a&gt;
&lt;/div&gt;


&lt;/p&gt;
&lt;p&gt;When approved, the Ethics Disclosure Committee is notified by
 sending a group email within the script.&lt;/p&gt;
&lt;br /&gt;
&lt;h3&gt;4. Saving Workflow History&lt;/h3&gt;
&lt;p&gt;Since history records existed in their original spreadsheet form
 and they wanted to insert these records into the new work flow
 spreadsheet as a one-time task, an Apps Script was used to copy the
 data.  Of course their columns did not match the new spreadsheet.  By
 using a mapping approach and a “read once” and “write once” technique,
 the Apps Script quickly made the changes.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
function myFunction() {
  var ss           = SpreadsheetApp.getActiveSpreadsheet();
  var sheet        = ss.getSheetByName('Sheet 1');
  var rowsWithData = sheet.getRange(2, 1, sheet.getLastRow(), 
                     sheet.getLastColumn()).getValues()
  var sheet1Data = []; 
  var sheet2Data = []; 
  for (var i = 0; i &lt; rowsWithData.length; i++) {
    switch (rowsWithData[i][4])   // This is the activity type
    {
      ...
      case "Gift":
            sheet1Data.push([rowsWithData[i][12], rowsWithData[i][13], 
                             rowsWithData[i][14]]);
            sheet2Data.push([rowsWithData[i][15]]);
            continue;
      ...
      default:
            continue;
    }
  }
  sheet.getRange(2, 6,  sheet1Data.length, 3).setValues(sheet1Data);
  sheet.getRange(2, 12, sheet2Data.length, 1).setValues(sheet2Data);
}
&lt;/pre&gt;

&lt;p&gt;Google Apps Script is very powerful and Dito uses it to build
 interesting solution for its customers. If you are using Google Apps
 then be sure to use Google Apps Script. You’ll be amazed with what you
 can build with it.&lt;/p&gt;

&lt;br /&gt;
&lt;br /&gt;

&lt;table cellpadding="5" cellspacing="0"&gt;
 &lt;tbody&gt;
  &lt;tr style="background-color: #f2f2f2;"&gt;
   &lt;td&gt;&lt;img class="profile"
    src="http://4.bp.blogspot.com/-YN6zIPheYA0/Tqn4INQAFVI/AAAAAAAAATg/yhVsH75ntQs/s90/Steve%2BWebster.png" /&gt;
   &lt;/td&gt;
   &lt;td valign="top"&gt;&lt;span class="largefont"&gt;Steve Webster&lt;/span&gt;
    &amp;nbsp; &lt;a class="alt"
    href="https://plus.google.com/118243012738484227290/about" rel="me"
    target="_blank"&gt;profile&lt;/a&gt;&lt;br /&gt;
    &lt;div class="bio"&gt;
     &lt;br /&gt;Google Sites and Scripts expert from &lt;a
      href="http://www.ditoweb.com"&gt;Dito&lt;/a&gt; specializing in training
     and application development. When not busy finding solutions to
     enhance customer capability in Google Apps, Steve shares examples
     of his work in the Google Apps Developer Blog.
    &lt;/div&gt; &lt;br /&gt;&lt;/td&gt;
  &lt;/tr&gt;
  &lt;br /&gt;
 &lt;/tbody&gt;
&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-9160756818126853139?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/tkQlUCBvibA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/9160756818126853139/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=9160756818126853139&amp;isPopup=true" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9160756818126853139?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9160756818126853139?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/tkQlUCBvibA/approval-workflow-using-apps-script.html" title="Approval Workflow using Apps Script" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-C8XmEOdW9Ro/T42QnRK1A6I/AAAAAAAAAgo/4eCyLuL9oWQ/s72-c/image03.png" height="72" width="72" /><thr:total>6</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/04/approval-workflow-using-apps-script.html</feedburner:origLink></entry><entry gd:etag="W/&quot;D0YDR3w8cSp7ImA9WhVXE04.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-2628326084289203284</id><published>2012-04-13T09:39:00.000-07:00</published><updated>2012-04-13T09:39:36.279-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-13T09:39:36.279-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google Forms" /><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Fusion Tables" /><title>Crowd Sourcing with Google Forms and Fusion Tables</title><content type="html">&lt;p&gt;Crowd sourcing has been growing substantially in popularity. More and more businesses and individuals are interested in gathering data from the general public for real-time data analysis and visualization. The concept is being adopted in several fields, including journalism, public health and safety, and business development. During this election year, for example, a journalist might be interested in learning what candidate his or her readers support, and the reasons why they support this candidate.&lt;/p&gt;

&lt;p&gt;Google Forms, Fusion Tables, and Apps Script make both data collection and analysis super simple! Using Google Forms, a journalist can quickly create an HTML form for readers to submit their opinions and feedback. Fusion Tables make data analysis easy with several cool data visualization options. Apps Script acts as the glue between Google Forms and Fusion Tables, enabling the Form to send data directly to Fusion Tables.&lt;/p&gt;

&lt;p&gt;Let’s take a look at how our journalist friend would use all these tools to collect her reader’s candidate preferences.&lt;/p&gt;

&lt;h2&gt;Google Forms&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://support.google.com/docs/bin/answer.py?hl=en&amp;answer=87809"&gt;Google Forms&lt;/a&gt; provides a simple UI tool to develop forms perfect for collecting data from readers. Here’s an example of a simple form the journalist can create to get information from her readers:&lt;/p&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-OF5A9VoGl7w/T4hUzZl8zfI/AAAAAAAAAgQ/NWiYjrB7wtI/s1600/image01.png" imageanchor="1" style=""&gt;&lt;img border="0" height="400" width="314" src="http://4.bp.blogspot.com/-OF5A9VoGl7w/T4hUzZl8zfI/AAAAAAAAAgQ/NWiYjrB7wtI/s400/image01.png" /&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;Once the form has been created, it can be embedded directly into the journalist’s website or blog using the embeddable HTML code provided by Google Forms.&lt;/p&gt;

&lt;h2&gt;Google Fusion Tables&lt;/h2&gt;

&lt;p&gt;&lt;a href="http://www.google.com/fusiontables/public/tour/index.html"&gt;Google Fusion Tables&lt;/a&gt; makes data analysis simple with its visualization capabilities. Using Fusion Tables, the journalist can create maps and charts of the collected data with just a few clicks of the mouse!&lt;/p&gt;

&lt;p&gt;Using some fake data as an example, here’s a pie chart that can be created using Fusion Tables to show the the results of the survey:&lt;/p&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-FkB2vBWi-T8/T4hU7kXKlSI/AAAAAAAAAgc/odBFsRgGFuk/s1600/image00.png" imageanchor="1" style=""&gt;&lt;img border="0" height="310" width="400" src="http://3.bp.blogspot.com/-FkB2vBWi-T8/T4hU7kXKlSI/AAAAAAAAAgc/odBFsRgGFuk/s400/image00.png" /&gt;&lt;/a&gt;&lt;/div&gt;


&lt;p&gt;With Fusion Tables, it’s also easy to filter data and create a pie chart visualization showing why people like Mitt Romney:&lt;/p&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-_oAPLrVNwBg/T4c9y65Y2pI/AAAAAAAAAfc/gXALMktZ4ig/s1600/image01.png" imageanchor="1" style=""&gt;&lt;img border="0" height="234" width="400" src="http://1.bp.blogspot.com/-_oAPLrVNwBg/T4c9y65Y2pI/AAAAAAAAAfc/gXALMktZ4ig/s400/image01.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;These visualizations can also be embedded in the journalist’s website or blog, as Fusion Tables provides embeddable HTML code for all its visualizations. Now, any time someone visits the webpage with the embedded visualization, they will see the current poll result!&lt;/p&gt;

&lt;h2&gt;Apps Script&lt;/h2&gt;

&lt;p&gt;Finally, &lt;a href="https://developers.google.com/apps-script/"&gt;Apps Script&lt;/a&gt; acts as the glue between the Google Form and the Fusion Table, since there is currently no direct way to send Google Form submissions to a Fusion Table. During a hack event last year, I took some time to write an Apps Script &lt;a href="http://kh-samples.googlecode.com/svn/trunk/code/appsscript.js"&gt;script&lt;/a&gt; that submits the form data to Fusion Tables. The script uses the onFormSubmit Apps Script functionality as described in &lt;a href="http://googleappsdeveloper.blogspot.com/2012/03/integrating-google-docs-with.html"&gt;this blog post&lt;/a&gt;. The Fusion Tables code is based on the code described in &lt;a href="http://googleappsdeveloper.blogspot.com/2011/09/using-fusion-tables-with-apps-script.html"&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To learn how to set up your own Google Form to collect data and save that data in a Fusion Table, please see &lt;a href="http://kh-samples.googlecode.com/svn/trunk/code/instructions.html"&gt;these instructions&lt;/a&gt;.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://3.bp.blogspot.com/-aEF77X56iNM/T4c-045yGxI/AAAAAAAAAfo/MH6FzecD7QQ/s1600/kathryn_hurley.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Kathryn Hurley&lt;/span&gt;    &lt;a class="alt" href="https://plus.google.com/103681029286159969300/posts" rel="me" target="_blank"&gt;profile&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Kathryn is a Developer Programs Engineer for Fusion Tables at Google. In this role, she helps spread the word about Fusion Tables by presenting at conferences and developer events. Kathryn received an MS in Web Science from the University of San Francisco. Prior work experience includes database management, web production, and research in mobile and peer-to-peer computing.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-2628326084289203284?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/3R4WegEtjv0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/2628326084289203284/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=2628326084289203284&amp;isPopup=true" title="6 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2628326084289203284?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2628326084289203284?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/3R4WegEtjv0/crowd-sourcing-with-google-forms-and.html" title="Crowd Sourcing with Google Forms and Fusion Tables" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-OF5A9VoGl7w/T4hUzZl8zfI/AAAAAAAAAgQ/NWiYjrB7wtI/s72-c/image01.png" height="72" width="72" /><thr:total>6</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/04/crowd-sourcing-with-google-forms-and.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CE4GRX88fyp7ImA9WhVQFkg.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-3419721806158605889</id><published>2012-04-05T12:00:00.000-07:00</published><updated>2012-04-05T12:08:44.177-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-04-05T12:08:44.177-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Introducing the Script Service</title><content type="html">&lt;p&gt;Triggers in Apps Script allow you to schedule your script’s execution in response to an event, at a specific time and date, or at given time intervals. Publishing scripts as services allows you to deploy your web apps with a click of a button. The new &lt;a href="https://developers.google.com/apps-script/service_script"&gt;Script service&lt;/a&gt; lets you perform both of these tasks programmatically. With the Script service at your disposal, you can create smooth install flows, chain your scripts’ executions, generate summaries of current triggers, and even programmatically publish the scripts as services.&lt;/p&gt;

&lt;h2&gt;Create smooth install flows&lt;/h2&gt;

&lt;p&gt;You can now set up your triggers from a menu. Here is an example of how you would schedule function &lt;code&gt;foo()&lt;/code&gt; to run one year from now via a &lt;a href="https://developers.google.com/apps-script/articles/defining_menus"&gt;custom menu&lt;/a&gt; in Google Spreadsheets:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function onOpen() {
  SpreadsheetApp.getActive()
    .addMenu("Setup",
             [{name: "Create Trigger", 
               functionName: "createTrigger"}]);
}

function createTrigger() {
  var now = new Date();
  ScriptApp.newTrigger("foo")
           .timeBased()
           .atDate(parseInt(now.getFullYear())+1, 
                            now.getMonth(),
                            now.getDate())
           .create();
}&lt;/pre&gt;

&lt;h2&gt;Chain script executions&lt;/h2&gt;

&lt;p&gt;By programmatically setting triggers, you are able to schedule a future script’s executions from the script that is currently running. Here is an example of a function that schedules another function to execute two hours after it completes:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
// this function is run to send the initial reminder and schedule a subsequent one
function sendRequest() {
  GmailApp.sendEmail("class@example.edu",
                     "Assignment #1 is due in 2 and a half hours!",
                     "Your first assignment is due in 2 and a half hours!");
  var now = new Date();
  var later = new Date(now.getTime() + 2 * 60 * 60 * 1000);
  ScriptApp.newTrigger("sendReminder").timeBased().at(later).create();
}

// this function will execute two hours later, sending another reminder
function sendReminder() {
  GmailApp.sendEmail("class@example.edu",
                     "Assignment #1 is due in 30 minutes!",
                     "Your first assignment is due in half an hour!");
}&lt;/pre&gt;

&lt;h2&gt;Generate a summary of current triggers&lt;/h2&gt;

&lt;p&gt;With the ability to schedule triggers programmatically, it is important to keep track of the existing ones. The function below logs all the triggers that are associated with the current script. Alternatively, you could also write this summary to a spreadsheet or email it to yourself.&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function logMyTriggers() {
  var triggers = ScriptApp.getScriptTriggers();
  for(i in triggers) {
    Logger.log("Trigger ID: " + triggers[i].getUniqueId()
               + "\nTrigger handler function: "
               + triggers[i].getHandlerFunction()
               + "\nTrigger type: " + triggers[i].getEventType()
               + "\n----------------------");
  }
}
&lt;/pre&gt;

&lt;h2&gt;Publish scripts programmatically&lt;/h2&gt;

&lt;p&gt;You can now let your users publish your scripts through a Google Spreadsheets &lt;a href="https://developers.google.com/apps-script/articles/defining_menus"&gt;custom menu&lt;/a&gt; as a part of the script’s set up process. Below is an illustration of this process using an example of a simple note-taking web app. Once a user clicks the menu item, the script becomes published as a service and the URL is presented back to the user via a dialog box.&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function doGet(e) {
  var app = UiApp.createApplication();
  createApp(app, false);
  return app;
}

function doPost(e) {
  var now = new Date();
  MailApp.sendEmail(Session.getEffectiveUser(),
                    "Notes taken at " + now, e.parameter.notes);
  var app = UiApp.getActiveApplication();
  createApp(app, true);
  return app;
}

function createApp(app, isSubmitted) {
  app.setWidth(550)
     .setHeight(700);
  var vp = app.createVerticalPanel();
  var title = app.createHTML("Enter your notes into the text area below." +
                             "&amp;lt;br&amp;gt;Hit submit to email them to yourself!")
                 .setStyleAttribute("fontSize", "25px");
  var lbl = app.createLabel("Notes submitted!")
               .setStyleAttribute("fontSize", "17px");
  var form = app.createFormPanel();
  var notes = app.createTextArea()
                 .setWidth("500px")
                 .setHeight("500px")
                 .setName("notes");
  var sb = app.createSubmitButton("Submit");
  vp.add(title)
    .add(notes)
    .add(sb);
  if (isSubmitted)
    vp.add(lbl);
  form.add(vp);
  return app.add(form);
}

function onInstall() {
  onOpen();
}

function onOpen() {
  SpreadsheetApp.getActive()
    .addMenu("Setup",
             [{name: "Publish as a service.", 
               functionName: "setup"}]);
}

function setup() {
  var url = "";
  try {
    ScriptApp.getService()
             .enable(ScriptApp.getService().Restriction.MYSELF);
    url = ScriptApp.getService().getUrl();
    Browser.msgBox("Your web app is now accessible at the following URL:\n"
                   + url);
  } catch (e) {
    Browser.msgBox("Script authorization expired.\nPlease run it again.");
    ScriptApp.invalidateAuth();
  }
}&lt;/pre&gt;

&lt;p&gt;The Script service provides you with the means of creating even more powerful applications and makes the development and deployment process smooth.&lt;/p&gt;

&lt;p&gt;For more information, please visit the Script service &lt;a href="https://developers.google.com/apps-script/service_script"&gt;Reference Documentation&lt;/a&gt; and &lt;a href="https://developers.google.com/apps-script/guide_events#ScriptService"&gt;User Guide&lt;/a&gt;.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://1.bp.blogspot.com/-s08VB7GWBNU/T33djwAKSXI/AAAAAAAAAe4/Whv-QzVd0tc/s1600/Anton%2BSoradoi.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Anton Soradoi&lt;/span&gt;    &lt;a class="alt" href="https://plus.google.com/103646719242379546072/posts" rel="me" target="_blank"&gt;profile&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Anton is a Developer Support Specialist at Google. He works closely with Apps Script developers to help them discover its full potential. Prior to Google, he worked as a Web Developer.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-3419721806158605889?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/6-LCsdnFqM4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/3419721806158605889/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=3419721806158605889&amp;isPopup=true" title="5 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/3419721806158605889?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/3419721806158605889?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/6-LCsdnFqM4/introducing-script-service.html" title="Introducing the Script Service" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-s08VB7GWBNU/T33djwAKSXI/AAAAAAAAAe4/Whv-QzVd0tc/s72-c/Anton%2BSoradoi.png" height="72" width="72" /><thr:total>5</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/04/introducing-script-service.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ck8HRXc5eyp7ImA9WhVQEU8.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-2577322136660748960</id><published>2012-03-30T08:20:00.000-07:00</published><updated>2012-03-30T08:20:34.923-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-30T08:20:34.923-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><title>A First Attempt at Apps Script with Spreadsheets</title><content type="html">&lt;p&gt;&lt;i&gt;The Apps Script team held a hackathon in Washington DC on March 7. Over 80 developers attended and we had some great demos at the end of the evening. One of the demos was from Rusty Mellinger, who explains his script in this blog post. If you missed the DC hackathon, &lt;a href="https://sites.google.com/site/appsscripthackathonchicago/"&gt;sign up&lt;/a&gt; for our next one in Chicago on April 19. -Jan Kleinert&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;I was lucky enough to attend Google’s Apps Script Hackathon at their office in DC, recently, and got a chance to play with Apps Script.  After a quick walk-through tutorial, Jan gave us a couple of hours to hack around with it.&lt;/p&gt;

&lt;p&gt;Scripts in Apps Script are written in JavaScript and stored, edited, and run on Google's servers,  interfacing with a &lt;a href="http://www.google.com/url?q=https%3A%2F%2Fdevelopers.google.com%2Fapps-script%2Fdefaultservices"&gt;big list of included services&lt;/a&gt;.  You can call the scripts from spreadsheets, Google Sites, or from hits to a generated URL.&lt;/p&gt;

&lt;h2&gt;Roommate Payment Spreadsheet&lt;/h2&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-6M6tsfTD3f4/T3XCoF7gpQI/AAAAAAAAAeU/Y5-XX7hkdGE/s1600/image00.png" imageanchor="1" style=""&gt;&lt;img border="0" height="168" width="400" src="http://4.bp.blogspot.com/-6M6tsfTD3f4/T3XCoF7gpQI/AAAAAAAAAeU/Y5-XX7hkdGE/s400/image00.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;My roommates and I keep a spreadsheet on Google Docs to track who owes what, but since we’re a house full of software engineers, it’s gotten pretty complicated.  Each row records the details of a single transaction: who paid, the total, and what percentages of the payment are on behalf of which roommates.  All these interpersonal debts are added up into the (J5:M8) matrix, cancelled out across the diagonal into (P5:S8) to get a single debt for each roommate pairing, and then those are totalled into the final "Shake Out", (F4:F7), which says whether you owe or are owed.  Maybe Apps Script could make my life simpler here?&lt;/p&gt;

&lt;h2&gt;Automatic Emails&lt;/h2&gt;

&lt;p&gt;First, I’m currently owed a fair amount of money, so I set up automated reminder emails to the roommates who are behind:&lt;p&gt;

&lt;pre class="prettyprint lang-js"&gt;
// Send emails to everybody with their current status.
function emailDebtors() {
  var ss       = SpreadsheetApp.getActiveSpreadsheet();
  var results  = ss.getRange( "G4:G7" ).getValues();
  var emails   = ss.getRange( "O3:R3" ).getValues();
  var numUsers = 4;
  
  for(var i = 0; i != numUsers; i++) {
    var val = Math.round(results[i][0]);
    
    if (val &gt; 0) {
      // This guy owes money in the shake-out.
      MailApp.sendEmail(
        emails[0][i], "You're a deadbeat!",
        "You owe $" + val + ". Please pay it!");
    }
  }
}&lt;/pre&gt;

&lt;p&gt;This just pulls the current totals from the (G4:G7) "Shake Out", as well as their respective email addresses from (O3:R3).  When this function is called, if any of them owe more than $0, they get a friendly reminder!&lt;/p&gt;

&lt;h2&gt;Custom Menus&lt;/h2&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-FfhPIbU0SnQ/T3XDzDPNtoI/AAAAAAAAAeg/t4tXbyTTt-s/s1600/image02.png" imageanchor="1" style=""&gt;&lt;img border="0" height="181" width="400" src="http://1.bp.blogspot.com/-FfhPIbU0SnQ/T3XDzDPNtoI/AAAAAAAAAeg/t4tXbyTTt-s/s400/image02.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;I could set that up to trigger daily or weekly, but it only really needs to happen when somebody needs to collect what they’re owed, so I’ve added it as an option to the sheet’s menu on start-up.&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [ { name: "Email debtors", 
                        functionName: "emailDebtors"}];
  ss.addMenu( "SkyCastle", menuEntries );
}&lt;/pre&gt;

&lt;p&gt;Easy!  Now when somebody wants to collect, they just click the “SkyCastle -&gt; Email debtors” option and the appropriate reminder emails are sent out, from their own Gmail address.&lt;/p&gt;

&lt;h2&gt;Historical Charting&lt;/h2&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-5UgBZx-CjRk/T3XEKN0c6PI/AAAAAAAAAew/FUKtmc4NAKs/s1600/image01.png" imageanchor="1" style=""&gt;&lt;img border="0" height="270" width="400" src="http://4.bp.blogspot.com/-5UgBZx-CjRk/T3XEKN0c6PI/AAAAAAAAAew/FUKtmc4NAKs/s400/image01.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;I still had a couple of hours, and wanted to play with the UI and Google Charts services, so I decided to chart the “Shake Out” values over the history of the spreadsheet.  The existing cells are hard-coded to operate on the total sums from the full sheet, so I had to re-implement the math to track it line-by-line.  (This isn’t all bad, because I can use it to double check the existing arithmetic, which was sorely needed.)&lt;/p&gt;

&lt;p&gt;The basic sketch is as follows:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
var data = Charts.newDataTable()
                 .addColumn(Charts.ColumnType.NUMBER, "Row");
for (var i = 0; i != 4; i++) {
  data.addColumn(Charts.ColumnType.NUMBER, names[i]);
}

for (var i = 0; i != NUMROWS; i++) {
  var row = Array(5);
  // …
  // Process the current line here, and compute the shake-out.
  // …
  data.addRow(row);
}
data.build();&lt;/pre&gt;

&lt;p&gt;I’ve omitted the actual calculation, because it’s just a bunch of hacks specific to our spreadsheet formulas.  Each row contains the row number, and the accumulated shake-out thus far, and gets added to the `data` table.  I break out of the loop once I go off the end of my data and start hitting `NaNs`.&lt;/p&gt;

&lt;p&gt;To create the line chart and add it to a new UI window:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;var chart = Charts.newLineChart()
      .setDataTable(data)
      .setDimensions(700, 400)
      .setTitle("Debt History")
      .build();

var uiApp = UiApp.createApplication()
                 .setWidth(700)
                 .setHeight(400)
                 .setTitle("Payment History");
uiApp.add(chart);
ss.show(uiApp);
return uiApp;&lt;/pre&gt;

&lt;p&gt;After adding this function as another option in our custom `SkyCastle` menu and clicking it, we see a nice graph.  (I’m almost always on the bottom, but that’s because I make the actual rent and utility payments.)  The final entries are equal to the original "Shake Out" cells, so our old arithmetic seems correct, too.&lt;/p&gt;

&lt;h2&gt;Lessons Learned&lt;/h2&gt;

&lt;p&gt;The built-in debugger isn’t bad; use the `Select function` dropdown and click the bug icon.  I also used Logger.log() liberally while trying to get things working right.  (Go to `View -&gt; Logs` in the Script Editor to view that output.)&lt;/p&gt;

&lt;p&gt;Apps Script seems to work well, overall, and hooks into a nice and expanding array of Google products and data sources.  The GWT-backed UI service is a clever idea, though I barely had a chance to touch it.&lt;/p&gt;

&lt;p&gt;Thanks again to Jan and Google for hosting this Hackathon;  I can’t wait for the next one!&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://2.bp.blogspot.com/-cxl7kpY_SJk/T3W6yDnu6hI/AAAAAAAAAeI/f1bz1jxNoPw/s200/pic.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Rusty Mellinger&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Rusty Mellinger co-founded &lt;a href="http://illogic.com/"&gt;Illogic Inc&lt;/a&gt;, making heavy use of Google Apps and GWT.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-2577322136660748960?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/jYFH5NK0LX4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/2577322136660748960/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=2577322136660748960&amp;isPopup=true" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2577322136660748960?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2577322136660748960?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/jYFH5NK0LX4/first-attempt-at-apps-script-with.html" title="A First Attempt at Apps Script with Spreadsheets" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-6M6tsfTD3f4/T3XCoF7gpQI/AAAAAAAAAeU/Y5-XX7hkdGE/s72-c/image00.png" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/03/first-attempt-at-apps-script-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8NQXw7eSp7ImA9WhVQEE4.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-377759837543580653</id><published>2012-03-29T08:11:00.000-07:00</published><updated>2012-03-29T08:11:30.201-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-29T08:11:30.201-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="java" /><category scheme="http://www.blogger.com/atom/ns#" term="App Engine" /><category scheme="http://www.blogger.com/atom/ns#" term="python" /><title>Making PATCH requests from App Engine</title><content type="html">&lt;p&gt;&lt;code&gt;PATCH&lt;/code&gt; requests allow you to perform &lt;a href="https://code.google.com/apis/tasks/v1/performance.html#patch"&gt;partial updates &lt;/a&gt;on many of our REST APIs and in most cases can save bandwidth.&lt;/p&gt;

&lt;p&gt;If you have ever tried to do a PATCH request on an App Engine application, you probably realized that it is not possible and that the list of HTTP Methods allowed is whitelisted to the following methods only: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt;. Trying to perform a &lt;code&gt;PATCH&lt;/code&gt; request raises the following Exception:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;java.net.ProtocolException: &lt;code&gt;PATCH&lt;/code&gt; is not one of the supported http methods: [GET, POST, HEAD, PUT, DELETE]&lt;/pre&gt;

&lt;p&gt;There is a workaround to this. Most of our APIs support the &lt;code&gt;X-HTTP-Method-Override&lt;/code&gt; header. This header can be used in a &lt;code&gt;POST&lt;/code&gt; request to “fake” other HTTP methods. Simply set the value of the &lt;code&gt;X-HTTP-Method-Override&lt;/code&gt; header to the HTTP method you would like to actually perform.&lt;/p&gt;

&lt;p&gt;For example, to make a &lt;code&gt;PATCH&lt;/code&gt; request to the &lt;a href="http://code.google.com/apis/tasks/"&gt;Google Tasks API&lt;/a&gt; to update only the &lt;code&gt;Notes&lt;/code&gt; field of a particular task you could use the following HTTP request:&lt;/p&gt;

&lt;pre&gt;&lt;b&gt;POST&lt;/b&gt; /tasks/v1/lists/@default/tasks/TASK_ID HTTP/1.1
Host: www.googleapis.com
&lt;b&gt;X-HTTP-Method-Override: PATCH&lt;/b&gt;
Authorization:  Bearer &lt;REDACTED ACCESS TOKEN&gt;
Content-Type:  application/json
Content-Length: 31

{“Notes” : “Patch is working!”}&lt;/pre&gt;

&lt;p&gt;Which would be equivalent to this HTTP Request, which is not supported on App Engine:

&lt;pre&gt;&lt;b&gt;PATCH&lt;/b&gt; /tasks/v1/lists/@default/tasks/TASK_ID HTTP/1.1
Host: www.googleapis.com
Authorization:  Bearer &lt;REDACTED ACCESS TOKEN&gt;
Content-Type:  application/json
Content-Length: 31

{“Notes” : “Patch is working!”}&lt;/pre&gt;

&lt;p&gt;For instance, in an App Engine Java environment you could construct and execute this request this way:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;URL url = new URL("https://www.googleapis.com/tasks/v1/" +     
    "lists/@default/tasks/" + TASK_ID);
HTTPRequest request = new HTTPRequest(url, &lt;b&gt;HTTPMethod.POST&lt;/b&gt;);
request.addHeader(new HTTPHeader("X-HTTP-Method-Override", "&lt;b&gt;PATCH&lt;/b&gt;"));
request.addHeader(new HTTPHeader("Authorization", "Bearer " +
    ACCESS_TOKEN));
request.setPayload("{\"Notes\" : \"Patch is working!\"}".getBytes());

URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService();
HTTPResponse response = fetchService.fetch(request);&lt;/pre&gt;

&lt;p&gt;This trick can also be used if your application server is behind a firewall, behind a proxy server or in any other environment where HTTP methods other than &lt;code&gt;POST&lt;/code&gt; might not be allowed. In that case you could use the &lt;code&gt;X-HTTP-Method-Override&lt;/code&gt; header the same way to workaround these limitations.&lt;/p&gt;

&lt;p&gt;You may also use our &lt;a href="http://code.google.com/p/google-api-java-client/"&gt;Google APIs Client library for Java&lt;/a&gt; or our &lt;a href="http://code.google.com/p/google-api-python-client/"&gt;Google APIs Client library for Python&lt;/a&gt;, both of which have support for &lt;code&gt;PATCH&lt;/code&gt; requests and use the &lt;code&gt;X-HTTP-Method-Override&lt;/code&gt; header when appropriate.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://sites.google.com/site/developeradvocates/image/nicolas_garnier.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Nicolas Garnier&lt;/span&gt;   &lt;a class="alt" href="http://www.google.com/profiles/Nivco.las" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/nivco" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://code.google.com/events/#&amp;amp;speaker=nivco"&gt;events&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Nicolas joined Google’s Developer Relations in 2008. Since then he's worked on commerce oriented products such as Google Checkout and Google Base.  Currently, he is working on Google Apps with a focus on the Google Calendar API, the Google Contacts API, and the Tasks API. Before joining Google, Nicolas worked at Airbus and at the French Space Agency where he built web applications for scientific researchers.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-377759837543580653?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/4_gWS1uRmt0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/377759837543580653/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=377759837543580653&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/377759837543580653?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/377759837543580653?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/4_gWS1uRmt0/making-patch-requests-from-app-engine.html" title="Making PATCH requests from App Engine" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/03/making-patch-requests-from-app-engine.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0ECQno_fSp7ImA9WhVSFUU.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-8362496955897878201</id><published>2012-03-12T15:01:00.000-07:00</published><updated>2012-03-12T15:01:03.445-07:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-12T15:01:03.445-07:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Integrating Google Docs with Salesforce.com using Apps Script</title><content type="html">&lt;p&gt;
&lt;i&gt;&lt;b&gt;Editor's Note:&lt;/b&gt; Ferris Argyle is going to present &lt;a href="https://plus.google.com/u/0/116114690319921950962/posts/R4x7y4mAwTb"&gt;Salesforce Workflow Automation with Google Spreadsheet and Apps Script&lt;/a&gt; at Cloudforce. Do not miss Ferris's talk - &lt;a href="https://plus.google.com/u/0/105831704374179338116"&gt;Saurabh Gupta&lt;/a&gt;&lt;/i&gt;
&lt;/p&gt;


&lt;p&gt;
  As part of Google's Real Estate and Workplace Services (REWS) Green Team, the
  Healthy Materials program is charged with ensuring Google has the &lt;a
    href="http://www.google.com/green/operations/sustainable-buildings.html"&gt;healthiest
    workplaces possible&lt;/a&gt;. We collect and review information for thousands of
  building materials to make sure that our offices are free of formaldehyde,
  heavy metals, PBDEs and other toxins that threaten human health and reduce our
  productivity.
&lt;/p&gt;

&lt;h2&gt;
  A Case for using Google Docs and Salesforce.com
&lt;/h2&gt;
&lt;p&gt;
  My team, as you might imagine, has a great deal of data to collect and manage.
  We recently implemented &lt;a
    class="c9"
    href="http://www.salesforce.com/"&gt;Salesforce&lt;/a&gt;&lt;a
    class="c9"
    href="http://www.salesforce.com/"&gt;.com&lt;/a&gt; to manage that data, as it can
  record attributes of an object in a dynamic way, is good at tracking
  correspondence activity and allows for robust reports on the data, among many
  other functions.
&lt;/p&gt;
&lt;p&gt;
  We needed Saleforce.com to integrate with our processes in Google Apps. We
  wanted to continue collecting data using a Google Docs form but needed it
  integrated with Salesforce.com because we:
&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Liked the way the form looked and functioned&lt;/li&gt;
  &lt;li&gt;Wanted to retain continuity for our users, including keeping the same
    URL&lt;/li&gt;
  &lt;li&gt;Wanted a backup of submissions&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;
  And this is where Google Apps Script came to our rescue. We found that we
  could use Google Apps Script to create a new Case or Lead in Salesforce.com
  when a form is submitted through our Google Docs form. This allowed us to
  continue using our existing form and get our data directly and automatically
  into Salesforce.com.
&lt;/p&gt;

&lt;h2&gt;
  Google Docs + Apps Script + Salesforce.com = Integrated Goodness!
&lt;/h2&gt;

&lt;p&gt;
  Salesforce.com has two built-in options for capturing data online - Cases and
  Leads. Google Docs Forms can capture data for both of them. Set up your Case
  or Lead object with the desired fields in Salesforce.com. The next step is to
  generate the HTML for a form. You will use the IDs in the
  Salesforce.com-generated HTML when writing your Google Apps script.
&lt;/p&gt;
&lt;br/&gt;
&lt;h3&gt;
  &lt;b&gt;A) Getting the HTML in Salesforce.com:&lt;/b&gt;
&lt;/h3&gt;
&lt;p&gt;
  1. Login to Salesforce.com and go to Your Name &gt; Setup &gt; Customize &gt; Leads or
  Self-Service (for Cases) &gt; Web-to-Lead or Web-to-Case
&lt;/p&gt;
&lt;p&gt;
  2. Make sure Web-to-Lead/Web-to-Case is enabled. Click on Edit (Leads) or
  Modify (Cases) and enable if it is not.
&lt;/p&gt;
&lt;p&gt;
  3. Click on the 'Create Web to Lead Form' button (for Leads) or the 'Generate
  the HTML' link (for Cases)
&lt;/p&gt;
&lt;p&gt;
  4. Select the fields you want to capture and click 'Generate'. Save the HTML
  in a text file. You can leave 'Return URL' blank
&lt;/p&gt;

&lt;p&gt;
  &lt;img
    height="290"
    src="http://2.bp.blogspot.com/-GApiefJw6EY/T1gxaUJKBiI/AAAAAAAAAdA/VkET4u_E-Lk/s1600/image02.png"
    width="401"&gt;
&lt;/p&gt;
&lt;br/&gt;

&lt;h3&gt;
  &lt;b&gt;B) Setting up Google Apps Form/Spreadsheet:&lt;/b&gt;
&lt;/h3&gt;
&lt;p&gt;
  Create your form and spreadsheet (or open up the one you already have and want
  to keep using). This is very easy to do. Go to your &lt;a
    class="c9"
    href="https://docs.google.com"&gt;Docs&lt;/a&gt; and click on 'Create' to open a new
  form. Use the form editor to add the desired fields to your form- they'll show
  up as column headings in the corresponding spreadsheet. When someone fills out
  your form, their answers will show up in the right columns under those
  headings.
&lt;/p&gt;
&lt;br/&gt;
&lt;h3&gt;
  &lt;b&gt;C) Writing the Google Apps Script:&lt;/b&gt;
&lt;/h3&gt;
&lt;p&gt;
  The script is set up to take the data in specified cells from the
  form/spreadsheet and send it into designated fields in your Salesforce.com
  instance (identified by the org id in the HTML generated above). For example,
  the form submitter's email is recorded through the form in one cell, and sent
  into the email field in either the Lead or Case object in Salesforce.com.
&lt;/p&gt;
&lt;p&gt;
  1. Create a new script (Tools &gt; Script Manager &gt; New).
&lt;/p&gt;
&lt;p&gt;
  2. Write the script below using the pertinent information from your
  Salesforce.com-generated code (shown further down).
&lt;/p&gt;
&lt;pre class="prettyprint lang-js"&gt;
function SendtoSalesforce() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
  var row = sheet.getLastRow();
  var firstname = sheet.getRange(row, 2).getValue();
  var lastname = sheet.getRange(row, 3).getValue();
  var email = sheet.getRange(row, 4).getValue();
  var company = sheet.getRange(row, 5).getValue();
  var custom = sheet.getRange(row, 6).getValue();
  var resp = UrlFetchApp
      .fetch(
          'https://www.salesforce.com/servlet/servlet.WebToLead?encoding=UTF-8',
          {
            method: 'post',
            payload: {
              'orgid' : '00XXXXXXXX',
              'first_name' : firstname,
              'last_name' : lastname,
              'email' : email,
              'company' : company,
              '00YYYYYYYY' : custom,
              'external' : '1'
            }
          });
  Logger.log(resp.getContentText());
}

&lt;/pre&gt;

&lt;p&gt;
  Define your variables by directing the script to the correct cell (row, column
  number). Then in the payload section, match the field id from your
  Salesforce.com HTML (red) to the variable you defined (blue). For example, the
  email address of the submitter is defined as variable 'email', can be found in
  the 4th column of the last row submitted, and the id for that field in
  Salesforce.com is 'email'.
&lt;/p&gt;

&lt;p&gt;
  &lt;img height="270" width="520"
    src="http://4.bp.blogspot.com/-CyX-zPMmPQ0/T1gyCKOlYZI/AAAAAAAAAdM/QbX6y9DJytg/s1600/image01.jpg"
   &gt;
&lt;/p&gt;

&lt;br/&gt;

&lt;p&gt;
  Note that any custom fields you've created will have an alpha-numeric id.
&lt;/p&gt;
&lt;p&gt;
  3. Save your script and do a test run.
&lt;/p&gt;
&lt;br/&gt;
&lt;h3&gt;
  &lt;b&gt;D) Wiring Script to a Form Submission.&lt;/b&gt;
&lt;/h3&gt;
&lt;p&gt;
  To send your data automatically into Salesforce.com, you need to set a trigger
  that will run the script every time a form is submitted. To do this, go to
  your script and click Resources&gt;Current script's triggers.
&lt;/p&gt;
&lt;p&gt;
  1. Create a Trigger for your function so that it runs when a form is
  submitted.
&lt;/p&gt;

&lt;p&gt;
  &lt;img height="128" width="550"
    src="http://3.bp.blogspot.com/-qNa6adOUvq8/T1gzpQnXsmI/AAAAAAAAAdw/6I84rJIDthM/s1600/image00.png"
    &gt;
&lt;/p&gt;
&lt;br/&gt;
&lt;p&gt;
  2. Post the link to your form on your website, send it in an email, link to it
  on G+, etc. Get it out there!
&lt;/p&gt;
&lt;p&gt;
  That's it! Now when someone submits a form, the information will come into
  your spreadsheet, and then immediately be sent into Salesforce.com. You can
  adjust your Salesforce.com settings to create tasks when the information comes
  in, send out an auto-response to the person filling out the form and set up
  rules for who is assigned as owner to the record. You'll also have the
  information backed up in your spreadsheet.
&lt;/p&gt;

&lt;p&gt;
  This has been a great solution for our team, and we hope others find it useful
  as well!
&lt;/p&gt;
&lt;br/&gt;
&lt;table
  cellpadding="5"
  cellspacing="0"&gt;
  &lt;tbody&gt;
    &lt;tr style="background-color: #f2f2f2;"&gt;
      &lt;td&gt;&lt;img
        src="http://2.bp.blogspot.com/-FEg7ctZWtzs/T1g0vhgRDOI/AAAAAAAAAd8/dpDTU9-JpkI/s1600/image03.jpg"
        width="154"&gt;&lt;/td&gt;
      &lt;td valign="top"&gt;&lt;span class="largefont"&gt;Beth Sturgeon&lt;/span&gt; &amp;nbsp;
        &lt;br /&gt;
        &lt;div class="bio"&gt;
          &lt;br /&gt; Beth Sturgeon is a member of Google's Green Team in Mountain
          View, which makes sure that Google's offices are the healthiest, most
          sustainable workplaces around. Prior to Google, she had a past life as
          a wildlife researcher.

        &lt;/div&gt; &lt;br /&gt;
      &lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;/body&gt;
&lt;/html&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-8362496955897878201?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/U8TcXAWnctc" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/8362496955897878201/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=8362496955897878201&amp;isPopup=true" title="8 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8362496955897878201?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8362496955897878201?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/U8TcXAWnctc/integrating-google-docs-with.html" title="Integrating Google Docs with Salesforce.com using Apps Script" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-GApiefJw6EY/T1gxaUJKBiI/AAAAAAAAAdA/VkET4u_E-Lk/s72-c/image02.png" height="72" width="72" /><thr:total>8</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/03/integrating-google-docs-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;Ak8ERn44fyp7ImA9WhVSEkw.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-2997568199455370130</id><published>2012-03-08T07:00:00.000-08:00</published><updated>2012-03-08T07:00:07.037-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-03-08T07:00:07.037-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><title>Sheetcaster: 3D in Apps Script</title><content type="html">&lt;p&gt;&lt;i&gt;Editor’s note: This is a guest post by Thomas Coudray, Amaury de la Vieuville, and Ahmed Bougacha.  Thomas, Amaury, and Ahmed attended the Google Apps Script Hackathon in Paris, and in this post they are sharing their creative use of Google Apps Script to render a 3D scene in a Google Spreadsheet. -- Jan Kleinert&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Recently, we heard about the &lt;a href="http://googleappsdeveloper.blogspot.com/2011/11/google-apps-emea-developer-tour.html"&gt;Google Apps Script Hackathon&lt;/a&gt; arriving in Paris, France. We did not know much about Apps Script - heck, even JavaScript! Perfect occasion to learn something. We spent most of the event hacking around with the ever-growing collection of Google APIs. As a tribute to the folks over at id Software, we settled on one of the most fun (however useless) ways to use it: rendering a 3D scene in a spreadsheet.&lt;/p&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-nx7i_0b13HY/T1Z3azEbP5I/AAAAAAAAAco/6Ajo21MAx1Q/s1600/sheetcaster1.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="245" width="400" src="http://3.bp.blogspot.com/-nx7i_0b13HY/T1Z3azEbP5I/AAAAAAAAAco/6Ajo21MAx1Q/s400/sheetcaster1.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;The rendering is done using a technique called &lt;a href="http://en.wikipedia.org/wiki/Ray_casting"&gt;ray-casting&lt;/a&gt;, made popular by the 90s id Software game Wolfenstein 3D. Ray-casting is a really brilliant and straightforward algorithm:&lt;/p&gt;

&lt;p&gt;First, we render the background: color the upper (sky) and lower (floor) halves of the screen in different colors. We store the pixel colors in a matrix, the screen buffer:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
screen = new Array(SIZE_Y);
for (var lin = 0; lin &lt; SIZE_Y; lin++) {
  screen[lin] = new Array(SIZE_X);
  for (var col = 0; col &lt; SIZE_X; col++) {
    screen[lin][col] = colorToString((lin &lt; MID) ? UPPER_BG_COLOR
                                                 : LOWER_BG_COLOR);
  }
}&lt;/pre&gt;

&lt;p&gt;Note that we draw the screen only once the buffer is fully colored, to avoid the overhead of coloring cells individually.&lt;/p&gt;

&lt;p&gt;Then for each column of the screen:
&lt;ol&gt;
  &lt;li&gt;Cast a ray&lt;/li&gt;
  &lt;li&gt;Move along the ray until hitting a wall, calculate the distance to that wall&lt;/li&gt;
  &lt;li&gt;Draw a column whose height is inversely proportional to that distance&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;The trick is in the drawing: the upper and lower halves of the screen are symmetrical in shape, and the only computed value is the display height of the wall. The screen really is just a fancy formatting for an integer array of columns.&lt;/p&gt;

&lt;p&gt;The camera is represented using:
&lt;ul&gt;
  &lt;li&gt;Its (real-valued) x/y coordinates in the map plane&lt;/li&gt;
  &lt;li&gt;Its angle relative to some predefined direction&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;p&gt;We store these 3 values at the bottom of the sheet, to ensure persistence (else, each refresh would bring us back to the start location!).&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function Camera() {
  this.x = CAMERA_X;
  this.y = CAMERA_Y;
  this.theta = CAMERA_THETA;
  
  this.saveToSheet = function(sheet) {
    // The player state has to be saved between each frame
    sheet.getRange(STORE_LIN, 1, 1, 1).setValue(this.x);
    sheet.getRange(STORE_LIN, 2, 1, 1).setValue(this.y);
    sheet.getRange(STORE_LIN, 3, 1, 1).setValue(this.theta);
  };
  
  this.readFromSheet = function(sheet) {
    this.x = sheet.getRange(STORE_LIN, 1, 1, 1).getValue();
    this.y = sheet.getRange(STORE_LIN, 2, 1, 1).getValue();
    this.theta = sheet.getRange(STORE_LIN, 3, 1, 1).getValue();
  };

  ...
}&lt;/pre&gt;

&lt;p&gt;The map is a logical matrix, thus limiting us to discrete boxes for walls: for every cell, there either is (1), or is not (0), a wall:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
// starting 10x10 map
var S = 10;
var map =
  [
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
    [1, 0, 1, 0, 0, 0, 0, 0, 0, 1],
    [1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
    [1, 0, 0, 0, 1, 0, 0, 1, 0, 1],
    [1, 0, 0, 0, 1, 0, 0, 1, 0, 1],
    [1, 0, 1, 1, 1, 1, 0, 1, 0, 1],
    [1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
    [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
  ];&lt;/pre&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-_dSn08go0q4/T1Z4Dv8JCCI/AAAAAAAAAc0/bCR9FHEcbWU/s1600/sheetcaster2.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="98" width="100" src="http://4.bp.blogspot.com/-_dSn08go0q4/T1Z4Dv8JCCI/AAAAAAAAAc0/bCR9FHEcbWU/s200/sheetcaster2.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;It is also possible to modify the map in real-time: write a character in the boxes you want to swap, then hit Refresh map.&lt;/p&gt;

&lt;p&gt;Moving involves adding (or subtracting for backwards movements) to the xy coordinates, using basic trigonometry, but only after checking the validity of the move (i.e. that it will not collide with a wall):&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function Camera() {
  ...

  this.move = function(distance) {
    // return whether valid move or not
    x = this.x + Math.cos(this.theta) * distance;
    y = this.y + Math.sin(this.theta) * distance;
    if (isValidPos(x, y)) {
      this.x = x;
      this.y = y;
      return true;
    }
    return false;
  };
}

function moveUp() {
  readMapFromSheet(sheet);     // Retrieve the map from the sheet
  var camera = new Camera();
  camera.readFromSheet(sheet); // Retrieve the camera state from the sheet
  camera.move(0.5);
  raycast(camera);
}&lt;/pre&gt;

&lt;p&gt;Turning left (respectively right) is even simpler, adding (respectively subtracting) small constants to the camera angle (mod 2 PI):&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
function Camera() {
  ...

  this.rotate = function(alpha) {
    this.theta = (this.theta + alpha + 2 * Math.PI) % (2 * Math.PI);
  };
}

function lookRight() {
  readMapFromSheet(sheet);
  var camera = new Camera();
  camera.readFromSheet(sheet);
  camera.rotate(-0.25);
  raycast(camera);
}&lt;/pre&gt;

&lt;p&gt;Actual actions (moving/turning) are shown in a menu:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
  var subMenus = [
      {name:"Reset",functionName:"onOpen"},
      {name:"Refresh map",functionName:"refresh"},
      {name:"Move forward",functionName:"up"},
      {name:"Look left",functionName:"left"},
      {name:"Look right",functionName:"right"},
      {name:"Move backward",functionName:"down"},
      {name:"Turn around",functionName:"turn"},
  ];
  spreadsheet.addMenu("Sheetcaster", subMenus);&lt;/pre&gt;

&lt;p&gt;The ray is cast as follows:
&lt;ul&gt;
  &lt;li&gt;Its origin is the camera's 2D coordinates in the map plane&lt;/li&gt;
  &lt;li&gt;Its direction is calculated off the camera's and the column index (the center column will have the exact same direction as the camera; the other columns' directions depend on the field of view parameter)&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
/*
 * Given a value on the x axis (screen column),
 * return the ray that will be cast
 */
function getRay(camera, x) {
  var cos = Math.cos(camera.theta);
  var sin = Math.sin(camera.theta);

  // from -1 to 1: 0 being when x is the middle column
  var k = ((SIZE_X / 2) - x) / SIZE_X; 

  return new Vector_(
    cos / 2 - k * sin * K_FOV,
    sin / 2 + k * cos * K_FOV
  );
}&lt;/pre&gt;

&lt;p&gt;Moving the ray is the most involved step:
&lt;ul&gt;
  &lt;li&gt;Calculate the distance to the next vertical and horizontal borders&lt;/li&gt;
  &lt;li&gt;Move to the closest border&lt;/li&gt;
&lt;/ul&gt;
&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
while (!hit) {
  // Next potential wall is on the x axis
  if (dist.x &lt; dist.y) { 
    // Distance from the camera, delta: 
    /  Distance between each horizontal wall along the ray  
    dist.x      += delta.x; 
    // step.x is either 1 or -1, depending on the ray direction
    mapCoord.x  += step.x;  
    hit = readMap_(mapCoord.x, mapCoord.y);
  } else { // Next potential wall is on the y axis                 
    dist.y     += delta.y;
    mapCoord.y += step.y;
    hit = readMap_(mapCoord.x, mapCoord.y);
  }
}&lt;/pre&gt;

&lt;p&gt;The height of the drawn column is nothing fancy: the further the wall, the smaller-looking the wall, hence the smaller the height of the column.&lt;/p&gt;

&lt;p&gt;Again, nothing really complicated. However, the simplicity of this wall-height technique is the reason behind its major caveat: there is no clean way to look up or down: you can only turn left or right, and move forward or backward.&lt;/p&gt;

&lt;p&gt;Displaying the rendered image is done using a spreadsheet. Each cell becomes a small square pixel, its color being the background color of the cell. We pass our scren buffer matrix to the handy setBackgroundColors:&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;
sheet.getRange(1, 1, SIZE_Y, SIZE_X).setBackgroundColors(screen);&lt;/pre&gt;

&lt;p&gt;As you probably noticed, the low display density makes the sharp, jagged, edges really visible. Fear not, reader, for we also implemented anti-aliasing!&lt;/p&gt;

&lt;p&gt;The anti-aliasing algorithm is even simpler:
&lt;ol&gt;
  &lt;li&gt;Accumulate the length of &lt;i&gt;runs&lt;/i&gt; (successions of same-sized columns)&lt;/li&gt;
  &lt;li&gt;Draw a gradient, from the background (wall and floor) to the wall, above (and below) the columns&lt;/li&gt;
&lt;/ol&gt;
&lt;/p&gt;

&lt;p&gt;When the runs are really small (&lt; 5 columns), we attenuate the gradient intensity, as it would only add another pixel above (below) the column, thus rendering the antialiasing utterly useless.&lt;/p&gt;

&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-1slD7fu-Klw/T1ZwQo4amvI/AAAAAAAAAbs/kGQidbcisok/s1600/sheetcaster3.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" height="241" width="301" src="http://1.bp.blogspot.com/-1slD7fu-Klw/T1ZwQo4amvI/AAAAAAAAAbs/kGQidbcisok/s320/sheetcaster3.png" /&gt;&lt;/a&gt;&lt;/div&gt;

&lt;p&gt;Real-time was not an objective, the main problem being controlling the player/camera. Scripted movements should however be quite easy to implement with a fixed duration loop, restarting itself using an Apps Script recurrent time-driven trigger (a minute-long loop, repeated every minute). This is left as an exercise to the reader.&lt;/p&gt;

&lt;p&gt;Please feel free to &lt;a href="https://docs.google.com/a/google.com/spreadsheet/ccc?key=0At-4449Da_xrdGlfaFpEUnB1Vy05dW9lcmNsMkVsSFE&amp;newcopy"&gt;copy the script&lt;/a&gt; and walk around this Apps Script virtual world.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://4.bp.blogspot.com/-u3ybvv83tR8/T1Z0rxUUO8I/AAAAAAAAAcE/P9vM8SE-9Rw/s200/thomas_coudray.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Thomas Coudray&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Thomas is interested in low level computing and application security.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://2.bp.blogspot.com/-M8KQSWBiXU8/T1Z1errvgMI/AAAAAAAAAcc/6MzEjVCn-k8/s200/amaury_de_la_vieuville.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Amaury de la Vieuville&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Amaury is passionate about algorithmic problem-solving and software engineering.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://2.bp.blogspot.com/-4vBNkqMEFf0/T1Z1a5VaD5I/AAAAAAAAAcQ/ctHBVKggcOs/s200/ahmed_bougacha.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Ahmed Bougacha&lt;/span&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Ahmed is interested in kernels, compilers and theoretical computer science.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-2997568199455370130?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/RMeAQ4c4dAY" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/2997568199455370130/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=2997568199455370130&amp;isPopup=true" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2997568199455370130?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/2997568199455370130?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/RMeAQ4c4dAY/sheetcaster-3d-in-apps-script.html" title="Sheetcaster: 3D in Apps Script" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-nx7i_0b13HY/T1Z3azEbP5I/AAAAAAAAAco/6Ajo21MAx1Q/s72-c/sheetcaster1.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/03/sheetcaster-3d-in-apps-script.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEEBQnYzfyp7ImA9WhRaGUk.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-5611251123226969954</id><published>2012-02-22T12:30:00.000-08:00</published><updated>2012-02-22T12:30:53.887-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-22T12:30:53.887-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><title>Create a Spreadsheet User Directory with Apps Script</title><content type="html">As a consultant helping companies move to the Google cloud, I receive many
feature requests before, during, and after each migration. Often I’m asked
about re-creating small and specific solutions that support particular
business needs not fully covered by Google Apps out of the box. In many
cases, a simple &lt;a class="c7" href="http://code.google.com/googleapps/appsscript/index.html"&gt;Google Apps Script&lt;/a&gt;&amp;nbsp;solution satisfies the business requirement.
&lt;br /&gt;
&lt;h2&gt;





What is the Google Spreadsheet User Directory?&lt;/h2&gt;
The “Google Spreadsheet User Directory” is a solution I’m frequently asked about. Google Apps Domain administrators can use a simple Apps Script that can be saved into a Google Spreadsheet and then set to run on a schedule, via a “time-driven” trigger. By using the Google Profiles API (available only for domain administrators),&amp;nbsp;domain administrators can create a Google Spreadsheet which contains Google Apps domain user information.The user profile data can then be consumed and used by other business logic code, either in the spreadsheet itself or elsewhere.
&lt;br /&gt;
Using Apps Script to provide this kind of solution was an obvious choice for
the following reasons.&lt;br /&gt;
&lt;br /&gt;
&lt;ol&gt;
&lt;li&gt;Apps Script makes the Google Spreadsheet User Directory a simple, flexible solution that the customer can quickly understand and extend. The JavaScript syntax is easy to learn and program in, and there is no need to compile and deploy code.&lt;/li&gt;
&lt;li&gt;The Apps Script code is conveniently integrated into Google Spreadsheets, so there is no need to use any other software. Advanced
functions can be exposed to end users for data manipulation through the spreadsheet menu, and scheduling an Apps Script to run&amp;nbsp;at a regular interval&amp;nbsp;is trivial via the Spreadsheet “Triggers” mechanism.&lt;/li&gt;
&lt;li&gt;Google Apps Script provides services for accessing Google Profiles, Contact Info, and Google Groups plus Google Docs, Google Sites, Google Charts, and more. &amp;nbsp;The Google Spreadsheet User Directory script makes use of both the new Apps Script &lt;a href="http://code.google.com/googleapps/appsscript/service_domain.html"&gt;Domain Services API&lt;/a&gt;&amp;nbsp;and the &lt;a href="http://code.google.com/googleapps/domain/profiles/developers_guide.html"&gt;GData Profiles API&lt;/a&gt;, via the “UrlFetch” service.
&lt;/li&gt;
&lt;li&gt;The Apps Script code can be easily shared through Google&amp;nbsp;Spreadsheet templates and through the Google Script gallery.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;





Using the Google Spreadsheet User Directory&lt;/h2&gt;
The Google Spreadsheet User Directory code consists of a primary scanUserProfiles()&amp;nbsp;function and some supporting “utility” functions. The three steps for setting up the code to run are:
1. Set up the “Consumer_Key” and “Consumer_Secret” ScriptProperties&amp;nbsp;and run the scanUserProfiles()&amp;nbsp;function in the Apps Script integrated development environment to get the first “Authorization Required” screen. (I’ve included an illustration below... Choose “Authorize.”).
&lt;img height="195" src="http://1.bp.blogspot.com/-KDexHR1KdLU/T0UclMetV4I/AAAAAAAAAaA/wnfxEGd9wzI/s1600/image01.gif" width="364" /&gt;
&lt;br /&gt;
&lt;table&gt;&lt;tbody&gt;
&lt;tr&gt;         &lt;td&gt;2. Since scanUserProfiles()&amp;nbsp;uses OAuth with UrlFetch&amp;nbsp;to get User Profile information via the GData API, it needs to be run at least
one more time inside of the Apps Script IDE, so that the OAuth “Authorize” prompt can be shown to the programmer and accepted.&lt;/td&gt;
&lt;td&gt;&lt;img height="132" src="http://2.bp.blogspot.com/-Tmi70BacJu8/T0Ue2xNfXOI/AAAAAAAAAaM/QrTNRhDPf3k/s200/image00.gif" width="226" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;   &lt;/table&gt;
3. After authorization, the scanUserProfiles() script is free to make authorized requests to the Google User Profiles feed, as long as the
developer who saved it has “domain admin” rights.

&lt;br /&gt;
&lt;h2&gt;





Design of the Google Spreadsheet User Directory&lt;/h2&gt;
The following snippets show the OAuth setup, the user profiles Url&amp;nbsp;setup,
and the initial UrlFetch.

&lt;br /&gt;
&lt;pre class="prettyprint linenums:56"&gt;var oAuthConfig1 = UrlFetchApp.addOAuthService("googleProfiles");
oAuthConfig1.setRequestTokenUrl("https://www.google.com/accounts/OAuthGetRequestToken?scope=https:// www.google.com/m8/feeds/profiles");
oAuthConfig1.setAccessTokenUrl("https://www.google.com/accounts/OAuthGetAccessToken");
oAuthConfig1.setAuthorizationUrl("https://www.google.com/accounts/OAuthAuthorizeToken?oauth_callback=https:// spreadsheets.google.com/macros");
oAuthConfig1.setConsumerKey(ScriptProperties.getProperty("Consumer_Key"));
oAuthConfig1.setConsumerSecret(ScriptProperties.getProperty("Consumer_Secret"));
var options1 = {
    oAuthServiceName : "googleProfiles",
    oAuthUseToken : "always",
    method : "GET",
    headers : {
      "GData-Version" : "3.0"
    },
    contentType : "application/x-www-form-urlencoded"
};
  &lt;/pre&gt;
&lt;pre class="prettyprint linenums:73"&gt;var theUrl = "";
if (nextUrl == "") {
  theUrl =
    "https://www.google.com/m8/feeds/profiles/domain/" + domain +
      "/full?v=3&amp;amp;max-results=" + profilesPerPass + "&amp;amp;alt=json";
} else {
  theUrl = nextUrl;
}
&lt;/pre&gt;
&lt;pre class="prettyprint linenums:81"&gt;if (theUrl != "DONE") {
  var largeString = "";
  try {
    var response = UrlFetchApp.fetch(theUrl, options1);
    largeString = response.getContentText();
  } catch (problem) {
    recordEvent_(problem.message, largeString, ss);
  }
}
&lt;/pre&gt;
&lt;pre class="prettyprint linenums:95"&gt;var provisioningJSONObj = null;
    var jsonObj = JSON.parse(largeString);
    var entryArray = jsonObj.feed.entry;  
&lt;/pre&gt;
The "nextUrl" variable above (line 74) is being pulled from a cell in the
spreadsheet, where I'm saving the "next" link from the fetched data. (If
there’s no “next” link, I save "DONE" to the same spreadsheet cell.) To
fetch JSON, I’m appending the parameter &amp;amp;;alt=json&amp;nbsp;on lines 75 and 76.

After I’ve got my JSON object, I create an array to store the data that I
will be writing out to the spreadsheet. I set the array default values and
make liberal use of try-catch blocks in this code, since there’s no telling
which of these fields will be populated, and which will not.

&lt;br /&gt;
&lt;pre class="prettyprint linenums:106"&gt;for (var i=0; i&amp;lt;entryArray.length; i++) {
    var rowArray = new Array();
    rowArray[0] = "";
    rowArray[1] = "";
    rowArray[2] = "";
    try { rowArray[0] = entryArray[i].gd$name.gd$fullName.$t; } catch (ex) {} //fullname
    try { rowArray[1] = entryArray[i].gd$name.gd$givenName.$t; } catch (ex) {} //firstname
    try { rowArray[2] = entryArray[i].gd$name.gd$familyName.$t; } catch (ex) {} //lastname
&lt;/pre&gt;
At the end of the data collection process for a single record/row, I add the rowArray&amp;nbsp;to another single-element array called valueArray&amp;nbsp;(line 207), to create a 2-D array that I can use with range.setValues&amp;nbsp;to commit my data to the spreadsheet in one shot (line 209).

&lt;br /&gt;
&lt;pre class="prettyprint linenums:205"&gt;var updateRow = getNextRowIndexByUNID_(rowArray[3],4,stageSheet);
var valueArray = new Array();
valueArray.push(rowArray);
var outputRange = stageSheet.getRange(updateRow, 1, 1, 12);
outputRange.setValues(valueArray);

&lt;/pre&gt;
The function getNextRowIndexByUNID&amp;nbsp;(line 205) just finds the next available
    row on the “staging” sheet of the spreadsheet, so I can write data to it.
    The code is inside of a “for” loop (starting on line 106) that executes once
    for each entry in the current JSON object (created lines 96 and 97).
  
&lt;br /&gt;
&lt;pre class="prettyprint linenums:267"&gt;} else {
    // COPY CHANGES TO "PRODUCTION" TAB OF SPREADSHEET
    var endTime = new Date();
    setSettingFromArray_("LastPassEnded",getZeroPaddedDateTime_(endTime),settingsArray,setSheet);
    if (parseInt(getSettingFromArray_("StagingCopiedToProduction",settingsArray)) == 0) {
     // THIS DOES A TEST-WRITE, THEN A "WIPE," THEN COPIES STAGING TO
      // PRODUCTION
     var copied = copySheet_(ss,"Staging","Employees");
     if (copied == "SUCCESS") {
       var sortRange = empSheet.getRange(2,1,empSheet.getLastRow(),empSheet.getLastColumn());
       sortRange.sort([3,2]); // SORT BY COLUMN C, THEN B
       // RESET SETTINGS
       setSettingFromArray_("NextProfileLink","",settingsArray,setSheet);
       setSettingFromArray_("LastRowUpdated",0,settingsArray,setSheet);
       setSettingFromArray_("StagingCopiedToProduction",1,settingsArray,setSheet);
     }
    }
} // end if "DONE"

  &lt;/pre&gt;
If the script finds “DONE” in the “NextProfileLink” cell of the spreadsheet,
    it will skip doing another UrlFetch to the next feed link (line 81).
    Instead, it will copy all records from the “staging” sheet of the
    spreadsheet to the “production” one, via a utility function called
    “copySheet” (line 273). Then it will sort the range, reset the copy
    settings, and it will mark another designated cell,
    “StagingCopiedToProduction” as “1” in the spreadsheet, to stop any further
    runs that day.
  
&lt;br /&gt;
&lt;h2&gt;



Scheduling the Google Spreadsheet User Directory Script to Run&lt;/h2&gt;
Below are the triggers I typically set up for the Spreadsheet User
    Directory. I recommend setting scanUserProfiles()&amp;nbsp;to run on an interval of
    less than 30 minutes, since the Google-provided token in each
    “NextProfileLink” url lasts about that long. I also recommend running the
    WipeEventLog()&amp;nbsp;utility function at the end of each day, just to clear data
    from prior runs&amp;nbsp;from the EventLog&amp;nbsp;tab of the spreadsheet.

&lt;br /&gt;
&lt;br /&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-og4WedYcd38/T0Uk1ZnDtuI/AAAAAAAAAaY/8HonPAXwUjA/s1600/image04.gif" imageanchor="1"&gt;&lt;img border="0" height="116" src="http://4.bp.blogspot.com/-og4WedYcd38/T0Uk1ZnDtuI/AAAAAAAAAaY/8HonPAXwUjA/s400/image04.gif" width="400" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;h2&gt;

Conclusion&lt;/h2&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;
&lt;a href="http://4.bp.blogspot.com/-V6p6xzM5ihQ/T0Ul-CiYNbI/AAAAAAAAAaw/WJS5EeFa914/s1600/image03.gif" imageanchor="1" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img border="0" height="200" src="http://4.bp.blogspot.com/-V6p6xzM5ihQ/T0Ul-CiYNbI/AAAAAAAAAaw/WJS5EeFa914/s200/image03.gif" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;
Above I’ve outlined how to create a basic User Directory out of a Google Spreadsheet and Apps Script that will always keep itself current. Since Google Spreadsheets support the&lt;a href="http://code.google.com/apis/chart/interactive/docs/reference.html"&gt;&amp;nbsp;&lt;/a&gt;&lt;a href="http://code.google.com/apis/chart/interactive/docs/reference.html"&gt;Google                Visualization API&lt;/a&gt;&amp;nbsp;and a&lt;a class="c7" href="http://code.google.com/apis/chart/interactive/docs/querylanguage.html"&gt;&amp;nbsp;&lt;/a&gt;&lt;a class="c7" href="http://code.google.com/apis/chart/interactive/docs/querylanguage.html"&gt;query                language&lt;/a&gt;&amp;nbsp;for sorting and filtering data, all kinds of              possibilities open up for creating corporate “directory” gadgets for              Google Sites (see the image at right) and for enabling business              processes that require workflows, role lookups, or the manipulation              of permissions on content in the various Google Apps.


&lt;br /&gt;
Using Apps Script made this solution quick and easy to produce and flexible
    enough to be extended and used in many different ways. The code is easy to
    share as well. If you’d like to give the Google Spreadsheet User Directory a
    try, then please copy &lt;a class="c7" href="https://docs.google.com/spreadsheet/ccc?key=0AqzFlfwsM0NrdE5NdldqcFVCTHk3ZWhBWFA0Yk5MWmc"&gt;this
      spreadsheet template&lt;/a&gt;, and modify and re-authorize it to run in your own
    domain.
  
Enjoy!

&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://4.bp.blogspot.com/-z_Kb0x-4Ja0/T0UomLQk_MI/AAAAAAAAAa8/aadTFsfMiXs/s1600/image02.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Shel Davis&lt;/span&gt;
&lt;br /&gt;
&lt;div class="bio"&gt;
&lt;br /&gt;
Guest author Shel Davis is a senior consultant with Cloud Sherpas, a company recently named the &lt;a href="http://goo.gl/UsSrq"&gt;Google Enterprise 2011 Partner of the Year&lt;/a&gt;. When Shel is not working on solutions for customers, he’s either teaching classes on Google Apps and Apps Script (&lt;a href="http://www.cloudsherpas.com/services/training-and-change-management/google-apps-script-training"&gt;Google Apps Script Training&lt;/a&gt;), or he’s at home, playing with his kids.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-5611251123226969954?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/HfSSOq707T8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/5611251123226969954/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=5611251123226969954&amp;isPopup=true" title="7 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5611251123226969954?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/5611251123226969954?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/HfSSOq707T8/create-spreadsheet-user-directory-with.html" title="Create a Spreadsheet User Directory with Apps Script" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-KDexHR1KdLU/T0UclMetV4I/AAAAAAAAAaA/wnfxEGd9wzI/s72-c/image01.gif" height="72" width="72" /><thr:total>7</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/02/create-spreadsheet-user-directory-with.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkEEQHw5fyp7ImA9WhRaFE8.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-9164722699340990647</id><published>2012-02-16T11:30:00.000-08:00</published><updated>2012-02-16T11:30:01.227-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-16T11:30:01.227-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Come Learn About Apps Script in Washington, DC</title><content type="html">&lt;p&gt;&lt;i&gt;Editor's note: This has been cross-posted from the &lt;a href="http://googlecode.blogspot.com/"&gt;Google Code blog&lt;/a&gt; -- Jan Kleinert&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Google Apps Script&lt;/a&gt; is a JavaScript cloud scripting language that provides easy ways to automate tasks across Google products and third party services. If you want to learn more about Google Apps Script, collaborate with other developers, and meet the Apps Script team, here’s your chance! We will be holding an &lt;a href="https://sites.google.com/site/appsscripthackathondc/"&gt;Apps Script hackathon&lt;/a&gt; in Washington, DC on Wednesday, March 7 from 2pm - 8pm.&lt;br /&gt;
&lt;br /&gt;
After we cover the basics of Apps Script, you can code along with us as we build a complete script, or you can bring your own ideas and get some help and guidance from the team. There will be food, power, and Apps Script experts available to help throughout the day. Just bring your laptop, ideas, enthusiasm, and basic knowledge of JavaScript. Check out out the &lt;a href="https://sites.google.com/site/appsscripthackathondc/"&gt;details of the event&lt;/a&gt; and be sure to &lt;a href="https://sites.google.com/site/appsscripthackathondc/registration"&gt;RSVP&lt;/a&gt; to let us know you’re coming.&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;
&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="https://sites.google.com/site/developeradvocates/image/jan_kleinert.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Jan Kleinert&lt;/span&gt;   &lt;a class="alt" href="https://plus.google.com/u/0/111752662319997528493/posts" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/jankleinert" rel="me" target="_blank"&gt;twitter&lt;/a&gt;  &lt;br /&gt;
&lt;div class="bio"&gt;&lt;br /&gt;
Jan is a Developer Programs Engineer based in NYC, focusing on helping developers get the most out of Google Apps Script. Prior to Apps Script, she worked on Commerce, helping merchants integrate with Google Checkout and on Chrome, helping developers build great web apps.&lt;/div&gt;&lt;br /&gt;
&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-9164722699340990647?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/Y_WnH-aTvmA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/9164722699340990647/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=9164722699340990647&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9164722699340990647?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/9164722699340990647?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/Y_WnH-aTvmA/come-learn-about-apps-script-in.html" title="Come Learn About Apps Script in Washington, DC" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/02/come-learn-about-apps-script-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0YBR348eip7ImA9WhRbF04.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-7475856700796362079</id><published>2012-02-08T11:59:00.000-08:00</published><updated>2012-02-08T11:59:16.072-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-02-08T11:59:16.072-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Charts, Gmail, Maps and Properties Services Graduating from Experimental</title><content type="html">The &lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Google Apps Script&lt;/a&gt; team strives to achieve a very high level of responsiveness to our community. We iterate fast to deliver new features and functionality. Releasing new Apps Script services under the “Experimental” flag allows us to gather valuable feedback from you. Real world exposure to these new experimental Apps Script services makes it easy for us to prioritize features, improve API design and documentation, and identify bugs and use cases. On top of that, experimental releases give us a way to support our more advanced users, who are perfectly happy to live on the cutting edge and get their hands on cool new Apps Script services as early as possible.&lt;br /&gt;
&lt;br /&gt;
We put thorough consideration into the use of the Experimental flag. Not every new Apps Script service that we launch is experimental. In October, we launched the &lt;a href="http://code.google.com/googleapps/appsscript/service_lock.html"&gt;Lock&lt;/a&gt; and &lt;a href="http://code.google.com/googleapps/appsscript/service_cache.html"&gt;Cache&lt;/a&gt; service as non-experimental. We believed that these two services were fundamentally mature, and thus able to be launched as non-experimental.&lt;br /&gt;
&lt;br /&gt;
Today, we are graduating four services from experimental status:&lt;ol&gt;&lt;li&gt;&lt;a href="http://code.google.com/googleapps/appsscript/service_charts.html"&gt;Charts Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/googleapps/appsscript/service_gmail.html"&gt;Gmail Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/googleapps/appsscript/service_maps.html"&gt;Maps Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://code.google.com/googleapps/appsscript/service_properties.html"&gt;Properties Service&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;You can expect us to continue on this path of careful deliberation in deciding when to use the experimental flag. We will continue to evaluate the design, documentation, and overall strengths of each experimental API and continue to work hard to graduate them to fully supported APIs. We welcome your feedback in helping us make Apps Script better. You can post your feature requests on our &lt;a href="http://code.google.com/p/google-apps-script-issues/issues/list"&gt;tracker&lt;/a&gt; and ask any question in our &lt;a href="https://groups.google.com/a/googleproductforums.com/forum/#!forum/apps-script"&gt;forum&lt;/a&gt;. If you are curious on how to use the above mentioned APIs then check out the tutorials for the &lt;a href="http://code.google.com/googleapps/appsscript/articles/gmail-stats.html"&gt;Charts and Gmail Services&lt;/a&gt;, the &lt;a href="http://code.google.com/googleapps/appsscript/articles/maps_tutorial.html"&gt;Maps Service&lt;/a&gt; and excellent example of using &lt;a href="http://code.google.com/googleapps/appsscript/articles/twitter_tutorial.html"&gt;Properties Service&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
The Apps Script team is standing by to help you!&lt;br /&gt;
&lt;br /&gt;
&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;br /&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://3.bp.blogspot.com/-AfmuFkbdBJQ/TjHzJVAWTXI/AAAAAAAAAMo/xVhok61rw7I/s400/Google%2BChromeScreenSnapz157.png"" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Saurabh Gupta&lt;/span&gt;   &lt;a class="alt" href="https://profiles.google.com/sg1705" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/#gluemesh" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://www.gluemesh.com/" rel="me" target="_blank"&gt;blog&lt;/a&gt;&lt;br /&gt;&lt;div class="bio"&gt;&lt;br /&gt;Saurabh is a Developer Programs Engineer at Google. He works closely with Google Apps Script developers to help them extend Google Apps. Over the last 10 years, he has worked in the financial services industry in different roles. His current mission is to bring automation and collaboration to Google Apps users.&lt;/div&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-7475856700796362079?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/KPlpR9TOv4M" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/7475856700796362079/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=7475856700796362079&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/7475856700796362079?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/7475856700796362079?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/KPlpR9TOv4M/charts-gmail-maps-and-properties.html" title="Charts, Gmail, Maps and Properties Services Graduating from Experimental" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-AfmuFkbdBJQ/TjHzJVAWTXI/AAAAAAAAAMo/xVhok61rw7I/s72-c/Google%2BChromeScreenSnapz157.png" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/02/charts-gmail-maps-and-properties.html</feedburner:origLink></entry><entry gd:etag="W/&quot;AkYEQ3c7cCp7ImA9WhRUFk0.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-8376078335796651701</id><published>2012-01-26T12:01:00.000-08:00</published><updated>2012-01-26T12:01:42.908-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-26T12:01:42.908-08:00</app:edited><title>Reading Query Results from Calendar in Pages</title><content type="html">&lt;p&gt;What’s the difference between reality and theory?  In theory, there is no difference.  But reality often imposes unanticipated constraints on developers.  These may come in the form of bandwidth restrictions, memory limits, timeouts, or other requirements of the systems that interact with your application.&lt;/p&gt;

&lt;p&gt;My team recently built an application that helps us analyze the scheduling and usage of conference rooms at Google.  We use the new &lt;a href="http://code.google.com/apis/calendar/v3/getting_started.html"&gt;Calendar API v3&lt;/a&gt; on &lt;a href="http://code.google.com/appengine/"&gt;Google App Engine&lt;/a&gt; to read the rooms’ schedules, which we combine with actual occupancy data to calculate utilization and other metrics.&lt;/p&gt;

&lt;p&gt;As you might imagine, Google has a lot of conference rooms (I believe the last official count was “more than twelve.”)  And many of the rooms seem to be booked fairly solid.  That means we need to read a lot of data from Calendar.  So much, in fact, that our queries time out if we try to read an entire calendar at once.  But the API team anticipated “Google scale” use and designed a mechanism that allows us to retrieve data in batches.&lt;/p&gt;

&lt;p&gt;The idea is simple.  When you create a request, you specify the page size: the maximum number of results you’d like Calendar to return in one batch.  Calendar returns the data you requested, along with an opaque page token, which you can think of as a bookmark.  To retrieve the next batch of data, you ask the API for the next page token and include the new token in your next request.  The page token keeps track of the results you’ve already seen, so Calendar can send the next batch each time.  You repeat this process until you’ve exhausted all the results.&lt;/p&gt;

&lt;p&gt;Here’s how we did this in Java:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
public void getRoomEvents(String roomEmail) throws IOException {
    // Create a request to list this room’s events (see code, below)
    Calendar.Events.List listRequest = getListRequest(roomEmail);
    do {
      // Retrieve one page of events
      Events events = executeListRequest(listRequest);
      List&lt;Event&gt; eventList = events.getItems();

      // Process each event
      for (Event event : eventList) {
        processEvent(event);
      }

      // Update the page token
      listRequest.setPageToken(events.getNextPageToken());

    // Stop when all results have been retrieved
    } while (listRequest.getPageToken() != null);
  }

  // Create a request to list the events for a room
  private Calendar.Events.List getListRequest(String roomEmail)
        throws IOException {
    return calendarClient.events().list(roomEmail)
        .setMaxResults(1000) // Limit each response to 1000 events
        .setPageToken(null)  // Start with the first page of results
        // Return an individual event for each instance occurrence of a
        // recurring event
        .setSingleEvents(true); 
  }
&lt;/pre&gt;

&lt;p&gt;We call &lt;code&gt;getRoomEvents()&lt;/code&gt; for each room, using the room’s email address to identify it to Calendar.  (You can retrieve events from your own calendar by substituting your own email address.)  Then &lt;code&gt;getListRequest()&lt;/code&gt; creates a request that we will send to Calendar.  The request asks for a list of up to 1000 events from the room’s calendar.&lt;/p&gt;

&lt;p&gt;The remainder of &lt;code&gt;getRoomEvents()&lt;/code&gt; is a loop that executes the request, processes the results, and updates the page token in preparation for the next request.  The loop continues, retrieving and processing each subsequent page of results, until the entire list has been returned.  The call to &lt;code&gt;getNextPageToken()&lt;/code&gt; indicates the end of the results by returning a null value.&lt;/p&gt;

&lt;p&gt;By paginating our requests we avoid timeouts and reduce memory requirements.  As an added benefit, each request completes fairly quickly, which means it’s also quick to retry if an error should occur.  And finally, a multithreaded application may be able to process one or more pages of results while it retrieves the next, speeding execution.  These advantages have led developers at Google to adopt pagination as a best practice.  Look for it in our APIs when you need to exchange large amounts of data, and consider adding it to your own services.&lt;/p&gt;

&lt;p&gt;If you have questions about our services or APIs, or if you want to see what other developers are doing with Google Calendar, check the discussions and documentation in the &lt;a href="http://code.google.com/apis/calendar/community/forum.html"&gt;Google Apps Calendar API forum&lt;/a&gt;.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" width="76px" style="width:76px;" src="http://4.bp.blogspot.com/-7KcjLbsjxrg/TyGvWYqnXqI/AAAAAAAAAZc/ecLG4cD4wxU/s200/adamliss.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Adam Liss&lt;/span&gt;   &lt;a class="alt" href="https://plus.google.com/u/0/116984609600395690760/about" rel="me" target="_blank"&gt;profile&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Adam is an engineer who believes that "technical" shouldn't necessarily mean "difficult."  He enjoys building infrastructure and tools that make Googlers more productive.  Before joining Google in 2010, he built network-security appliances and one of the first wireless application delivery platforms.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-8376078335796651701?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/rA7MbtTZ7Ac" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/8376078335796651701/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=8376078335796651701&amp;isPopup=true" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8376078335796651701?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8376078335796651701?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/rA7MbtTZ7Ac/reading-query-results-from-calendar-in.html" title="Reading Query Results from Calendar in Pages" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://4.bp.blogspot.com/-7KcjLbsjxrg/TyGvWYqnXqI/AAAAAAAAAZc/ecLG4cD4wxU/s72-c/adamliss.png" height="72" width="72" /><thr:total>1</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/reading-query-results-from-calendar-in.html</feedburner:origLink></entry><entry gd:etag="W/&quot;A0cFRX46eyp7ImA9WhRUFE4.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-4033892497175955537</id><published>2012-01-24T13:03:00.000-08:00</published><updated>2012-01-24T13:03:34.013-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-24T13:03:34.013-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Developers" /><title>Tips on using the APIs Discovery Service</title><content type="html">&lt;p&gt;Our newest set of APIs - &lt;a href="http://code.google.com/apis/tasks/v1/getting_started.html"&gt;Tasks&lt;/a&gt;, &lt;a href="http://code.google.com/apis/calendar/v3/getting_started.html"&gt;Calendar v3&lt;/a&gt;, &lt;a href="https://developers.google.com/+/api/"&gt;Google+&lt;/a&gt; to name a few - are supported by the &lt;a href="http://code.google.com/apis/discovery/"&gt;Google APIs Discovery Service&lt;/a&gt;. The Google APIs Discovery service offers an interface that allows developers to programmatically get API metadata such as:&lt;/p&gt;
&lt;ul&gt;
 &lt;li&gt;A directory of supported APIs.&lt;/li&gt;
 &lt;li&gt;A list of API resource schemas based on JSON Schema.&lt;/li&gt;
 &lt;li&gt;A list of API methods and parameters for each method and their inline documentation.&lt;/li&gt;
 &lt;li&gt;A list of available OAuth 2.0 scopes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The APIs Discovery Service is especially useful when building developer tools, as you can use it to automatically generate certain features. For instance we are using the APIs Discovery Service in &lt;a href="http://code.google.com/apis/discovery/libraries.html"&gt;our client libraries&lt;/a&gt; and in our &lt;a href="https://code.google.com/apis/explorer/"&gt;APIs Explorer&lt;/a&gt; but also to generate some of our &lt;a href="https://code.google.com/apis/tasks/v1/reference.html"&gt;online API reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Because the APIs Discovery Service is itself an API, you can use features such as &lt;a href="http://googlecode.blogspot.com/2010/03/making-apis-faster-introducing-partial.html"&gt;partial response&lt;/a&gt; which is a way to get only the information you need. Let’s look at some of the useful information that is available using the APIs Discovery Service and the partial response feature.&lt;/p&gt;

&lt;h3&gt;List the supported APIs&lt;/h3&gt;
&lt;p/&gt;
&lt;p&gt;You can get the list of all the APIs that are supported by the discovery service by sending a &lt;code&gt;GET&lt;/code&gt; request to the following endpoint:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
https://www.googleapis.com/discovery/v1/apis?fields=items(title,discoveryLink)
&lt;/pre&gt;

&lt;p&gt;Which will return a JSON feed that looks like this:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
{
    "items": [
        …
        {
            "title": "Google+ API",
            "discoveryLink": "./apis/plus/v1/rest"
        },
        {
            "title": "Tasks API",
            "discoveryLink": "./apis/tasks/v1/rest"
        },
        {
            "title": "Calendar API",
            "discoveryLink": "./apis/calendar/v3/rest"
        },
        …
    ]
}
&lt;/pre&gt;
&lt;p&gt;Using the &lt;code&gt;discoveryLink&lt;/code&gt; attribute in the resources part of the feed above you can access the discovery document of each API. This is where a lot of useful information about the API can be accessed.&lt;/p&gt;

&lt;h3&gt;Get the OAuth 2.0 scopes of an API&lt;/h3&gt;
&lt;p/&gt;

&lt;p&gt;Using the API-specific endpoint you can easily get the OAuth 2.0 scopes available for that API. For example, here is how to get the scopes of the Google Tasks API:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
https://www.googleapis.com/discovery/v1/apis/tasks/v1/rest?fields=auth(oauth2(scopes))
&lt;/pre&gt;

&lt;p&gt;This method returns the JSON output shown below, which indicates that &lt;code&gt;https://www.googleapis.com/auth/tasks&lt;/code&gt; and &lt;code&gt;https://www.googleapis.com/auth/tasks.readonly&lt;/code&gt; are the two scopes associated with the Tasks API.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
{
    "auth": {
        "oauth2": {
            "scopes": {
                "https://www.googleapis.com/auth/tasks": {
                    "description": "Manage your tasks"
                },
                "https://www.googleapis.com/auth/tasks.readonly": {
                    "description": "View your tasks"
                }
            }
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;Using requests of this type you could detect which APIs do not support OAuth 2.0. For example, the Translate API does not support OAuth 2.0, as it does not provide access to OAuth protected resources such as user data.  Because of this, a &lt;code&gt;GET&lt;/code&gt; request to the following endpoint:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
https://www.googleapis.com/discovery/v1/apis/translate/v2/rest?fields=auth(oauth2(scopes))
&lt;/pre&gt;

&lt;p&gt;Returns:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
{}
&lt;/pre&gt;

&lt;h3&gt;Getting scopes required for an API’s endpoints and methods&lt;/h3&gt;
&lt;p/&gt;
&lt;p&gt;Using the API-specific endpoints again, you can get the lists of operations and API endpoints, along with the scopes required to perform those operations. Here is an example querying that information for the Google Tasks API:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
https://www.googleapis.com/discovery/v1/apis/tasks/v1/rest?fields=resources/*/methods(*(path,scopes,httpMethod))
&lt;/pre&gt;

&lt;p&gt;Which returns:&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
{
    "resources": {
        "tasklists": {
            "methods": {
                "get": {
                    "path": "users/@me/lists/{tasklist}",                         
                    "httpMethod": "GET",
                    "scopes": [
                        "https://www.googleapis.com/auth/tasks",
                        "https://www.googleapis.com/auth/tasks.readonly"
                    ]
                },
                "insert": {
                    "path": "users/@me/lists",
                    "httpMethod": "POST",
                    "scopes": [
                        "https://www.googleapis.com/auth/tasks"
                    ]
                },
                …
            }
        },
        "tasks": {
            …
        }
    }
}
&lt;/pre&gt;

&lt;p&gt;This tells you that to perform a &lt;code&gt;POST&lt;/code&gt; request to the &lt;code&gt;users/@me/lists&lt;/code&gt; endpoint (to insert a new task) you need to have been authorized with the scope &lt;code&gt;https://www.googleapis.com/auth/tasks&lt;/code&gt; and that to be able to do a &lt;code&gt;GET&lt;/code&gt; request to the &lt;code&gt;users/@me/lists/{tasklist}&lt;/code&gt; endpoint you need to have been authorized with either of the two Google Tasks scopes.&lt;/p&gt;

&lt;p&gt;You could use this to do some automatic discovery of the scopes you need to authorize to perform all the operations that your applications does.&lt;/p&gt;

&lt;p&gt;You could also use this information to detect which operations and which endpoints you can access given a specific authorization token ( OAuth 2.0, OAuth 1.0 or Authsub token).  First, use either the Authsub Token Info service or the OAuth 2.0 Token Info Service to determine which scopes your token has access to (see below); and then deduct from the feed above which endpoints and operations requires access to these scopes.&lt;/p&gt;

&lt;pre&gt;                        
&lt;b&gt;[Access Token]&lt;/b&gt; -----(Token Info)----&gt; &lt;b&gt;[Scopes]&lt;/b&gt; -----(APIs Discovery)----&gt; &lt;b&gt;[Operations/API Endpoints]&lt;/b&gt;
&lt;/pre&gt;

&lt;p&gt;Example of using the OAuth 2.0 Token Info service:&lt;p&gt;

&lt;p&gt;Request:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
GET /oauth2/v1/tokeninfo?access_token=&lt;OAuth 2.0 Access Token&gt; HTTP/1.1
Host: www.googleapis.com
&lt;/pre&gt;

&lt;p&gt;Response:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
HTTP/1.1 200 OK
Content-Type: application/json; charset=UTF-8
…

{
    "issued_to": "1234567890.apps.googleusercontent.com",
    "audience": "1234567890.apps.googleusercontent.com",
    "scope": "https://www.google.com/m8/feeds/ 
              https://www.google.com/calendar/feeds/",
    "expires_in": 1038
}
&lt;/pre&gt;

&lt;p&gt;There is a lot more you can do with the APIs Discovery Service so I invite you to have a deeper look at the documentation to find out more.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://sites.google.com/site/developeradvocates/image/nicolas_garnier.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Nicolas Garnier&lt;/span&gt;   &lt;a class="alt" href="http://www.google.com/profiles/Nivco.las" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/nivco" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://code.google.com/events/#&amp;amp;speaker=nivco"&gt;events&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Nicolas joined Google’s Developer Relations in 2008. Since then he's worked on commerce oriented products such as Google Checkout and Google Base.  Currently, he is working on Google Apps with a focus on the Google Calendar API, the Google Contacts API, and the Tasks API. Before joining Google, Nicolas worked at Airbus and at the French Space Agency where he built web applications for scientific researchers.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-4033892497175955537?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/Wt2eB4o4Qms" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/4033892497175955537/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=4033892497175955537&amp;isPopup=true" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/4033892497175955537?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/4033892497175955537?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/Wt2eB4o4Qms/tips-on-using-apis-discovery-service.html" title="Tips on using the APIs Discovery Service" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/tips-on-using-apis-discovery-service.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CkcFQnk8fCp7ImA9WhRVGUs.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-8042533873388026453</id><published>2012-01-19T00:00:00.000-08:00</published><updated>2012-01-19T00:00:13.774-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-19T00:00:13.774-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>Google Apps EMEA Developer Tour (continued)</title><content type="html">&lt;p&gt;Two months ago &lt;a href="http://googlecode.blogspot.com/2011/11/google-apps-emea-developer-tour.html"&gt;we announced&lt;/a&gt; that a few of us from the Google Apps Developer Relations team would be going around EMEA to meet with developers and talk about Google Apps technologies. We have met great developers from Germany, France, Russia, Czech Republic, Egypt, Switzerland, Israel, and Spain during Google Developer Days, hackathons, developer conferences and GTUG meetings.&lt;/p&gt;

&lt;p&gt;This year we are continuing the tour with a series of Google Apps Script hackathons taking place in &lt;a href="http://apps-dev-tour.appspot.com/agenda.html#Vienna"&gt;Vienna&lt;/a&gt;, &lt;a href="http://apps-dev-tour.appspot.com/agenda.html#Milan"&gt;Milan&lt;/a&gt;, &lt;a href="http://apps-dev-tour.appspot.com/agenda.html#Madrid"&gt;Madrid&lt;/a&gt;, &lt;a href="http://apps-dev-tour.appspot.com/agenda.html#Munich"&gt;Munich&lt;/a&gt; and &lt;a href="http://apps-dev-tour.appspot.com/agenda.html#Dublin"&gt;Dublin&lt;/a&gt; over the next few months. These hackathons provide a fun and hands-on way to learn about Google Apps Script and a good opportunity to give us your feedback on this technology.&lt;/p&gt;

&lt;p&gt;For more information about the tour and to register for these events, please visit the &lt;a href="http://apps-dev-tour.appspot.com/"&gt;Google Apps EMEA Developer Tour&lt;/a&gt; website.&lt;/p&gt;

&lt;iframe width="530" height="350" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" style="margin-top: 20px;border-radius:5px;" src="http://maps.google.com/maps/ms?z=4&amp;amp;t=t&amp;amp;vpsrc=5&amp;amp;msa=0&amp;amp;msid=212740107356106824507.0004b09b4f5a62b1c5bfb&amp;amp;ie=UTF8&amp;amp;ll=48.516604,8.369141&amp;amp;spn=17.888665,46.538086&amp;amp;output=embed"&gt;&lt;/iframe&gt;

&lt;p&gt;We plan to organize many other Google Apps events close to you in the near future. Look for updates on the &lt;a href="http://apps-dev-tour.appspot.com/"&gt;Google Apps EMEA Developer Tour&lt;/a&gt; website or keep an eye out for further announcements on this blog.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://sites.google.com/site/developeradvocates/image/nicolas_garnier.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Nicolas Garnier&lt;/span&gt;   &lt;a class="alt" href="http://www.google.com/profiles/Nivco.las" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/nivco" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://code.google.com/events/#&amp;amp;speaker=nivco"&gt;events&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Nicolas joined Google’s Developer Relations in 2008. Since then he's worked on commerce oriented products such as Google Checkout and Google Base.  Currently, he is working on Google Apps with a focus on the Google Calendar API, the Google Contacts API, and the Tasks API. Before joining Google, Nicolas worked at Airbus and at the French Space Agency where he built web applications for scientific researchers.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-8042533873388026453?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/_8okS-KNb7c" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/8042533873388026453/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=8042533873388026453&amp;isPopup=true" title="1 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8042533873388026453?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/8042533873388026453?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/_8okS-KNb7c/google-apps-emea-developer-tour.html" title="Google Apps EMEA Developer Tour (continued)" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>1</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/google-apps-emea-developer-tour.html</feedburner:origLink></entry><entry gd:etag="W/&quot;C0cFQ3Y7fyp7ImA9WhRVGU4.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-645475526053634190</id><published>2012-01-18T13:55:00.000-08:00</published><updated>2012-01-18T15:56:52.807-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-18T15:56:52.807-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Google Calendar API" /><title>Calendar API v3 Best Practices: Reminders</title><content type="html">&lt;p&gt;We recently posted some best practices for working with recurring events in Google Calendar API v3. In this blog post we’ll highlight another improved area in the v3 API: event reminders.&lt;/p&gt;

&lt;h2&gt;Reminders&lt;/h2&gt;

&lt;p&gt;Google Calendar API v3 offers developers flexible control over event reminders, including per-calendar default settings and custom overrides for individual events.&lt;/p&gt;

&lt;p&gt;The user’s default reminders for events on a given calendar can be found in the corresponding entry in the Calendar List collection. The Calendar List collection acts a bit like a list of bookmarks, containing entries for the calendars that the user owns or has looked at in the past (it corresponds to the content of the "My Calendars" and "Other Calendars" list on the bottom left in the Web version of Google Calendar). Each entry is annotated with user-specific settings for the individual calendar, such as the preferred color in the UI and the default reminders.&lt;/p&gt;

&lt;p&gt;Google Calendar currently supports three ways of reminding its users of events: "popup", prompting a message directly in the browser, mobile phone or desktop client, as well as "email" and "sms" for messages sent through the respective channels. To change the defaults, update the Calendar List entry and include the reminder method and how many minutes in advance the user should be alerted. In the following example, we set an email reminder to be sent 60 minutes before an event, and a popup reminder 10 minutes before.&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;{
 "summary": "Work Calendar",
 ...
 "defaultReminders": [
   {
     "method": "email",
     "minutes": 60
   },
   {
     "method": "popup",
     "minutes": 10
   }
 ]
}&lt;/pre&gt;

&lt;center&gt;&lt;i&gt;A Calendar List entry with title and default reminders.&lt;/i&gt;&lt;/center&gt;

&lt;p&gt;The default reminders will be applied to all existing and future events on this calendar, provided they don’t have custom reminders set already. In contrast to earlier versions of the API, newly created events will also have reminders set by default.&lt;/p&gt;

&lt;p&gt;Sometimes, there are events that we want a special reminder for, or none at all. To override the defaults for a specific event, switch the &lt;code&gt;useDefault&lt;/code&gt; flag in the &lt;code&gt;reminders&lt;/code&gt; section to &lt;code&gt;false&lt;/code&gt;, and include a set of custom reminders, or leave the list empty. When you define a set of override reminders for a recurring series, they are automatically applied to each of its occurrences, unless they have been overridden explicitly. Like the default reminders on the calendar, these are personal reminders for the user that is logged in, and will not influence the settings others might have for the same calendar or event.  Here is an example that overrides the default reminders with a 15 minute SMS reminder for that specific event.&lt;/p&gt;

&lt;pre class="prettyprint lang-js"&gt;{
 "summary": "API Office Hours",
 ...
 "reminders": {
   "useDefault": false,
   "overrides": [
     {
       "method": "sms",
       "minutes": 15
     }
   ]
 }
}&lt;/pre&gt;

&lt;center&gt;&lt;i&gt;An event representation with title and reminder overrides.&lt;/i&gt;&lt;/center&gt;

&lt;p&gt;The defaults for the given calendar are included at the top of any event listing result. This way, reminder settings for all events in the result can be determined by the client without having to make the additional API call to the corresponding entry in the Calendar List collection.&lt;/p&gt;

&lt;p&gt;In this post and an &lt;link&gt;earlier post about best practices with recurring events&lt;/link&gt;, we have covered some improved areas of the latest version of the Google Calendar API. Have a look at the &lt;a href="http://code.google.com/apis/calendar/v3/migration_guide.html"&gt;migration guide&lt;/a&gt; for a more complete view of other changes we made in the new version, and let us know what you think.&lt;/p&gt;

&lt;p&gt;If you have any questions about handling reminders or other features of the new Calendar API, post them on the &lt;a href="http://code.google.com/apis/calendar/community/forum.html"&gt;Calendar API forum&lt;/a&gt;.&lt;/p&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://1.bp.blogspot.com/-_FQ5XomCjpw/TuI5FxjR9-I/AAAAAAAAAV4/UKg5HiFmTcs/s400/image01.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Peter Lundblad&lt;/span&gt; &amp;nbsp;  &lt;a class="alt" href="http://plus.google.com/104780772893555468975" rel="me" target="_blank"&gt;profile&lt;/a&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Peter joined Google in 2006.  He's been leading the Calendar API team for the last 2 years. He's previously worked on video uploads for YouTube.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;

&lt;br /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://2.bp.blogspot.com/-_Khy5lbHDWA/TuI5d7WabOI/AAAAAAAAAWE/i5QKiVnk2YU/s400/image00.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Fabian Schlup&lt;/span&gt; &amp;nbsp;  &lt;a class="alt" href="http://www.google.com/profiles/fschlup" rel="me" target="_blank"&gt;profile&lt;/a&gt; 
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Fabian is a Software Engineer at Google in Zürich, working on Calendar and Tasks, with a focus on APIs.&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-645475526053634190?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/ImYB_9ragz8" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/645475526053634190/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=645475526053634190&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/645475526053634190?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/645475526053634190?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/ImYB_9ragz8/calendar-api-v3-best-practices.html" title="Calendar API v3 Best Practices: Reminders" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://1.bp.blogspot.com/-_FQ5XomCjpw/TuI5FxjR9-I/AAAAAAAAAV4/UKg5HiFmTcs/s72-c/image01.jpg" height="72" width="72" /><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/calendar-api-v3-best-practices.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CEcDQn44fCp7ImA9WhRWF0w.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-3690778472751207005</id><published>2012-01-04T11:05:00.000-08:00</published><updated>2012-01-04T13:21:13.034-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-04T13:21:13.034-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><category scheme="http://www.blogger.com/atom/ns#" term="Guest Post" /><title>Fun with Apps Script at the Google Apps Developer Hackathon in NYC</title><content type="html">&lt;p&gt;&lt;b&gt;Editor’s Note:&lt;/b&gt;&lt;i&gt;This is a guest post from John Watkinson.  John is a developer and the co-founder of &lt;a href="http://docracy.com"&gt;Docracy&lt;/a&gt;, a start-up company that hosts crowd-sourced legal documents and provides free e-signing services. He recently attended, and won first place, at the Google Apps Hackathon in NYC.  Here's John's story about the event.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Our company is focused on documents in the cloud, so we attended the Google Apps Developer Hackathon on December 1st in New York City to learn more about the various Google Apps APIs, and how we can best integrate with them. During the initial presentations from Ryan Boyd and Saurabh Gupta of the Google Apps team, we were delighted to learn about &lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Apps Script&lt;/a&gt;. It provides a powerful JavaScript interface into Google Apps, including &lt;a href="http://code.google.com/googleapps/appsscript/service_document.html"&gt;Docs&lt;/a&gt;, &lt;a href="http://code.google.com/googleapps/appsscript/service_calendar.html"&gt;Calendar&lt;/a&gt;, &lt;a href="http://code.google.com/googleapps/appsscript/service_gmail.html"&gt;Gmail&lt;/a&gt;, &lt;a href="http://code.google.com/googleapps/appsscript/service_maps.html"&gt;Maps&lt;/a&gt; and many others.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Hack #1: “The Wolf”&lt;/h3&gt;
&lt;p&gt;After the tech presentations, we formed teams and started hacking with the technology. I joined a team with Matt Hall (also from Docracy), Nick Siderakis, Jeff Hsin and Scott Thompson. We struck upon the idea of creating a function that would make calls to &lt;a href="http://www.wolframalpha.com/"&gt;Wolfram Alpha&lt;/a&gt; via their &lt;a href="http://products.wolframalpha.com/api/"&gt;developer API&lt;/a&gt;. It took all five of us hacking away for a few hours to parse the Alpha results properly, but eventually we had an Apps Script custom function for Spreadsheet called “wolf”. It had a kind of magic feeling about it, as you could pass simple English-language queries into the function and get back exact numeric results in your Google Spreadsheet. Some example working queries:&lt;/p&gt;
&lt;ul style="list-style: none;"&gt;
  &lt;li&gt;&lt;code&gt;=wolf("distance from the earth to the moon in km")&lt;/code&gt; &lt;em&gt;Answer: 403,778km&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;=wolf("population of canada in 1960")&lt;/code&gt; &lt;em&gt;Answer: 17.9 million&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;=wolf("average january temperature in buenos aires")&lt;/code&gt; &lt;em&gt;Answer: 64F&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;=wolf("9th digit of pi")&lt;/code&gt; &lt;em&gt;Answer: 5&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;=wolf("count of olympic medals won by japan in 2008")&lt;/code&gt; &lt;em&gt;Answer: 25&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Any function in Apps Script can be used as a spreadsheet function directly, so our main &lt;code&gt;wolf&lt;/code&gt; function is the entry point to our script. We use Apps Script’s &lt;code&gt;UrlFetch&lt;/code&gt; service to make our API call to Wolfram Alpha (note, the full URL is truncated for brevity):&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
var response = UrlFetchApp.fetch("http://api.wolframalpha.com/..." +
  encodeURIComponent(input));
&lt;/pre&gt;
&lt;p&gt;The response from Alpha is an XML file that includes a lot of metadata we don’t need. So, we used a recursive function and the XML manipulation tools in Apps Script to track down the element of interest to us (below is a simplified version):&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
function findText(e) {
  if (e.getName().getLocalName() == 'plaintext') {
    return e.getText();
  } else {
    var children = e.getElements();
    for (var i = 0; i &lt; children.length; i++) {
      var result = findText(children[i]);
      if (result) return result;
    }
    return null;
  }
}
&lt;/pre&gt;
&lt;p&gt;A little bit more work is required to parse the result, as sometimes Alpha will report numbers in words (i.e. “40.7 million”), but otherwise that’s it!&lt;p&gt;
&lt;p&gt;For a small script, it definitely feels pretty powerful! Our little invention earned us a first place finish at the hackathon, and the prizes were little indoor RC-controlled helicopters. I managed to irreparably damage mine in a gruesome crash into my refrigerator already, but I hear that many of the others are still serviceable and flying missions daily.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Hack #2: Sheet Sweeper&lt;/h3&gt;
&lt;p&gt;Once we were comfortable with the &lt;a href="http://code.google.com/googleapps/appsscript/defaultservices.html"&gt;Apps Script Services&lt;/a&gt; (and aware of its capabilities with spreadsheets in particular), a side project emerged that used some of the other integration features available. Apps Script functions can listen for events that are triggered by the editing of a spreadsheet cell (if the user grants the appropriate permissions). Using these onEdit &lt;a href="http://code.google.com/googleapps/appsscript/guide_events.html"&gt;Event Handlers&lt;/a&gt;, we wrote an implementation of the classic game Mine Sweeper that is playable right in the spreadsheet!&lt;/p&gt;
&lt;p/&gt;
&lt;div class="separator" style="text-align: center;"&gt;
&lt;a href="http://3.bp.blogspot.com/-Yo90FgfFcD4/TwSgzwiV3_I/AAAAAAAAAZQ/gtouyS7WOzw/s1600/image00.png" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" width="400" src="http://3.bp.blogspot.com/-Yo90FgfFcD4/TwSgzwiV3_I/AAAAAAAAAZQ/gtouyS7WOzw/s500/image00.png" /&gt;&lt;/a&gt;
&lt;div&gt;&lt;a href="http://www.youtube.com/watch?v=4Y4mfD-Jog0"&gt;Watch on YouTube&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can play the game by making a copy of &lt;a href="https://docs.google.com/a/google.com/spreadsheet/ccc?key=0Ap2tXBYlw7QmdEg3SVUwSXFXVDNKcGloY0JLcERlZ0E#gid=0"&gt;this spreadsheet&lt;/a&gt; and then clicking the menu item SheetSweeper &amp;gt; New Game. When you play the game, the script responds to onEdit events that are dispatched from the spreadsheet.&lt;/p&gt;
&lt;pre class="prettyprint"&gt;
function onOpen() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var menuEntries = [{name: "New Game", functionName: "startGame"}];
  ss.addMenu("SheetSweeper", menuEntries);
}
&lt;/pre&gt;

&lt;p&gt;Then when the user edits a cell, the script responds and implements the mine sweeper logic:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
function onEdit(event) {
  var ss = event.source.getActiveSheet();
  var cell = ss.getActiveCell();
  if (cell.getValue() == 'x') {
    // Flagging a mine
    flagCell(cell);
  } else {
    // Clearing a cell
    clearCell(cell);
  }  
}
&lt;/pre&gt;

&lt;p&gt;We had a great time at the hackathon, and are happy to have been introduced to Apps Script. We believe it has an exciting future ahead of it.&lt;p&gt;


&lt;p /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img style="width:90px;" width="90px" class="profile" src="https://lh3.googleusercontent.com/-t3OszVpHgzw/AAAAAAAAAAI/AAAAAAAABD4/G89QRX-Wpqw/s200-c-k/photo.jpg" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;John Watkinson&lt;/span&gt; &amp;nbsp;   &lt;a class="alt" href="https://plus.google.com/u/0/103808128853828210342/" rel="me" target="_blank"&gt;profile&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;John is a developer and the co-founder of &lt;a href="http://docracy.com"&gt;Docracy&lt;/a&gt;, a start-up company that hosts crowd-sourced legal documents and provides free e-signing services. He previously co-founded the mobile development shop &lt;a href="http://larvalabs.com"&gt;Larva Labs&lt;/a&gt;, which developed the popular &lt;a href="http://androidify.com"&gt;Androidify&lt;/a&gt; app with the Google Creative Lab.
&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-3690778472751207005?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/faMc9HzGlQA" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/3690778472751207005/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=3690778472751207005&amp;isPopup=true" title="4 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/3690778472751207005?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/3690778472751207005?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/faMc9HzGlQA/fun-with-apps-script-at-google-apps.html" title="Fun with Apps Script at the Google Apps Developer Hackathon in NYC" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-Yo90FgfFcD4/TwSgzwiV3_I/AAAAAAAAAZQ/gtouyS7WOzw/s72-c/image00.png" height="72" width="72" /><thr:total>4</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/fun-with-apps-script-at-google-apps.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEANQ3c6fCp7ImA9WhRWFkw.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-1192278272237522131</id><published>2012-01-03T10:37:00.000-08:00</published><updated>2012-01-03T10:53:12.914-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2012-01-03T10:53:12.914-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Administrative APIs" /><category scheme="http://www.blogger.com/atom/ns#" term="Google Calendar API" /><title>Automatically setting a vacation responder from a calendar event</title><content type="html">&lt;p&gt;Are you ever at an airport with no internet access, and realize that you forgot to set your "Out of Office" (OOO) message?" I forget regularly, but always remember to block off my calendar. Why not have the calendar automatically update my OOO message? We can! With the Calendar API, we can retrieve calendar events and set a vacation responder for the event duration for any user in the domain using the Email Settings API.&lt;p&gt;

&lt;p&gt;The first step is to authorize the Calendar and Email Settings client to make any call to the APIs respectively. The new Calendar API requires &lt;a href="http://code.google.com/p/google-api-python-client/source/checkout"&gt;google-api-client&lt;/a&gt; library while the Email settings API still works with the &lt;a href="http://code.google.com/p/gdata-python-client/source/checkout"&gt;gdata-client&lt;/a&gt; library in Python. These libraries use different underlying protocols and handle authorization differently.&lt;p&gt;

&lt;p&gt;Fortunately we can use the same code to get an OAuth 2.0 token for both the &lt;a href="http://code.google.com/apis/calendar/v3/using.html"&gt;Calendar API&lt;/a&gt; and the &lt;a href="http://code.google.com/googleapps/domain/email_settings/developers_guide_protocol.html"&gt;Email Settings API&lt;/a&gt;.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
from oauth2client.file import Storage
from oauth2client.client import AccessTokenRefreshError
from oauth2client.client import OAuth2WebServerFlow
from oauth2client.tools import run

SCOPES = ('https://www.googleapis.com/auth/calendar '
     'https://apps-apis.google.com/a/feeds/emailsettings/2.0/')

FLOW = flow_from_clientsecrets('client_secrets.json', scope=SCOPES)

storage = Storage('vacation.dat')
credentials = storage.get()
if credentials is None or credentials.invalid:
    credentials = run(FLOW, storage)
&lt;/pre&gt;

&lt;p&gt;Now that we have obtained the OAuth 2.0 token from OAuth2WebServerFlow, we can use it in either library. To authorize the Calendar service:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
# Create an httplib2.Http object to handle the HTTP 
# requests and authorize it with our good Credentials.

http = httplib2.Http()
http = credentials.authorize(http)

# Build authorized service for Calendar API
service = build('calendar', 'v3', http=http)
&lt;/pre&gt;

&lt;p&gt;Authorizing the Email Settings client requires adapting the credentials:&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
auth2token = gdata.gauth.OAuth2Token(client_id=client_id,
  client_secret=client_secret,
  scope=SCOPE,
  access_token=credentials.access_token,
  refresh_token=credentials.refresh_token,
  user_agent='vacation-responder-sample/1.0')

email_client = auth2token.authorize(
  gdata.apps.emailsettings.client.EmailSettingsClient(
    domain=domain))
&lt;/pre&gt;

&lt;p&gt;Now its time to update the vacation responder based on calendar event using the authorized client. Lets query for events containing the string ‘vacation’ from the Calendar API. The following code snippet shows the retrieval of events.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
# Convert the date to the RFC 3339 timestamp format
# for Calendar API.
cur_datetime = datetime.datetime.now()
cur_date = cur_datetime.strftime('%Y-%m-%dT%H:%M:%SZ')

events = service.events().list(calendarId=username,
      q='vacation', timeMin=cur_date, singleEvents=True,
      orderBy='startTime').execute()
&lt;/pre&gt;

&lt;p&gt;By specifying a user’s email address as the calendarID, we can query against any primary calendars in our domain that are shared and readable by the administrator.  If the user opts to hide their calendar, we won’t find any of their events.  For additional details about the query on calendar events, refer to  the ca&lt;a href="http://code.google.com/apis/calendar/v3/using.html#api_params"&gt;lendar query parameters&lt;/a&gt; from the reference guide.&lt;/p&gt;

&lt;p&gt;Next we can obtain the start and end time of the event and &lt;a href="http://code.google.com/googleapps/domain/email_settings/developers_guide_protocol.html#GA_email_vacation_main"&gt;update the vacation responder&lt;/a&gt; using the Email Settings client if the event’s duration is day long or more.&lt;/p&gt;

&lt;pre class="prettyprint"&gt;
if 'items' in events:
  for event in events['items']:
    startDate = event['start']
    endDate = event['end']
    # If event is set for the day not just for a time slot.
    if 'date' in startDate:
      email_client.UpdateVacation(username=username,
            enable=True,
            subject='Out of office', 
            message='If urgent call me.',
            domain_only=True, 
            start_date=startDate['date'],
            end_date=endDate['date'])
      print '\nVacation responder set for the days between %s to %s'  % 
            (startDate['date'], endDate['date'])
      break
&lt;/pre&gt;

&lt;p&gt;You can &lt;a href="http://code.google.com/p/google-vacation-responder-demo/source/browse/vacation_responder.py"&gt;download the sample&lt;/a&gt; and build your own application on top of it. We hope that this sample makes it easier to get started, particularly for apps that need to combine APIs from the two API clients. Please feel free to reach us in the &lt;a href="http://code.google.com/googleapps/support/domain_info_and_management_forum.html"&gt;Google Domain Info and Management Forum&lt;/a&gt; with any questions you have or write us feedback on this post.&lt;/p&gt;

&lt;p /&gt;&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://sites.google.com/site/developeradvocates/image/shraddha_gupta.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Shraddha Gupta&lt;/span&gt; &amp;nbsp;   &lt;a class="alt" href="https://plus.google.com/114165026575240989729/about" rel="me" target="_blank"&gt;profile&lt;/a&gt;
&lt;br /&gt;&lt;div class="bio"&gt;
&lt;br /&gt;Shraddha is a Developer Programs Engineer for Google Apps. She has her MTech in Computer Science and Engineering from IIT Delhi, India.
&lt;/div&gt;
&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-1192278272237522131?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/EHuZ4IP3be4" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/1192278272237522131/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=1192278272237522131&amp;isPopup=true" title="0 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1192278272237522131?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1192278272237522131?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/EHuZ4IP3be4/automatically-setting-vacation.html" title="Automatically setting a vacation responder from a calendar event" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><thr:total>0</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2012/01/automatically-setting-vacation.html</feedburner:origLink></entry><entry gd:etag="W/&quot;CU8CQ3gzcSp7ImA9WhRWEUU.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-1600058305066268242</id><published>2011-12-28T13:43:00.000-08:00</published><updated>2011-12-29T10:37:42.689-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-29T10:37:42.689-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Marketplace" /><title>Improved Customer Engagement &amp; Discovery in the Google Apps Marketplace</title><content type="html">&lt;p&gt;We recently updated the &lt;a href="http://www.google.com/enterprise/marketplace"&gt;Google Apps Marketplace&lt;/a&gt; with several new features to help developers better engage with their customers and improve discoverability of apps in the marketplace.&lt;/p&gt;
&lt;p/&gt;
&lt;h3&gt;Reply to Comments &amp; Reviews&lt;/h3&gt;
&lt;p&gt;It’s no secret that &lt;a href="http://googleappsdeveloper.blogspot.com/2011/09/finding-success-on-google-apps.html"&gt;engaging your customers&lt;/a&gt; and responding to their feedback is critical to success.  It’s now possible to engage in conversations with customers based on comments &amp; reviews for your app in the marketplace.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;
&lt;a href="http://2.bp.blogspot.com/-n8inMM0-waI/TvuM8-6R1aI/AAAAAAAAAYs/_Fgx0tqwvDs/s1600/image00.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" width="500" src="http://2.bp.blogspot.com/-n8inMM0-waI/TvuM8-6R1aI/AAAAAAAAAYs/_Fgx0tqwvDs/s540/image00.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p/&gt;
&lt;h3&gt;Rich Application Snippets in Google Search&lt;/h3&gt;
&lt;p&gt;Google Search recently introduced &lt;a href="http://googlewebmastercentral.blogspot.com/2011/09/introducing-application-rich-snippets.html"&gt;rich snippets for applications&lt;/a&gt; several months ago with 
enhanced search results for applications from marketplaces like &lt;a href="http://market.android.com"&gt;Android Market&lt;/a&gt; and others.  Marketplace apps will soon be appearing as rich snippets with ratings and pricing details.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-NohT_qV2JIQ/TvuNBEXnDkI/AAAAAAAAAY4/OYFsHCcHG5k/s1600/image01.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" width="500" src="http://1.bp.blogspot.com/-NohT_qV2JIQ/TvuNBEXnDkI/AAAAAAAAAY4/OYFsHCcHG5k/s540/image01.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p/&gt;
&lt;h3&gt;New Category Home Pages&lt;/h3&gt;
&lt;p&gt;Lastly, we introduced new home pages for each category in the Marketplace to feature the top installed and newest apps for that category.&lt;/p&gt;

&lt;p style="text-align: center;"&gt;
&lt;a href="http://1.bp.blogspot.com/-mmezUHpkUeA/TvuNEpwJccI/AAAAAAAAAZE/ojgPcF7MPJk/s1600/image02.jpg" imageanchor="1" style="margin-left:1em; margin-right:1em"&gt;&lt;img border="0" width="500" src="http://1.bp.blogspot.com/-mmezUHpkUeA/TvuNEpwJccI/AAAAAAAAAZE/ojgPcF7MPJk/s540/image02.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;


&lt;p&gt;We hope that you find value in these changes and wish everyone a happy new year!&lt;/p&gt;

&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;br /&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://sites.google.com/site/developeradvocates/image/steve_bazyl.png" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Steven Bazyl&lt;/span&gt;  &amp;nbsp; &lt;a class="alt" href="https://plus.google.com/103354693083460731603/" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/stevenbazyl" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://code.google.com/events/#&amp;speaker=sbazyl"&gt;events&lt;/a&gt;&lt;br /&gt;&lt;div class="bio"&gt;&lt;br /&gt;Steve is a Developer Advocate for Google Apps and the Google Apps Marketplace.  He enjoys helping developers find ways to integrate their apps and bring added value to users.&lt;/div&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-1600058305066268242?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/jGks2IFXPJk" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/1600058305066268242/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=1600058305066268242&amp;isPopup=true" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1600058305066268242?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1600058305066268242?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/jGks2IFXPJk/improved-customer-engagement-discovery.html" title="Improved Customer Engagement &amp; Discovery in the Google Apps Marketplace" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://2.bp.blogspot.com/-n8inMM0-waI/TvuM8-6R1aI/AAAAAAAAAYs/_Fgx0tqwvDs/s72-c/image00.jpg" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2011/12/improved-customer-engagement-discovery.html</feedburner:origLink></entry><entry gd:etag="W/&quot;DEYGQno8eSp7ImA9WhRWEU0.&quot;"><id>tag:blogger.com,1999:blog-1377183911445147227.post-1606921859123168882</id><published>2011-12-28T11:00:00.000-08:00</published><updated>2011-12-28T13:02:03.471-08:00</updated><app:edited xmlns:app="http://www.w3.org/2007/app">2011-12-28T13:02:03.471-08:00</app:edited><category scheme="http://www.blogger.com/atom/ns#" term="Apps Script" /><title>2011 - A year of features, use cases and developers of Apps Script</title><content type="html">&lt;p&gt;2011 was the year of momentum for &lt;a href="http://code.google.com/googleapps/appsscript/"&gt;Google Apps Script&lt;/a&gt;. As 2012 dawns upon us, let us take a moment to reflect on the past year.&lt;/p&gt;

&lt;p&gt;We started 2011 with a bang! In January we released &lt;a href="http://googleappsscript.blogspot.com/2011/02/apps-scripts-new-debugger-improved.html"&gt;a cloud-based Debugger into&lt;/a&gt; Apps Script’s IDE that proved to be very useful for developers. The Script Editor was upgraded bringing about many features and bug fixes. In March we implemented a very powerful feature of embedding &lt;a href="http://googleappsscript.blogspot.com/2011/03/build-applications-in-sites-using-apps.html"&gt;Apps Script in Google Sites pages as Gadgets&lt;/a&gt;, making it easy to enhance Sites in amazing ways. We also improved Contacts Services, making it more stable with an improved API.&lt;/p&gt;

&lt;p&gt;At &lt;a href="http://www.google.com/events/io/2011/index-live.html"&gt;Google I/O&lt;/a&gt; in May we launched &lt;a href="http://googleappsdeveloper.blogspot.com/2011/06/gmail-and-document-services-now.html"&gt;Document Services, Gmail Services&lt;/a&gt; and the drag ‘n’ drop &lt;a href="http://googleappsdeveloper.blogspot.com/2011/06/building-ui-in-apps-script-just-got.html"&gt;GUI Builder&lt;/a&gt;. These were major steps forward in making sure that Apps Script provides a full set of APIs to allow developers to build rich workflow and automation solutions.&lt;/p&gt;

&lt;p&gt;We were very busy during the summer months preparing for a series of launch for later part of 2011. In September, we launched &lt;a href="http://googleappsdeveloper.blogspot.com/2011/09/visualize-your-data-charts-in-google.html"&gt;Charts Services&lt;/a&gt;.  It allows users to dynamically create Charts and embed them in emails, UiApp or export as images. We also released three Google API services for &lt;a href="http://googlecode.blogspot.com/2011/09/three-new-apis-for-google-apps-script.html"&gt;Prediction, UrlShortener and Tasks APIs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://googleappsdeveloper.blogspot.com/2011/10/concurrency-and-google-apps-script.html"&gt;Lock&lt;/a&gt; and &lt;a href="http://googleappsdeveloper.blogspot.com/2011/11/caching-in-cloud-with-google-script.html"&gt;Cache&lt;/a&gt; Services launched in October. These services are important for building performant and scalable applications. We also improved the Script Editor by adding Project Support and made other UI improvements. November brought about the launch of &lt;a href="http://googleappsdeveloper.blogspot.com/2011/11/creating-more-responsive-applications.html"&gt;Client Handlers and Validators&lt;/a&gt;. This is only the beginning of our commitment to allow developers to build more advanced UI using Apps Script.&lt;/p&gt; 

&lt;p&gt;In December we continued to improve the reliability and stability of Apps Script runtime. We capped the year by releasing &lt;a href="http://googleappsdeveloper.blogspot.com/2011/12/google-groups-and-google-apps-script.html"&gt;Groups&lt;/a&gt; and &lt;a href="http://googleappsdeveloper.blogspot.com/2011/12/domain-user-management-with-apps-script.html"&gt;Domain&lt;/a&gt; Services. And who can forget the very useful &lt;a href="http://googleappsdeveloper.blogspot.com/2011/12/introducing-adsense-management-services.html"&gt;AdSense Services&lt;/a&gt; for AdSense advertisers!&lt;/p&gt;

&lt;p&gt;Throughout the year we expanded our outreach channels. There were Apps Script sessions at Google I/O and Bootcamp, and several attendees got their last minute tickets through the &lt;a href="http://googleappsscript.blogspot.com/2011/03/last-call-for-google-io-google-apps.html"&gt;Apps Script I/O challenge&lt;/a&gt;. We were at &lt;a href="http://www.google.com/events/developerday/2011/"&gt;Google Developer Day&lt;/a&gt; and &lt;a href="http://code.google.com/events/devfests/2011/index.html"&gt;DevFest&lt;/a&gt; events, met with GTUGs, and hosted &lt;a href="http://apps-dev-tour.appspot.com/"&gt;hackathons&lt;/a&gt; throughout the world. Our blog also featured scripts like Revevol’s &lt;a href="http://googleenterprise.blogspot.com/2011/03/improving-revevols-productivity-with.html"&gt;Trainer Finder&lt;/a&gt;, Corey’s &lt;a href="http://googleappsdeveloper.blogspot.com/2011/07/gmail-snooze-with-apps-script.html"&gt;Gmail Snooze&lt;/a&gt;, Dave’s Flubaroo, Top Contributor’s &lt;a href="http://googleappsdeveloper.blogspot.com/2011/10/4-ways-to-do-mail-merge-using-google.html"&gt;Mail Merge&lt;/a&gt;, Saqib’s &lt;a href="http://googleappsdeveloper.blogspot.com/2011/09/building-idea-bank-using-google-apps.html"&gt;Idea Bank&lt;/a&gt;, and Drew’s &lt;a href="http://googleappsdeveloper.blogspot.com/2011/10/calorie-counting-with-google-apps.html"&gt;Calorie Counting&lt;/a&gt; that showed the power of Apps Script.&lt;/p&gt;

&lt;p&gt;Recently we started Office Hours in G+ hangout. These hangouts proved to be very popular, personal and effective means to share ideas with Apps Script community.  Join us some day!&lt;/p&gt;

&lt;p&gt;In our efforts to help educators, we worked with &lt;a href="http://gothamschools.org/2011/12/21/a-queens-school-finds-opportunity-in-googles-education-apps/"&gt;a New York city school&lt;/a&gt; to help them make most out of Google Apps. We also hosted many EDU focused webinars and workshops.  In great Google tradition, Apps Script team participated in &lt;a href="http://www.google.com/edu/cape/"&gt;CAPE&lt;/a&gt; and &lt;a href="http://googleblog.blogspot.com/2011/06/googleserve-2011-giving-back-around.html"&gt;Google Serve&lt;/a&gt;.&lt;/p&gt; 

&lt;p&gt;2012 is going to be an even more exciting and promising year. Tighten your seat belts because we intend to keep firing on all cylinders!&lt;/p&gt;
&lt;table cellpadding="5" cellspacing="0"&gt;&lt;tbody&gt;&lt;br /&gt;&lt;tr style="background-color: #f2f2f2;"&gt;&lt;td&gt;&lt;img class="profile" src="http://3.bp.blogspot.com/-AfmuFkbdBJQ/TjHzJVAWTXI/AAAAAAAAAMo/xVhok61rw7I/s400/Google%2BChromeScreenSnapz157.png"" /&gt;&lt;/td&gt;&lt;td valign="top"&gt;&lt;span class="largefont"&gt;Saurabh Gupta&lt;/span&gt;   &lt;a class="alt" href="https://profiles.google.com/sg1705" rel="me" target="_blank"&gt;profile&lt;/a&gt; | &lt;a class="alt" href="http://twitter.com/#gluemesh" rel="me" target="_blank"&gt;twitter&lt;/a&gt; | &lt;a class="alt" href="http://www.gluemesh.com/" rel="me" target="_blank"&gt;blog&lt;/a&gt;&lt;br /&gt;&lt;div class="bio"&gt;&lt;br /&gt;Saurabh is a Developer Programs Engineer at Google. He works closely with Google Apps Script developers to help them extend Google Apps. Over the last 10 years, he has worked in the financial services industry in different roles. His current mission is to bring automation and collaboration to Google Apps users.&lt;/div&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/1377183911445147227-1606921859123168882?l=googleappsdeveloper.blogspot.com' alt='' /&gt;&lt;/div&gt;&lt;img src="http://feeds.feedburner.com/~r/GoogleAppsDeveloperBlog/~4/wuueUNHaVY0" height="1" width="1"/&gt;</content><link rel="replies" type="application/atom+xml" href="http://googleappsdeveloper.blogspot.com/feeds/1606921859123168882/comments/default" title="Post Comments" /><link rel="replies" type="text/html" href="http://www.blogger.com/comment.g?blogID=1377183911445147227&amp;postID=1606921859123168882&amp;isPopup=true" title="3 Comments" /><link rel="edit" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1606921859123168882?v=2" /><link rel="self" type="application/atom+xml" href="http://www.blogger.com/feeds/1377183911445147227/posts/default/1606921859123168882?v=2" /><link rel="alternate" type="text/html" href="http://feedproxy.google.com/~r/GoogleAppsDeveloperBlog/~3/wuueUNHaVY0/2011-year-of-features-use-cases-and.html" title="2011 - A year of features, use cases and developers of Apps Script" /><author><name>Google Apps Developer Blog Editor</name><uri>http://www.blogger.com/profile/07808045840831274565</uri><email>noreply@blogger.com</email><gd:image rel="http://schemas.google.com/g/2005#thumbnail" width="16" height="16" src="http://img2.blogblog.com/img/b16-rounded.gif" /></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="http://3.bp.blogspot.com/-AfmuFkbdBJQ/TjHzJVAWTXI/AAAAAAAAAMo/xVhok61rw7I/s72-c/Google%2BChromeScreenSnapz157.png" height="72" width="72" /><thr:total>3</thr:total><feedburner:origLink>http://googleappsdeveloper.blogspot.com/2011/12/2011-year-of-features-use-cases-and.html</feedburner:origLink></entry></feed>

