Como criar um gadget de dados do Google

Eric Bidelman, equipe de APIs de dados do Google
outubro de 2008

Introdução

Público-alvo

Este artigo o ajudará a criar um gadget do Blogger. Você precisa conhecer as APIs de dados do Google e a biblioteca de cliente JavaScript. Você também precisa ser fluente em JavaScript e ter alguma experiência com a implementação de um gadget do GitHub usando o formato gadgets.* API.

Este exemplo também demonstra como usar bibliotecas externas com sucesso em seus gadgets. Usei o jQuery (principalmente para efeitos de IU) e o TinyMCE, um ótimo plug-in de editor de texto avançado do WYSIWYG.

Motivação

É necessário muito pouco JavaScript para criar um gadget que utilize JSON com uma das APIs de dados do Google. O maior problema de um gadget é o fato de os dados serem públicos e somente leitura. Para criar mais gadgets interessantes, você precisa acessar os dados privados de um usuário (algo que requer autenticação). Até agora, não existia uma ótima maneira de aproveitar as APIs da Conta do Google. O XPN requer redirecionamentos do navegador, e o ClientLogin expõe as credenciais de um usuário, do lado do cliente. Até mesmo invadir um gadget do type="url" foi inconveniente.

Digite o proxy OAuth.

Proxy OAuth

Se você não conhece o OAuth, ele é um padrão de autenticação que permite ao usuário compartilhar dados privados com outro site ou gadget. A especificação do OAuth exige que todas as solicitações de dados sejam assinadas digitalmente. Isso é ótimo para a segurança, mas no caso de um gadget JavaScript, não é seguro gerenciar chaves privadas e criar assinaturas digitais. Também há a complicação adicional de problemas entre domínios.

Felizmente, esses problemas são resolvidos com o uso de um recurso da plataforma de gadgets chamado de proxy OAuth. O proxy OAuth foi projetado para facilitar a vida dos desenvolvedores de gadgets. Ele oculta grande parte dos detalhes de autenticação do OAuth e faz o trabalho pesado para você. O proxy assina solicitações de dados em nome do seu gadget, portanto, não é necessário gerenciar chaves privadas ou se preocupar com solicitações de assinatura. Simplesmente funciona!

O proxy OAuth é baseado em um projeto de código aberto chamado Shindig, que é uma implementação da especificação do gadget.

Observação: o proxy OAuth só é compatível com gadgets que utilizam a API gadgets.* e são executados em contêineres do WebGL. Ela não é compatível com a API de gadgets legados.

Como começar

No restante deste tutorial, o foco será a criação de um gadget para acessar os dados do Blogger de um usuário. Vamos passar pela autenticação (usando o proxy OAuth), pela biblioteca de cliente JavaScript e, por fim, por uma entrada no Blogger.

Autenticação

Antes de mais nada, precisamos dizer para o gadget usar o OAuth. Para fazer isso, adicione o elemento <OAuth> na seção <ModulePrefs> do gadget:

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

Os três endpoints de URL no elemento <Service> correspondem aos endpoints do token OAuth do Google. Veja a seguir uma explicação dos parâmetros de consulta:

  • scope

    Este parâmetro é obrigatório no URL da solicitação. Seu gadget só poderá acessar os dados do(s) scope(s) usado(s) neste parâmetro. Neste exemplo, o gadget acessará o Blogger. Se o gadget quiser acessar mais de uma API de dados do Google, concatene as scope adicionais com um %20. Por exemplo, se você quiser acessar o Agenda e o Blogger, defina o escopo como http://www.blogger.com/feeds/%20http://www.google.com/calendar/feeds/.

  • oauth_callback

    Esse parâmetro é opcional no URL de autorização. A página de aprovação do OAuth vai redirecionar você para esse URL depois que o usuário aprovar o acesso aos dados. É possível deixar esse parâmetro fora de você, defini-lo como sua própria "página aprovada" ou usar http://oauth.gmodules.com/gadgets/oauthcallback. A segunda oferece a melhor experiência do usuário quando ele instala o gadget pela primeira vez. Essa página fornece um snippet de JavaScript que fecha automaticamente a janela pop-up.

