Creating a Google Data Gadget

Eric Bidelman, Google Data APIs team
October 2008

Introduction

Audience

This article will walk you through creating a Blogger gadget. It assumes you are familiar with the Google Data APIs and the JavaScript client library . You should also be fluent in JavaScript and have some experience implementing an OpenSocial gadget using the gadgets.* API .

This example also demonstrates how to successfully use external libraries in your gadgets. I've used jQuery (mainly for its UI effects) and TinyMCE , a great WYSIWYG rich text editor plugin.

Motivation

It takes very little JavaScript to create a gadget that uses JSON with one of the Google Data APIs. The major annoyance of such a gadget is that the data is public and read-only. To build more interesting gadgets, you need access to a user's private data (something that requires authentication). Until now, there hasn't been a great way to take advantage of the Google Account APIs . AuthSub requires browser redirects and ClientLogin exposes a user's credentials, client-side. Even hacking up a type="url" gadget has been inconvenient.

Enter the OAuth Proxy.

OAuth Proxy

If you're not familiar with OAuth, its an authentication standard that allows a user to share their private data with another website or gadget. The OAuth specification requires that all data requests are digitally signed. This is great for security, but in the case of a JavaScript gadget, managing private keys and creating digital signatures is insecure. There's also the added complication of cross-domain issues.

Luckily, these problems are solved by taking advantage of a feature from the gadgets platform called the OAuth Proxy. The OAuth Proxy is designed to make life easier for gadget developers. It hides much of OAuth's authentication details and does the heavy lifting for you. The Proxy signs data requests on behalf of your gadget, so there's no need to manage private keys or worry about signing requests. It just works!

The OAuth Proxy is based on an open-source project called Shindig , which is an implementation of the gadget specification.

Note:The OAuth Proxy is only supported for gadgets utililzing the gadgets.* API and running in OpenSocial containers. It is not supported for the legacy gadgets API .

Getting Started

The rest of this tutorial will focus on creating a gadget to access a user's Blogger data. We'll go through authentication (using the OAuth Proxy), using the JavaScript client library, and finally, posting a entry to Blogger.

Authentication

First things first, we need to tell the gadget to use OAuth. To do that, add the <OAuth> element in the gadget's <ModulePrefs> section:

< ModulePrefs 
> ... 
< OAuth 
>  
< Service 
  
 name 
 = 
 "google" 
>  
< Access 
  
 url 
 = 
 "https://www.google.com/accounts/OAuthGetAccessToken" 
  
 method 
 = 
 "GET" 
  
 / 
>  
  
< Request 
  
 url 
 = 
 "https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.blogger.com/feeds/" 
  
 method 
 = 
 "GET" 
  
 / 
>  
  
< Authorization 
  
 url 
 = 
 "https://www.google.com/accounts/OAuthAuthorizeToken? 
 oauth_callback=http://oauth.gmodules.com/gadgets/oauthcallback" 
  
 / 
>  
  
< / 
 Service 
>
< / 
 OAuth 
> ... 
< / 
 ModulePrefs 
>

The three url endpoints in the <Service> element correspond to Google's OAuth token endpoints . Here's explanation of the query parameters:

  • scope

    This parameter is required in the Request URL. Your gadget will only be able to access data from the scope (s) used in this parameter. In this example, the gadget will access Blogger. If your gadget wanted to access more than one Google Data API, concatenate the additional scope (s) with a %20 . As an example, if you wanted to access both Calendar and Blogger, set the scope to http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/ .

  • oauth_callback

    This parameter is optional in the Authorization URL. The OAuth approval page will redirect to this URL after the user has approved access to their data. You can choose to leave off this parameter, set it to your own "approved page", or preferably, use http://oauth.gmodules.com/gadgets/oauthcallback . The later provides the best user experience when users first install your gadget. That page provides a snippet of JavaScript that automatically closes the popup window.

