Google Code offered in: English - Español - 日本語 - 한국어 - Português - Pусский - 中文(简体) - 中文(繁體)
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.
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.
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. At this time, iGoogle in the US (www.google.com/ig) is the only container that
supports this feature, though other sites are beginning to develop their own implementations.
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.
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:
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.http://oauth.gmodules.com/gadgets/oauthcallback
and the window closes.So from the steps above, gadgets have the notion of three different states:
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>
Each <div> is displayed by itself using showOnly(). See the full example gadget for details on that function.
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.
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="http://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>
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.
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.
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);
}
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.