Agora que temos nosso gadget usando o OAuth, o usuário precisa aprovar o acesso aos seus dados. Este é o fluxo de autenticação:

  1. O gadget é carregado pela primeira vez e tenta acessar os dados do Blogger do usuário.
  2. A solicitação falha porque o usuário não concedeu acesso ao gadget. Felizmente, o objeto retornado na resposta contém um URL (response.oauthApprovalUrl), em que vamos enviar o usuário para login. O gadget exibe a mensagem "Fazer login no Blogger" e define o valor de oauthApprovalUrl.
  3. Em seguida, o usuário clica em "Fazer login no Blogger", e a página de aprovação do OAuth é aberta em uma janela separada. O gadget aguarda a conclusão do processo de aprovação pelo usuário mostrando um link: "Eu aprovei o acesso".
  4. No pop-up, o usuário escolhe conceder/negar acesso ao nosso gadget. Depois de clicar em "Permitir acesso", o usuário será direcionado para http://oauth.gmodules.com/gadgets/oauthcallback, e a janela será fechada.
  5. O gadget reconhece a janela fechada e tenta acessar o Blogger novamente, solicitando novamente os dados do usuário. Para detectar o fechamento da janela, usei um gerenciador de pop-ups. Se você não usar esse código, o usuário poderá clicar manualmente em "Aprovei o acesso".
  6. O gadget agora exibe a sua IU normal. Essa visualização persistirá, a menos que o token de autenticação seja revogado em IssuedtmpTokens.

Dos passos acima, os gadgets têm a noção de três estados diferentes:

  1. Não autenticado. O usuário precisa iniciar o processo de aprovação.
  2. Aguardando o usuário aprovar o acesso aos dados.
  3. Autenticado. Os gadgets mostram seu estado funcional normal.

No meu gadget, usei contêineres <div> para separar cada estágio:

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

Cada <div> é exibido sozinho usando showOnly(). Consulte o exemplo de gadget completo para ver detalhes sobre essa função.

Como usar a biblioteca de cliente JavaScript

Para buscar conteúdo remoto no GitHub, faça uma chamada para o método gadgets.io.makeRequest usando a API gadgets.*. No entanto, como estamos criando um gadget de dados do Google, não é necessário tocar nas APIs gadgets.io.*. Em vez disso, aproveite a biblioteca de cliente JavaScript, que tem métodos especiais para fazer solicitações para cada serviço de dados do Google.

Observação: no momento da publicação deste artigo, a biblioteca JavaScript era compatível apenas com Blogger, Agenda, Contatos, Finanças e Google Base. Para usar uma das outras APIs, use gadgets.io.makeRequest sem a biblioteca.

Carregar a biblioteca

Para carregar a biblioteca JavaScript, inclua o carregador comum na seção <Content> e importe a biblioteca depois que o gadget for inicializado. Inserir um callback para gadgets.util.registerOnLoadHandler() ajuda a determinar quando o gadget está pronto:

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

A chamada para blogger.useOAuth('google') diz à biblioteca para usar o proxy OAuth em vez de CmdJS, seu método normal de autenticação. Por fim, o gadget tenta recuperar os dados do Blogger do usuário chamando fetchData(). Esse método é definido abaixo.

Buscando dados

Agora que tudo está configurado, como podemos GET ou POST os dados para o Blogger?

Um paradigma comum no WebGL é definir uma função chamada fetchData() em seu gadget. Esse método normalmente processa os diferentes estágios da autenticação e busca dados usando gadgets.io.makeRequest. Como estamos usando a biblioteca de cliente JavaScript, gadgets.io.makeRequest é substituído por uma chamada para 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);
}

Na segunda vez que essa função é chamada, response.feed contém dados.

Observação: a getBlogFeed() usa a mesma função para o gerenciador de callback e erro.

Postar uma entrada no Blogger

A última etapa é postar uma nova entrada em um blog. O código abaixo demonstra o que acontece quando o usuário clica no botão "Salvar".

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

Conclusão

Agora você tem os elementos básicos para começar a codificar um gadget, além das APIs de dados do Google.

Esperamos que, neste artigo, você tenha apreciado a facilidade com que o proxy OAuth facilita a autenticação do gadget. Combinar essa ferramenta com a biblioteca de cliente JavaScript de dados do Google facilita a criação de gadgets interessantes, interativos e sofisticados.

Se você tiver alguma dúvida ou comentário sobre este artigo, acesse o fórum de discussão das APIs das Contas do Google.

Recursos