Now that we have our gadget using OAuth, the user needs to approve access to their data. Here's the authentication flow:

  1. The gadget loads for the first time and attempts to access the user's Blogger data.
  2. The request fails because the user hasn't granted access to the gadget. Fortunately, the object returned in the response contains a URL ( response.oauthApprovalUrl ) where we'll send the user to login. The gadget displays the "Sign in to Blogger" and sets its href to the value of oauthApprovalUrl .
  3. Next, the user clicks "Sign in to Blogger" and the OAuth approval page opens in a separate window. The gadget waits for the user to finish the approval process by displaying a link: "I've approved access".
  4. In the popup, the user will choose to grant/deny access to our gadget. Once they click "Grant access", they're taken to http://oauth.gmodules.com/gadgets/oauthcallback and the window closes.
  5. The gadget recognizes the window closed and attempts to accesss Blogger a second time by re-requesting the user's data. To detect the window closing, I've used a popup handler . If you don't use such code, the user can manually click the "I've approved access".
  6. The gadget now displays its normal UI. This view will persist unless the authentication token is revoked under IssuedAuthSubTokens .

So from the steps above, gadgets have the notion of three different states:

  1. Unauthenticated. The user needs to start the approval process.
  2. Waiting for the user to approve access to their data.
  3. Authenticated. The gadgets displays it's normal functional state.

In my gadget, I've used <div> containers to separate each stage:

< Content 
  
 type 
 = 
 "html" 
>
< ! 
 [ 
 CDATA 
 [ 
< !-- 
  
 Normal 
  
 state 
  
 of 
  
 the 
  
 gadget 
 . 
  
 The 
  
 user 
  
 is 
  
 authenticated 
  
 -- 
>  
< div 
  
 id 
 = 
 "main" 
  
 style 
 = 
 "display:none" 
>  
< form 
  
 id 
 = 
 "postForm" 
  
 name 
 = 
 "postForm" 
  
 onsubmit 
 = 
 "savePost(this); return false;" 
>  
< div 
  
 id 
 = 
 "messages" 
  
 style 
 = 
 "display: none" 
>< / 
 div 
>  
< div 
  
 class 
 = 
 "selectFeed" 
> Publish 
  
 to 
 : 
  
< select 
  
 id 
 = 
 "postFeedUri" 
  
 name 
 = 
 "postFeedUri" 
  
 disabled 
 = 
 "disabled" 
>< option>loading 
  
 blog 
  
 list 
 ...</ 
 option 
>< / 
 select 
>  
< / 
 div 
>  
< h4 
  
 style 
 = 
 "clear:both" 
> Title 
< / 
 h4 
>  
< input 
  
 type 
 = 
 "text" 
  
 id 
 = 
 "title" 
  
 name 
 = 
 "title" 
 / 
>  
< h4>Content 
< / 
 h4 
>  
< textarea 
  
 id 
 = 
 "content" 
  
 name 
 = 
 "content" 
  
 style 
 = 
 "width:100%;height:200px;" 
>< / 
 textarea 
>  
< h4 
  
 style 
 = 
 "float:left;" 
> Labels 
  
 ( 
 comma 
  
 separated 
 ) 
< / 
 h4><img 
  
 src 
 = 
 "blogger.png" 
  
 style 
 = 
 "float:right" 
 / 
>  
< input 
  
 type 
 = 
 "text" 
  
 id 
 = 
 "categories" 
  
 name 
 = 
 "categories" 
 / 
>  
< p><input 
  
 type 
 = 
 "submit" 
  
 id 
 = 
 "submitButton" 
  
 value 
 = 
 "Save" 
 / 
>  
  
< input 
  
 type 
 = 
 "checkbox" 
  
 id 
 = 
 "draft" 
  
 name 
 = 
 "draft" 
  
 checked 
 = 
 "checked" 
 / 
>  
< label 
  
 for 
 = 
 "draft" 
> Draft 
 ? 
< / 
 label 
>< / 
 p 
>  
< / 
 form 
>
< / 
 div 
>

< div 
  
 id 
 = 
 "approval" 
  
 style 
 = 
 "display: none" 
>  
< a 
  
 href 
 = 
 "#" 
  
 id 
 = 
 "personalize" 
> Sign 
  
 in 
  
 to 
  
 Blogger 
< / 
 a 
>
< / 
 div 
>

< div 
  
 id 
 = 
 "waiting" 
  
 style 
 = 
 "display: none" 
>  
< a 
  
 href 
 = 
 "#" 
  
 id 
 = 
 "approvalLink" 
> I 
 've approved access</a> 
< / 
 di 
< !-- 
  
 An 
  
 errors 
  
 section 
  
 is 
  
 not 
  
 necessary 
  
 but 
  
 great 
  
 to 
  
 have 
  
 -- 
>
< div 
  
 id 
 = 
 "errors" 
  
 style 
 = 
 "display: none" 
>< / 
 div 
>  
< !-- 
  
 Also 
  
 not 
  
 necessary 
 , 
  
 but 
  
 great 
  
 for 
  
 informing 
  
 users 
  
 -- 
>  
< div 
  
 id 
 = 
 "loading" 
>  
< h3>Loading 
 ...</ 
 h3 
>  
< p><img 
  
 src 
 = 
 "ajax-loader.gif" 
>< / 
 p 
>
< / 
 div 
> ]] 
>  
< / 
 Content 
 & 
 gt 

