创建 Google 数据小工具

Eric Bidelman,Google 数据 API 团队
2008 年 10 月

简介

观众

本文将向您介绍如何创建 Blogger 小工具。本文假定您熟悉 Google Data APIJavaScript 客户端库。您还应熟练掌握 JavaScript,并且善于使用小工具*实现 OpenSocial 小工具。API

此示例还演示了如何在小工具中成功使用外部库。我使用过 jQuery(主要是为了实现其界面效果)和 TinyMCE,这是一款所见即所得富文本编辑器插件。

设计初衷

只需使用一个 JavaScript 即可轻松创建使用 JSON 和其中一个 Google 数据 API 的小工具。此类小工具的主要烦恼是:数据为公共数据且为只读。要构建更有趣的小工具,您需要访问用户的私有数据(需要进行身份验证的内容)。到目前为止,还没有很棒的方式来利用 Google 帐号 API。AuthSub 要求浏览器重定向,而 您将会在客户端公开用户的凭据。即使入侵 type="url" 小工具也很麻烦。

输入 OAuth 代理。

OAuth 代理

如果您不熟悉 OAuth,它是一种身份验证标准,可让用户与其他网站或小工具共享其私人数据。OAuth 规范要求所有数据请求都经过数字签名。这种加密方式非常有利于确保安全性,但在 JavaScript 小工具中,管理私钥和创建数字签名是不安全的。 此外,还会增加跨网域问题。

幸运的是,利用小工具平台中的 OAuth 代理功能可以解决这些问题。OAuth 代理旨在让小工具开发者的生活更轻松。它会隐藏大部分 OAuth 身份验证详细信息,并代您完成这些繁杂的工作。代理会代表您的小工具对数据请求进行签名,因此您无需管理私钥,也无需担心对请求进行签名。大功告成!

OAuth 代理基于一个名为 Shindig 的开源项目,该项目是该小工具规范的实现。

注意:只有使用 gadgets.* API 并在 OpenSocial 容器中运行的小工具才支持 OAuth 代理。 旧版小工具 API 不支持此功能。

开始使用

本教程的其余部分将重点介绍如何创建用于访问用户的 Blogger 数据的小工具。我们将进行身份验证(使用 OAuth 代理)、使用 JavaScript 客户端库,以及向 Blogger 发布一个条目。

身份验证

首先,我们需要告知小工具使用 OAuth。为此,请在该小工具的 <ModulePrefs> 部分添加 <OAuth> 元素:

<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>

<Service> 元素中的三个网址端点对应于 Google 的 OAuth 令牌端点。以下是对查询参数的说明:

  • scope

    此参数是请求网址所必需的。您的小工具将只能访问此参数中使用的scope的数据。 在此示例中,这个小工具将访问 Blogger。如果您的小工具想要访问多个 Google Data API,请将附加的 scope%20 串联起来。例如,如果您要访问 Google 日历和 Blogger,请将范围设置为 http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/

  • oauth_callback

    此参数在授权网址中是可选的。用户批准访问其数据后,OAuth 审批页面会重定向到此网址。 您可以选择省略此参数,将其设置为您自己的“已批准页面”,或者最好使用 http://oauth.gmodules.com/gadgets/oauthcallback。后者可在用户首次安装您的小工具时提供最佳用户体验。该网页提供一段 JavaScript 代码,用于自动关闭弹出式窗口。

现在,我们已经有了使用 OAuth 的小工具,现在需要用户批准访问他们的数据。身份验证流程如下:

  1. 该小工具是首次加载并尝试访问用户的 Blogger 数据。
  2. 由于用户尚未授予对该小工具的访问权限,因此请求失败。幸运的是,响应中返回的对象包含一个网址 (response.oauthApprovalUrl),我们会将用户引导至该网址。该小工具会显示“登录 Blogger”,并将其 href 的值设为 oauthApprovalUrl
  3. 接下来,用户点击“登录 Blogger”,OAuth 审批页面将在一个单独的窗口中打开。该小工具通过显示“我已批准的访问权限”链接,等待用户完成审批流程。
  4. 在弹出式窗口中,用户将选择授予/拒绝访问我们的小工具。点击“授予访问权限”后,系统会将您转到 http://oauth.gmodules.com/gadgets/oauthcallback,且窗口会关闭。
  5. 该小工具识别出该窗口已关闭,并通过尝试重新请求用户的数据来尝试再次访问 Blogger。为了检测窗口关闭情况,我使用了弹出式窗口处理程序。如果您不使用此类代码,用户可以手动点击“我已批准访问权限”。
  6. 此时,小工具会显示其正常界面。除非撤消了 IssuedAuthSubTokens 下的身份验证令牌,否则此视图将一直存在。

因此,从以上步骤中,小工具具有三种不同的状态:

  1. 未通过身份验证。用户需要启动审批流程。
  2. 正在等待用户批准对其数据的访问权限。
  3. 已通过身份验证。小工具会显示其正常功能状态。

在我的小工具中,我使用了 <div> 容器来分隔每个阶段:

<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>

每个 <div> 均使用 showOnly() 单独显示。如需详细了解该函数,请参阅完整的示例小工具

使用 JavaScript 客户端库

如需在 OpenSocial 中提取远程内容,请使用 gadgets.* API 调用 gadgets.io.makeRequest 方法。不过,由于我们正在构建 Google 数据小工具,因此无需使用 gadgets.io.* API。而是改用 JavaScript 客户端库,该客户端库提供了向每个 Google 数据服务发出请求的特殊方法。

注意:在撰写本文时,JavaScript 库仅支持 Blogger日历通讯录财务Google Base。如需使用其他 API,请在不使用相应库的情况下使用 gadgets.io.makeRequest

加载库

要加载 JavaScript 库,请在 <Content> 部分添加该通用加载器,然后在该小工具初始化后导入该库。向 gadgets.util.registerOnLoadHandler() 提供回调将有助于确定小工具何时准备就绪:

<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>

调用 blogger.useOAuth('google') 会告知库使用 OAuth 代理(而不是 AuthSubJS - 其常规身份验证方法)。最后,该小工具会尝试调用 fetchData() 来检索用户的 Blogger 数据。该方法定义如下。

正在提取数据

现在,一切都已设置完毕,我们该如何向 Blogger 实际GETPOST 数据呢?

OpenSocial 中的一种常见范例是在小工具中定义一个名为 fetchData() 的函数。此方法通常处理身份验证的不同阶段,并使用 gadgets.io.makeRequest 提取数据。由于我们使用的是 JavaScript 客户端库,因此 gadgets.io.makeRequest 已替换为对 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);
}

第二次调用此函数时,response.feed 会包含数据。

注意getBlogFeed() 在其回调函数和错误处理程序中使用相同的函数。

将博文发布到 Blogger

最后一步是在博客中发布新条目。下面的代码演示了用户点击“保存”按钮时发生的情况。

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);
}

总结

现在,您已经有了构建 Google Analytics(分析)数据 API 所需的代码的基础组件。

希望本文对您理解 OAuth 代理使小工具身份验证如此简单有所了解。将这一强大的工具与 Google 数据 JavaScript 客户端库相结合,可以轻松构建有趣、交互式和复杂的小工具。

如果您对本文有任何疑问或意见,请访问 Google 帐号 API 论坛

资源