建立 Google 資料小工具

Google Data API 團隊 Eric Bidelman
2008 年 10 月

簡介

觀眾

本文將逐步說明如何建立 Blogger 小工具。本文假設您已熟悉 Google Data APIJavaScript 用戶端程式庫。您也應精通 JavaScript,並有使用 gadgets.* 實作 OpenSocial 小工具的經驗。API

這個範例也示範如何在小工具中順利使用外部程式庫。我使用 jQuery (主要是為了 UI 效果) 和 TinyMCE,這款所見即所得的富文字編輯器外掛程式非常實用。

動機

只要使用少量的 JavaScript,就能建立透過 Google Data API 搭配 JSON 的小工具。這類小工具的主要缺點是資料公開且為唯讀。如要建構更有趣的小工具,您需要存取使用者的私人資料 (這需要驗證)。到目前為止,還沒有充分運用 Google 帳戶 API 的好方法。AuthSub 需要瀏覽器重新導向,而 ClientLogin 會在用戶端公開使用者的憑證。就連破解type="url"小工具也很不方便。

輸入 OAuth Proxy。

OAuth Proxy

如果您不熟悉 OAuth,這是一種驗證標準,可讓使用者與其他網站或小工具共用私人資料。OAuth 規格規定所有資料要求都必須經過數位簽署。這對安全性來說是好事,但如果是 JavaScript 小工具,管理私密金鑰和建立數位簽章並不安全。此外,跨網域問題也讓情況更加複雜。

幸好,只要善用小工具平台提供的 OAuth Proxy 功能,就能解決這些問題。OAuth Proxy 的設計宗旨是讓小工具開發人員的工作更輕鬆。這項功能會隱藏大部分的 OAuth 驗證詳細資料,並為您處理繁重的工作。Proxy 會代表小工具簽署資料要求,因此您不需要管理私密金鑰,也不必擔心要求簽署問題。輕輕鬆鬆就能上手!

OAuth Proxy 是以名為 Shindig 的開放原始碼專案為基礎,該專案實作了 Gadget 規格。

注意: OAuth Proxy 僅支援使用 gadgets.* API 且在 OpenSocial 容器中執行的 Gadget。舊版小工具 API 不支援這項功能。

開始使用

本教學課程的其餘部分將著重於建立小工具,以存取使用者的 Blogger 資料。我們將逐步說明如何進行驗證 (使用 OAuth Proxy)、使用 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(s) 資料。在本範例中,小工具將存取 Blogger。如果小工具要存取多個 Google Data API,請將額外的 scope(s) 與 %20 串連。舉例來說,如要同時存取日曆和 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. 小工具現在會顯示一般 UI。除非在「IssuedAuthSubTokens」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&gt

每個 <div> 都會使用 showOnly() 單獨顯示。如要瞭解該函式,請參閱完整範例小工具

使用 JavaScript 用戶端程式庫

如要在 OpenSocial 中擷取遠端內容,請使用 gadgets.* API 呼叫 gadgets.io.makeRequest 方法。不過,由於我們要建構 Google Data 小工具,因此不需要使用 gadgets.io.* API。請改用 JavaScript 用戶端程式庫,其中有專用方法可向各項 Google Data 服務發出要求。

注意:撰寫本文時,JavaScript 程式庫僅支援 BloggerGoogle 日曆Google 聯絡人Google 財經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&gt

呼叫 blogger.useOAuth('google') 會告知程式庫使用 OAuth Proxy (而非 AuthSubJS,也就是程式庫的一般驗證方法)。最後,小工具會呼叫 fetchData(),嘗試擷取使用者的 Blogger 資料。該方法定義如下。

正在擷取資料

現在一切都已設定完成,我們該如何將 GETPOST 資料實際傳送至 Blogger?

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 Data API 編寫小工具。

希望本文能讓您瞭解 OAuth Proxy 如何簡化小工具驗證程序。結合這項強大工具與 Google Data JavaScript 用戶端程式庫,即可輕鬆建構有趣、互動式且精緻的小工具。

如對本文有任何疑問或意見,請前往 Google 帳戶 API 討論論壇

資源