Each <div> is displayed by itself using showOnly() . See the full example gadget for details on that function.

Using the JavaScript client library

To fetch remote content in OpenSocial, you make a call to the gadgets.io.makeRequest method using the gadgets.* API. However, since we're building a Google Data gadget, there's no need to touch the gadgets.io.* APIs. Instead, leverage the JavaScript client library which has special methods for making requests to each Google Data service.

Note: At the time of writing this article, the JavaScript library only supports Blogger , Calendar , Contacts , Finance , and Google Base . To use one of the other APIs, use gadgets.io.makeRequest without the library.

Loading the library

To load the JavaScript library, include the common loader in the <Content> section and import the library once the gadget has been initialized. Feeding a callback to gadgets.util.registerOnLoadHandler() will help determine when the gadget is ready:

< Content 
  
 type 
 = 
 "html" 
>
< ! 
 [ 
 CDATA 
 [ 
  
 ... 
  
< script 
  
 src 
 = 
 "https://www.google.com/jsapi" 
>< / 
 script 
>  
< script 
  
 type 
 = 
 "text/javascript" 
>  
 var 
  
 blogger 
  
 = 
  
 null 
 ; 
  
 // 
  
 make 
  
 our 
  
 service 
  
 object 
  
 global 
  
 for 
  
 later 
  
  
 // 
  
 Load 
  
 the 
  
 JS 
  
 library 
  
 and 
  
 try 
  
 to 
  
 fetch 
  
 data 
  
 once 
  
 it 
 's ready 
  
 function 
  
 initGadget 
 () 
  
 { 
  
  
 google 
 . 
 load 
 ( 
 'gdata' 
 , 
  
 '1.x' 
 , 
  
 { 
 packages 
 : 
  
 [ 
 'blogger' 
 ]}); 
  
 // 
  
 Save 
  
 overhead 
 , 
  
 only 
  
 load 
  
 the 
  
 Blogger 
  
 service 
  
 google 
 . 
 setOnLoadCallback 
 ( 
 function 
  
 () 
  
 { 
  
 blogger 
  
 = 
  
 new 
  
 google 
 . 
 gdata 
 . 
 blogger 
 . 
 BloggerService 
 ( 
 'google-BloggerGadget-v1.0' 
 ); 
  
 blogger 
 . 
 useOAuth 
 ( 
 'google' 
 ); 
  
 fetchData 
 (); 
  
 }); 
  
 } 
  
 gadgets 
 . 
 util 
 . 
 registerOnLoadHandler 
 ( 
 initGadget 
 ); 
  
< / 
 script 
>  
 ... 
 ]] 
>  
< / 
 Content 
 & 
 gt 

The call to blogger.useOAuth('google') tells the library to use the OAuth Proxy (instead of AuthSubJS - its normal authentication method). Lastly, the gadget attempts to retrieve the user's Blogger data by calling fetchData() . That method is defined below.

Fetching data

Now that things are all setup, how do we actually GET or POST data to Blogger?

A common paradigm in OpenSocial is to define a function called fetchData() in your gadget. This method typically handles the different stages of authentication and fetches data using gadgets.io.makeRequest . Since we're using the JavaScript client library, gadgets.io.makeRequest gets replaced by a call to blogger.getBlogFeed() :

 function 
  
 fetchData 
 () 
  
 { 
  
 jQuery 
 ( 
 '#errors' 
 ) 
 . 
 hide 
 (); 
  
  
 var 
  
 callback 
  
 = 
  
 function 
 ( 
 response 
 ) 
  
 { 
  
 if 
  
 ( 
 response 
 . 
 oauthApprovalUrl 
 ) 
  
 { 
  
 // 
  
 You 
  
 can 
  
 set 
  
 the 
  
 sign 
  
 in 
  
 link 
  
 directly 
 : 
  
 // 
  
 jQuery 
 ( 
 '#personalize' 
 ) 
 . 
 get 
 ( 
 0 
 ) 
 . 
 href 
  
 = 
  
 response 
 . 
 oauthApprovalUrl 
  
  
 // 
  
 OR 
  
 use 
  
 the 
  
 popup 
 . 
 js 
  
 handler 
  
 var 
  
 popup 
  
 = 
  
 shindig 
 . 
 oauth 
 . 
 popup 
 ({ 
  
 destination 
 : 
  
 response 
 . 
 oauthApprovalUrl 
 , 
  
 windowOptions 
 : 
  
 'height=600,width=800' 
 , 
  
 onOpen 
 : 
  
 function 
 () 
  
 { 
  
 showOnly 
 ( 
 'waiting' 
 ); 
  
 }, 
  
 onClose 
 : 
  
 function 
 () 
  
 { 
  
 showOnly 
 ( 
 'loading' 
 ); 
  
 fetchData 
 (); 
  
 } 
  
 }); 
  
 jQuery 
 ( 
 '#personalize' 
 ) 
 . 
 get 
 ( 
 0 
 ) 
 . 
 onclick 
  
 = 
  
 popup 
 . 
 createOpenerOnClick 
 (); 
  
 jQuery 
 ( 
 '#approvalLink' 
 ) 
 . 
 get 
 ( 
 0 
 ) 
 . 
 onclick 
  
 = 
  
 popup 
 . 
 createApprovedOnClick 
 (); 
  
  
 showOnly 
 ( 
 'approval' 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 response 
 . 
 feed 
 ) 
  
 { 
  
 showResults 
 ( 
 response 
 ); 
  
 showOnly 
 ( 
 'main' 
 ); 
  
 } 
  
 else 
  
 { 
  
 jQuery 
 ( 
 '#errors' 
 ) 
 . 
 html 
 ( 
 'Something went wrong' 
 ) 
 . 
 fadeIn 
 (); 
  
 showOnly 
 ( 
 'errors' 
 ); 
  
 } 
  
 }; 
  
  
 blogger 
 . 
 getBlogFeed 
 ( 
 'http://www.blogger.com/feeds/default/blogs' 
 , 
  
 callback 
 , 
  
 callback 
 ); 
 } 

The second time this function is called, response.feed contains data.

Note: getBlogFeed() uses the same function for it's callback and error handler.

Post an entry to Blogger

The last step is to post a new entry to a Blog. The code below demostrates what happens when the user clicks "Save" button.

 function 
  
 savePost 
 ( 
 form 
 ) 
  
 { 
  
  
 jQuery 
 ( 
 '#messages' 
 ). 
 fadeOut 
 (); 
  
 jQuery 
 ( 
 '#submitButton' 
 ). 
 val 
 ( 
 'Publishing...' 
 ). 
 attr 
 ( 
 'disabled' 
 , 
  
 'disabled' 
 ); 
  
  
 // 
  
 trim 
  
 whitespace 
  
 from 
  
 the 
  
 input 
  
 tags 
  
 var 
  
 input 
  
 = 
  
 form 
 . 
 categories 
 . 
 value 
 ; 
  
 var 
  
 categories 
  
 = 
  
 jQuery 
 . 
 trim 
 ( 
 input 
 ) 
  
 != 
  
 '' 
  
 ? 
  
 input 
 . 
 split 
 ( 
 ',' 
 ) 
  
 : 
  
 [] 
 ; 
  
  
 jQuery 
 . 
 each 
 ( 
 categories 
 , 
  
 function 
 ( 
 i 
 , 
  
 value 
 ) 
  
 { 
  
 var 
  
 label 
  
 = 
  
 jQuery 
 . 
 trim 
 ( 
 value 
 ); 
  
 categories 
 [ 
 i 
 ] 
  
 = 
  
 { 
  
 scheme 
 : 
  
 'http://www.blogger.com/atom/ns#' 
 , 
  
 term 
 : 
  
 label 
  
 } 
 ; 
  
 } 
 ); 
  
 // 
  
 construct 
  
 the 
  
 blog 
  
 post 
  
 entry 
  
 var 
  
 newEntry 
  
 = 
  
 new 
  
 google 
 . 
 gdata 
 . 
 blogger 
 . 
 BlogPostEntry 
 ( 
 { 
  
 title 
 : 
  
 { 
  
 type 
 : 
  
 'text' 
 , 
  
  
 text 
 : 
  
 form 
 . 
 title 
 . 
 value 
  
 } 
 , 
  
 content 
 : 
  
 { 
  
 type 
 : 
  
 'text' 
 , 
  
  
 text 
 : 
  
 form 
 . 
 content 
 . 
 value 
  
 } 
 , 
  
 categories 
 : 
  
 categories 
  
 } 
 ); 
  
  
 // 
  
 publish 
  
 as 
  
 draft 
 ? 
  
 var 
  
 isDraft 
  
 = 
  
 form 
 . 
 draft 
 . 
 checked 
 ; 
  
 if 
  
 ( 
 isDraft 
 ) 
  
 { 
  
 newEntry 
 . 
 setControl 
 ( 
 { 
 draft 
 : 
  
 { 
 value 
 : 
  
 google 
 . 
 gdata 
 . 
 Draft 
 . 
 VALUE_YES 
 }} 
 ); 
  
 } 
  
  
 // 
  
 callback 
  
 for 
  
 insertEntry 
 () 
  
 var 
  
 handleInsert 
  
 = 
  
 function 
 ( 
 entryRoot 
 ) 
  
 { 
  
 var 
  
 entry 
  
 = 
  
 entryRoot 
 . 
 entry 
 ; 
  
 var 
  
 str 
  
 = 
  
 isDraft 
  
 ? 
  
 '(as draft)' 
  
 : 
  
 '<a href="' 
  
 + 
  
 entry 
 . 
 getHtmlLink 
 (). 
 getHref 
 () 
  
 + 
  
 '" target="_blankt">View it</a>' 
 ; 
  
 jQuery 
 ( 
 '#messages' 
 ). 
 html 
 ( 
 'Post published! ' 
  
 + 
  
 str 
 ). 
 fadeIn 
 (); 
  
 jQuery 
 ( 
 '#submitButton' 
 ). 
 val 
 ( 
 'Save' 
 ). 
 removeAttr 
 ( 
 'disabled' 
 ); 
  
 } 
 ; 
  
  
 // 
  
 error 
  
 handler 
  
 for 
  
 insertEntry 
 () 
  
 var 
  
 handleError 
  
 = 
  
 function 
 ( 
 e 
 ) 
  
 { 
  
 var 
  
 msg 
  
 = 
  
 e 
 . 
 cause 
  
 ? 
  
 e 
 . 
 cause 
 . 
 statusText 
  
 + 
  
 ': ' 
  
 : 
  
 '' 
 ; 
  
 msg 
  
 += 
  
 e 
 . 
 message 
 ; 
  
 alert 
 ( 
 'Error: ' 
  
 + 
  
 msg 
 ); 
  
 } 
 ; 
  
  
 blogger 
 . 
 insertEntry 
 ( 
 form 
 . 
 postFeedUri 
 . 
 value 
 , 
  
 newEntry 
 , 
  
 handleInsert 
 , 
  
 handleError 
 ); 
 } 

Conclusion

Now you have the building blocks to start coding a gadget on top of the Google Data APIs.

Hopefully this article has given you an appreciation for how simple the OAuth Proxy makes gadget authentication. Combining this power tool with the Google Data JavaScript client library makes it easy to build interesting, interactive, and sophisticated gadgets.

If you have any questions or comments on this article, please visit us in the Google Accounts APIs discussion forum .

Resources

Create a Mobile Website
View Site in Mobile | Classic
Share by: