Participe da Maratona Behind the Code! A competição de programação que mais te desafia! Inscreva-se aqui

Crie um aplicativo de lista de pendências conveniente para dispositivos móveis com PHP, jQuery Mobile e Google Tasks

Eu acompanhava minha lista de pendências com um aplicativo desktop do Windows. Eu o abria sempre que me lembrava de algo importante, digitava uma etiqueta sucinta, designava uma data de vencimento e salvava. Como o aplicativo era ativado sempre que eu efetuava login no sistema, eu sabia imediatamente o que precisava de atenção e quando.

Esse aplicativo do Windows funcionava bem, mas tinha duas limitações: eu não podia verificar minha lista de pendências nem incluir tarefas quando estava longe do computador.

Então, eu encontrei o Google Tasks. Com ele, eu podia verificar as tarefas e adicionar tarefas novas enquanto eu estava em movimento.

O Google Tasks é integrado ao Gmail (embora muita gente não saiba disso) e permite criar e gerenciar listas de pendências online. Já que é possível acessar a lista de tarefas em um navegador, mesmo em movimento, é fácil incluir, visualizar e fechar tarefas.

O que é necessário para desenvolver a lista de pendências

  • Um ambiente de desenvolvimento Apache/PHP
  • Uma conta do Google para fins de teste
  • Slim

    Essa microestrutura PHP dará estrutura ao seu código PHP.

  • Biblioteca de clientes de APIs do Google para PHP

    Essa biblioteca fornece uma implementação robusta do OAuth e objetos do wrapper que simplificam o acesso a todas as APIs do Google.

  • jQuery Mobile

Essa estrutura coloca a interface com o usuário do seu aplicativo para funcionar rapidamente, com problemas mínimos de compatibilidade da plataforma e do navegador.

O que você precisa saber

Como muitos outros produtos do Google, o Google Tasks expõe uma API do Tasks que permite que aplicativos de terceiros se conectem a ele e desenvolvam aplicativos customizados em torno desses dados. Essa API, que segue o modelo REST, pode ser acessada por meio de qualquer kit de ferramentas de desenvolvimento habilitado para REST e já tem bibliotecas de cliente para várias linguagens de programação comuns, inclusive o PHP, minha linguagem preferida.

Para entender o código de exemplo de PHP mostrado aqui, é necessário conhecer o básico das classes e objetos em PHP e se sentir à vontade ao trabalhar com o REST. Também é necessário estar familiarizado com HTML, CSS e jQuery.

Vamos começar!

Configure bibliotecas e componentes dependentes

Primeiro, configure o Slim.

Para quem não conhece o Slim: é uma microestrutura PHP para o desenvolvimento rápido de aplicativos da web e APIs. Não se engane por causa do nome: o Slim inclui um roteador de URL sofisticado e suporte para modelos de página, mensagens em flash, cookies criptografados e middleware. É extremamente fácil de entender e usar e vem com uma documentação excelente e uma comunidade de desenvolvedores cheios de entusiasmo.

Etapa 1. Crie a estrutura do diretório do aplicativo com o Slim e as bibliotecas do Google OAuth

Mude para o diretório-raiz de documentos do servidor da web (normalmente, /usr/local/apache/htdocs no Linux ou C:\Program Files\Apache\htdocs no Windows) e crie um novo subdiretório para o aplicativo. Dê a esse diretório o nome tasks.

shell> cd /usr/local/apache/htdocs
shell> mkdir tasks

Esse diretório será referenciado neste artigo como $APP_ROOT.

Supondo que você tenha transferido por download a estrutura Slim e a biblioteca do Google OAuth descrita na seção anterior, extraia essas bibliotecas para $APP_ROOT/vendor. Além disso, transfira os arquivos index.php e .htaccess do archive de download do Slim para o seu diretório $APP_ROOT e edite o arquivo index.php para refletir o caminho correto para o arquivo Slim.php.

Agora, a estrutura de diretório deve ficar assim:

Printscreen demonstrando como a estrutura do diretório deve parecer.

Etapa 2. Defina um host virtual

Para facilitar o acesso ao aplicativo, é uma boa ideia definir um novo host virtual e defini-lo como o diretório de trabalho. Para fazer isso, edite o arquivo de configuração do Apache (httpd.conf ou httpd-vhosts.conf) e inclua as linhas a seguir:

NameVirtualHost 127.0.0.1
<VirtualHost 127.0.0.1>
    DocumentRoot "/usr/local/apache/htdocs/tasks"
    ServerName tasks.melonfire.com
</VirtualHost>

Essas linhas definem um novo host virtual cuja raiz de documento corresponde ao $APP_ROOT. Na listagem acima, o nome desse host é tasks.melonfire.com. Lembre-se de que você terá que mudar isso para localhost ou outro domínio sob o seu controle.

Reinicie o servidor da web para ativar as novas configurações. observe que talvez seja necessário atualizar o servidor do Sistema de Nomes de Domínio local da sua rede para informá-lo sobre o novo host também.

Quando terminar, aponte o navegador para o novo host. Você deve ver a página de boas-vindas da estrutura Slim:

Printscreen da página inicial do Slim.

Etapa 3. Registre a sua plataforma no Google Apps Platform

Para poder usar a API do Google Tasks, é necessário registrar seu aplicativo da web no Google. Para fazer isso, efetue login no Google usando as credenciais de conta do Google e acesse o Google Cloud Console. Crie um novo projeto, dê um nome a ele e, em seguida, ative o acesso à API do Google Tasks. Seu projeto no Google Cloud Console deve ficar assim:

Printscreen do acesso de APIs do Google Cloud Console.

Em seguida, registre seu aplicativo da web para obter um ID de cliente e um segredo do OAuth 2.0. Anote esses valores — você precisará deles para o cliente de OAuth PHP do Google.

Printscreen do OAuth ID no Google Cloud Console.

Lembre-se também de configurar a URL de direcionamento do aplicativo neste ponto. Essa é a URL para a qual o Google irá redirecionar o navegador do cliente depois de concluir o processo de autenticação do OAuth. Neste exemplo, esta URL é configurada como http://tasks.melonfire.com/login:

Printscreen do Google Cloud Console no redirecionamento de URL.

Enquanto estiver conectado, você também deve acessar o Gmail, que inclui uma interface integrada do Google Tasks, e incluir algumas listas de tarefas de exemplo e tarefas a ele. Isso é útil para garantir que o aplicativo PHP esteja funcionando corretamente. A interface do Google Tasks no Gmail é assim:

Printscreen do Gmail demonstrando como o Google Tasks funciona.

Talvez você esteja pensando que há coisas demais para fazer, mas a boa notícia é que você só precisa fazer isso uma vez.

Entendendo a API do Tasks

A API do Google Tasks funciona aceitando solicitações de REST referentes a ações em recursos e respondendo com as informações solicitadas. Um recurso é simplesmente uma URL que referencia o objeto ou a entidade no qual as ações devem ser realizadas —, tais como /lists ou /users. Uma ação é um dos quatro “verbos” HTTP — como GET (recuperar), POST (criar), PUT (atualizar) e DELETE (remover).

A API do Google Tasks inclui dois recursos primários: tarefas e listas de tarefas. Os usuários podem ter diversas listas de tarefas, e cada uma pode ter várias tarefas. As tarefas sempre ficam dentro de uma lista de tarefas. A API do Google Tasks considera que a primeira lista de tarefas criada por um usuário é a lista de tarefas “padrão” dele.

A API do Google Tasks codifica respostas em um formato JSON. Veja a seguir um exemplo de resposta da API, que emite uma solicitação GET autenticada para https://www.googleapis.com/tasks/v1/lists/@default/tasks, o endpoint da API para recuperar a lista de tarefas padrão do usuário.

Amostra de resposta da API do Google Tasks
{
 "kind": "tasks#tasks",
 "etag": "\"zhaMOBt\"",
 "items": [
  {
   "kind": "tasks#task",
   "id": "MTc3Mz",
   "etag": "\"zhaMOBt\"",
   "title": "Milk",
   "updated": "2013-11-11T07:46:09.000Z",
   "selfLink": "https://www.googleapis.com/tasks/v1/lists/MTc3Mz/tasks/MTc3MzQ1",
   "position": "00000000000637427684",
   "status": "needsAction"
  },
  {
   "kind": "tasks#task",
   "id": "MTc3Mz",
   "etag": "\"zhaMOBt\"",
   "title": "Bread",
   "updated": "2013-11-11T07:46:11.000Z",
   "selfLink": "https://www.googleapis.com/tasks/v1/lists/MTc3Mz/tasks/MTc3MzQ6",
   "position": "00000000000717532232",
   "status": "needsAction"
  },
  {
       ...
  }
 ]
}

Como a Listagem 1 mostra, a API do Tasks gera uma resposta codificada em JSON que contém uma lista de tarefas. Cada entrada de tarefa contém alguns metadados úteis, como título da tarefa, prazo final, auto-URL e status. Agora fica muito fácil decodificar esse JSON e convertê-lo para uma representação HTML adequada para exibir em um navegador da web. Entretanto, na maior parte do tempo, você não emitirá solicitações brutas de GET e POST para a API do Tasks. O cliente de OAuth PHP do Google e seus objetos de serviço fornecem um wrapper conveniente em torno dessas solicitações, encapsulando toda a funcionalidade relacionada em objetos e métodos de PHP.

Listagem de tarefas

A Listagem 2 usa a biblioteca de OAuth do Google com a estrutura Slim para conectar, autenticar e exibir um resumo de listas de tarefas e tarefas.

Fluxo de autenticação OAuth e recuperação da lista de tarefas
<?php
session_start();
require_once 'vendor/Slim/Slim.php';
require_once 'vendor/google-api-php-client/src/Google_Client.php';
require_once 'vendor/google-api-php-client/src/contrib/Google_TasksService.php';

\Slim\Slim::registerAutoloader();
$app = new \Slim\Slim();
$app->config(array(
  'debug' => true,
  'templates.path' => './templates'
));
$client = new Google_Client();
$client->setApplicationName('Project X');
$client->setClientId('YOUR-CLIENT-ID');
$client->setClientSecret('YOUR-CLIENT-SECRET');
$client->setRedirectUri('http://tasks.melonfire.com/login');
$client->setScopes(array(
  'https://www.googleapis.com/auth/tasks'
));
$app->client = $client;
$app->tasksService = new Google_TasksService($app->client);

$app->get('/login', function () use ($app) {

    if (isset($_GET['code'])) {
      $app->client->authenticate();
      $_SESSION['access_token'] = $app->client->getAccessToken();
      $app->redirect('/index');
      exit;
    }

    // if token available in session, set token in client
    if (isset($_SESSION['access_token'])) {
      $app->client->setAccessToken($_SESSION['access_token']);
    }

    if ($app->client->getAccessToken()) {
      if (isset($_SESSION['target'])) {
        $app->redirect($_SESSION['target']);
      } else {
        $app->redirect('/index');
      }
    } else {
      $authUrl = $app->client->createAuthUrl();
      $app->redirect($authUrl);
    }

});

$app->get('/index', 'authenticate', function () use ($app) {
  $lists = $app->tasksService->tasklists->listTasklists();
  foreach ($lists['items'] as $list) {
    $id = $list['id'];
    $tasks[$id] = $app->tasksService->tasks->listTasks($id);
  }
  $app->render('index.php', array('lists' => $lists, 'tasks' => $tasks));
});

$app->get('/logout', function () use ($app) {
  unset($_SESSION['access_token']);
  $app->client->revokeToken();
});

$app->run();

function authenticate () {
  $app = \Slim\Slim::getInstance();
  $_SESSION['target'] = $app->request()->getPathInfo();
  if (isset($_SESSION['access_token'])) {
    $app->client->setAccessToken($_SESSION['access_token']);
  }
  if (!$app->client->getAccessToken()) {
    $app->redirect('/login');
  }
}

A Listagem 2, que deve ser salva como $APP_ROOT/index.php, começa carregando o Slim e as bibliotecas de cliente do Google OAuth, juntamente com o objeto de serviço Google Tasks. Ele inicializa um novo objeto de aplicativo Slim e um novo objeto Google_Client. Nem é preciso dizer que o objeto Google_Client deve ser configurado com o mesmo ID de cliente, segredo de cliente e URL de redirecionamento definidos anteriormente no Google Cloud Console. Um serviço de objeto Google_TasksService também é inicializado; atua como um ponto de controle primário para interagir com a API do Google Tasks por meio do PHP.

O Slim opera definindo retornos de chamada de roteador para métodos de HTTP e terminais. Isso é feito simplesmente chamando o método — get() correspondente para solicitações GET, post() para solicitações de POST e assim sucessivamente — e passando a rota da URL a ser correspondida como o primeiro argumento para o método. O segundo argumento do método é uma função que especifica as ações a serem realizadas quando se faz a correspondência entre a rota e uma solicitação recebida. A Listagem 2 configura três retornos de chamada de roteador desse tipo: /index, /login e /logout. Vamos examinar cada um deles separadamente:

  • O retorno de chamada /login manipula o fluxo de autenticação OAuth. A explicação completa desse fluxo não faz parte do escopo deste artigo, mas é possível obter detalhes completos na documentação da API do Google. Resumidamente, esse retorno de chamada usa o método createAuthUrl() do objeto Google_Client para gerar uma URL para a página de autenticação do Google (veja a figura a seguir) e, em seguida, redireciona o navegador do cliente para essa URL. Depois que usuário autentica o aplicativo e confirma os dados aos quais ele tem acesso, o Google redireciona o cliente novamente para a URL /login, que recupera um token de acesso e o armazena na sessão. Esse token de acesso dá ao cliente o acesso à API do Google Tasks.
  • A autenticação OAuth bem-sucedida redireciona o cliente para a página de índice do aplicativo, localizada em /index. Esse retorno de chamada usa o objeto Google_TasksService configurado e seu método listTasklists() para recuperar uma lista das listas de tarefas autenticadas do usuário. Em seguida, o código faz a iteração em sua coleção de listas de tarefas e, para cada lista, chama o método listTasks() do objeto de serviço para recuperar as tarefas individuais dentro da lista. Em seguida, essas informações são transferidas para a visualização, que faz a renderização da mesma para o usuário. Eu irei mostrar mais do script de visualização.
  • O método /logout destrói a sessão, anulando o token de acesso armazenado nele. Para dar mais segurança, ele também chama o método revokeToken() do objeto de cliente, o que também invalida o token nos servidores do Google.

Printscreen da autorização do Google API.

Você já viu que o retorno de chamada /index recupera as listas de tarefas do usuário e as tarefas de cada lista. Essas informações são armazenadas em variáveis de PHP e transferidas para a visualização, que é responsável pela por formatá-las em uma lista legível para pessoas. O script de visualização deve estar localizado em $APP_ROOT/templates/index.php e é assim:

Página de índice
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js"></script>
</head>
<body>
    <div data-role="page">
      <div data-role="header">
      Tasks
      </div>
      <div data-role="content">
      <div data-role="collapsible-set" data-inset="false">
        <?php foreach ($lists['items'] as $list): ?>
          <?php $id = $list['id']; ?>
          <div data-role="collapsible">
            <h2><?php echo $list['title']; ?></h2>
            <ul data-role="listview">
              <?php if (isset($tasks[$id]['items'])): ?>
                <?php foreach ($tasks[$id]['items'] as $task): ?>
                <li>
                  <h3><?php echo $task['title']; ?></h3>
                </li>
                <?php endforeach; ?>
              <?php endif; ?>
            </ul>
          </div>
        <?php endforeach; ?>
        </div>
      </div>
    </div>
</body>
</html>

A Listagem 3 configura uma página de visualização de lista formatada de acordo com as convenções padrão do jQuery Mobile. O elemento da página primário é um elemento <div> com um atributo data-role="page". Dentro dele, há elementos <div> separados para o cabeçalho, o rodapé e o conteúdo da página. O conteúdo da página é constituído por uma série de elementos <div>, e cada elemento desses representa uma das listas de tarefas do usuário. Ao clicar no título de uma lista, você exibe suas tarefas.

Para ver como isso funciona, acesse http://tasks.melonfire.com/index (substitua essa URL pela URL do seu host virtual) no navegador. Você deve ver uma listagem de tarefas assim:

Printscreen da listagem de tarefas.

Criando e excluindo listas de tarefas

Obviamente, exibir as tarefas é apenas a primeira etapa; é conveniente que os usuários também possam incluir novas tarefas e listas de tarefas. Portanto, vamos definir uma nova rota em $APP_ROOT/index.php:

<?php

// ... other routes

$app->get('/add-list', 'authenticate', function () use ($app) {
  $app->render('add-list.php');
});

Com isso, uma solicitação para /add-list fará com que o modelo $APP_ROOT/templates/add-list.php seja renderizado para o usuário. A próxima listagem mostra o conteúdo desse modelo. A função authenticate() é uma função customizada que é executada antes da execução do retorno de chamada da rota; ao consultar a Fluxo de autenticação OAuth e recuperação da lista de tarefas, você verá que ela procura um token de acesso e redireciona o cliente para a página de login caso o token não seja encontrado, solicitando um novo login.

Formulário de criação da lista de tarefas
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js"></script>
</head>
<body>
    <div data-role="page">
      <div data-role="header">
      Add List
      </div>
      <div data-role="content">
        <div data-role="collapsible-set">
          <form method="post" action="/add-list">
            <label for="title">Title:</label>
            <input name="title" id="title" data-clear-btn="true" type="text"/>
            <input name="submit" value="Save" type="submit"
              data-icon="check" data-inline="true" data-theme="a" />
            <a href="/index" data-role="button" data-inline="true"
              data-icon="back" data-theme="a">Back</a>
          </form>
      </div>
    </div>
</body>
</html>

A Listagem 4 consiste em um formulário com um único campo, para o título da nova lista de tarefas. No envio, os dados do formulário serão enviados por POST de volta para a rota /add-list, que agora deve ser estendida para manipular a entrada do formulário. Este é o código adicional:

Criação da lista de tarefas
<?php

// ... other routes

$app->post('/add-list', 'authenticate', function () use ($app) {
  if (isset($_POST['submit'])) {
    $title = trim(htmlentities($_POST['title']));
    if (empty($title)) {
      $title = 'Untitled List';
    }
    $tasklist = new Google_TaskList();
    $tasklist->setTitle($title);
    $result = $app->tasksService->tasklists->insert($tasklist);
    $app->redirect('/index');
  }
});

A Listagem 5 limpa o título enviado por meio do formulário e, em seguida, cria um novo objeto Google_TaskList. Esse objeto representa um recurso de lista de tarefas na API do Google Tasks. O método setTitle() do objeto é usado para atribuir um título, e o método insert() do objeto é usado para salvar a nova lista de tarefas no Google Tasks.

Veja aqui o formulário e os resultados de seu envio:

Printscreen do formulário para criação de tarefas.

Se você verificar agora a interface do Google Tasks dentro do Gmail, a lista de tarefas recém-adicionada também deve estar lá. Tente você mesmo e veja!

Se você permite que os usuários adicionem listas, também precisa oferecer a eles uma forma de excluí-las. O inverso do método insert() do objeto de serviço é o seu método delete(), que aceita um identificador de lista de tarefas e exclui a lista correspondente do Google Tasks. Esta é a definição da rota:

Exclusão da lista de tarefas
<?php

// ... other routes

$app->get('/delete-list/:lid', 'authenticate', function ($lid) use ($app) {
  $app->tasksService->tasklists->delete($lid);
  $app->redirect('/index');
});

A Listagem 6 configura uma nova rota, /delete-list, que aceita um identificador de lista como parte da URL de solicitação. Em seguida, essa URL de solicitação é analisada pela estrutura de roteamento do Slim, o identificador de lista é extraído e o método delete() do objeto de serviço é usado para remover a lista correspondente do Google Tasks.

Agora só falta atualizar a listagem da página de índice com botões adicionais e remover listas. Este é o código da página de índice revisada:

Página de índice
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js"></script>
</head>
<body>
    <div data-role="page">
      <div data-role="header">
      Tasks
      </div>
      <div data-role="content">
      <div data-role="collapsible-set" data-inset="false">
        <?php foreach ($lists['items'] as $list): ?>
          <?php $id = $list['id']; ?>
          <div data-role="collapsible">
            <h2><?php echo $list['title']; ?></h2>
            <ul data-role="listview">
              <?php if (isset($tasks[$id]['items'])): ?>
                <?php foreach ($tasks[$id]['items'] as $task): ?>
                <li>
                  <?php if ($task['status'] == 'needsAction'): ?>
                  <h3><?php echo $task['title']; ?></h3>
                  <?php else: ?>
                  <h3 style="text-decoration:line-through"
                    ><?php echo $task['title']; ?></h3>
                  <?php endif; ?>
                  <?php if (isset($task['due']) &&
                    ($task['status'] == 'needsAction')): ?>
                  <p>Due on <?php echo date('d M Y',
                    strtotime($task['due'])); ?></p>
                  <?php endif; ?>
                  <?php if (isset($task['completed'])
                    && ($task['status'] == 'completed')): ?>
                  <p>Completed on <?php echo
                    date('d M Y', strtotime($task['completed'])); ?></p>
                  <?php endif; ?>
                </li>
                <?php endforeach; ?>
              <?php endif; ?>
            </ul>
            <a href="/delete-list/<?php echo $id; ?>"
              data-inline="true" data-role="button" data-icon="delete"
              data-theme="a">Remove list</a>
          </div>
        <?php endforeach; ?>
        </div>
      </div>
      <a href="/add-list" data-inline="true" data-role="button"
        data-icon="plus" data-theme="b">Add new list</a>
    </div>
</body>
</html>

Além de novos botões para incluir e excluir listas, essa versão do script de visualização também tem alguns aprimoramentos adicionais. Agora os prazos finais das tarefas são exibidos, e as tarefas concluídas aparecem riscadas, ao passo que as tarefas cujo prazo não se esgotou (status="needsAction") não estão riscadas. Fica assim:

Printscreen da lista de tarefas com prazos e datas.

Criando e excluindo tarefas

Da mesma forma que é possível incluir e excluir listas de tarefas, também é possível incluir e excluir tarefas em uma lista. Veja as novas rotas /add-item e /delete-item para esse fim:

Inclusão e exclusão de tarefas
<?php

// ... other routes

$app->get('/add-task/:tid', 'authenticate', function ($tid) use ($app) {
  $app->render('add-task.php', array('id' => $tid));
});

$app->post('/add-task', 'authenticate', function () use ($app) {
  if (isset($_POST['submit'])) {
    $title = trim(htmlentities($_POST['title']));
    $due = trim(htmlentities($_POST['due']));
    $id = trim(htmlentities($_POST['id']));
    if (empty($title)) {
      $title = 'Untitled Task';
    }
    if (empty($due)) {
      $due = 'tomorrow';
    }
    $task = new Google_Task();
    $task->setTitle($title);
    $task->setDue(date(DATE_RFC3339, strtotime($due)));
    $result = $app->tasksService->tasks->insert($id, $task);
    $app->redirect('/index');
  }
});

$app->get('/delete-task/:lid/:tid', 'authenticate', function ($lid, $tid) use ($app) {
  $app->tasksService->tasks->delete($lid, $tid);
  $app->redirect('/index');
});

Todas as tarefas devem estar associadas a uma lista de tarefas; portanto, o retorno de chamada da rota /add-task é configurado para receber um identificador de lista de tarefas como um parâmetro de solicitação GET. Em seguida, ele renderiza o modelo $APP_ROOT/templates/add-task.php, que contém um formulário para incluir novas tarefas e é descrito nesta listagem:

Formulário de criação de tarefas
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
  <link rel="stylesheet" type="text/css" href="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.min.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.core.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.mode.calbox.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/i18n/jquery.mobile.datebox.i18n.en_US.utf8.js"></script>
</head>
<body>
    <div data-role="page">
      <div data-role="header">
      Add Task
      </div>
      <div data-role="content">
        <div data-role="collapsible-set">
          <form method="post" action="/add-task">
            <input name="id" type="hidden" value="<?php echo $id; ?>" />
            <label for="title">Title:</label>
            <input name="title" id="title" data-clear-btn="true" type="text"/>
            <label for="due">Due:</label>
            <input name="due" id="due" type="date" data-role="datebox"
              data-options='{"mode": "calbox", "useFocus": true,
              "themeDateToday": "e"}' />
            <input name="submit" value="Save" type="submit"
              data-icon="check" data-inline="true" data-theme="a" />
            <a href="/index" data-role="button" data-inline="true"
              data-icon="back" data-theme="a">Back</a>
          </form>
      </div>
    </div>
</body>
</html>

A Listagem 9 contém um formulário com dois campos visíveis — um para o título da tarefa e outra para o prazo final. Para simplificar a entrada de data, o campo de entrada de data é configurado para usar o plugin jQuery Mobile DateBox, que exibe um selecionador de data gráfico para a entrada de dados do tipo apontar e clicar. Já que as tarefas devem ser associadas a uma lista de tarefas, o identificador de lista de tarefas recebido como um parâmetro de GET também é especificado como no formulário como um campo oculto.

Depois que o formulário é enviado, os dados inseridos nele são limpos e usados para inicializar o objeto Google_Task. Em seguida, esse objeto, juntamente com o identificador de lista de tarefas oculto, é passado para o método insert(), que o inclui no sistema do Google Tasks por meio de uma invocação de REST. E, por fim, o retorno de chamada de rota /delete-task, assim como o retorno de chamada /add-task, é configurado para receber um identificador de lista de tarefas e um identificador de tarefas. Em seguida, ele usa o método delete() do objeto de serviço para remover a tarefa especificada da lista de tarefas especificada.

Com as rotas e o login de negócios estabelecidos, só falta atualizar a página de índice com botões adicionais para incluir e excluir tarefas. Já que logo eu explicarei como atualizar o status de uma tarefa, agora é uma boa hora de incluir também um botão para essa funcionalidade. Este é o modelo revisado:

Página de índice
<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.css" />
  <link rel="stylesheet" type="text/css"
    href="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.min.css" />
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="http://code.jquery.com/mobile/1.3.2/jquery.mobile-1.3.2.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.core.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/latest/jqm-datebox.mode.calbox.min.js"></script>
  <script src="http://dev.jtsage.com/cdn/datebox/i18n/jquery.mobile.datebox.i18n.en_US.utf8.js"></script>
</head>
<body>
    <div data-role="page">
      <div data-role="header">
      Tasks
      </div>
      <div data-role="content">
      <div data-role="collapsible-set" data-inset="false">
        <?php foreach ($lists['items'] as $list): ?>
          <?php $id = $list['id']; ?>
          <div data-role="collapsible">
            <h2><?php echo $list['title']; ?></h2>
            <ul data-role="listview">
              <?php if (isset($tasks[$id]['items'])): ?>
                <?php foreach ($tasks[$id]['items'] as $task): ?>
                <li>
                  <div class="ui-grid-b">
                    <div class="ui-block-a">
                      <?php if ($task['status'] == 'needsAction'): ?>
                      <h3><?php echo $task['title']; ?></h3>
                      <?php else: ?>
                      <h3 style="text-decoration:line-through">
                        <?php echo $task['title']; ?></h3>
                      <?php endif; ?>
                      <?php if (isset($task['due']) &&
                        ($task['status'] == 'needsAction')): ?>
                      <p>Due on <?php echo date('d M Y',
                        strtotime($task['due'])); ?></p>
                      <?php endif; ?>
                      <?php if (isset($task['completed']) &&
                        ($task['status'] == 'completed')): ?>
                      <p>Completed on <?php echo
                        date('d M Y', strtotime($task['completed'])); ?></p>
                      <?php endif; ?>
                    </div>
                    <div class="ui-block-b"></div>
                    <div class="ui-block-c">
                      <?php if ($task['status'] == 'needsAction'): ?>
                      <a href="/update-task/<?php echo $id; ?>/
                        <?php echo $task['id']; ?>" data-inline="true"
                        data-role="button" data-icon="check"
                        data-theme="a">Done!</a>
                      <?php endif; ?>
                      <a href="/delete-task/
                        <?php echo $id; ?>/<?php echo $task['id']; ?>"
                        data-inline="true" data-role="button" data-icon="delete"
                        data-theme="a">Remove task</a>
                    </div>
                  </div>
                </li>
                <?php endforeach; ?>
              <?php endif; ?>
            </ul> <br/>
            <a href="/add-task/<?php echo $id; ?>"
              data-inline="true" data-role="button" data-icon="plus"
              data-theme="a">Add new task</a>
            <a href="/delete-list/<?php echo $id; ?>"
              data-inline="true" data-role="button" data-icon="delete"
              data-theme="a">Remove list</a>
          </div>
        <?php endforeach; ?>
        </div>
        <a href="/add-list" data-inline="true" data-role="button"
          data-icon="plus" data-theme="b">Add new list</a>
      </div>
    </div>
</body>
</html>

Como mostra a Listagem 10, a marcação de cada lista de tarefas foi atualizada para transformar cada lista em uma grade de duas colunas. A coluna esquerda contém o título e o prazo final da tarefa. A coluna direita contém ações possíveis para a tarefa em questão, como atualizá-la ou excluí-la. Por fim, um novo botão ao final possibilita a atualização da lista com novas tarefas.

O processo de incluir uma nova tarefa em uma lista de tarefas é assim:

Printscreen demonstrando como criar um tarefa.

Atualizando o status de tarefa

Portanto, agora o seu aplicativo suporta a inclusão e exclusão de tarefas e listas de tarefas. A última funcionalidade necessária e marcar as tarefas como concluídas. Formulário de criação de tarefas já inclui um botão para a sua funcionalidade, vinculado à rota /update-task. A Listagem 11 completa o círculo, especificando a lógica de negócios da rota.

Atualização de tarefas
<?php

// ... other routes

$app->get('/update-task/:lid/:tid', 'authenticate',
  function ($lid, $tid) use ($app) {
    $task = new Google_Task($app->tasksService->tasks->get($lid, $tid));
    $task->setStatus('completed');
    $result = $app->tasksService->tasks->update($lid, $task->getId(), $task);
    $app->redirect('/index');
});

O retorno de chamada de rota /update-task recebe identificadores de lista e de tarefa e usa essas informações da API do Google Tasks. Em seguida, essas informações são usadas para preencher um novo objeto Google_Tasks, e o método setStatus() do objeto é usado para alterar o status da tarefa para “concluído”. O método update() do objeto de serviço é usado para enviar por push a nova entrada de tarefa para os servidores do Google.

O processo de marcar uma tarefa como concluída é assim.

Printscreen demonstrando como atualizar ou concluir uma tarefa.

Resumo

Pronto! Você fez um curso relâmpago de integração de dados da API do Google Tasks a um aplicativo PHP usando uma combinação de jQuery Mobile, a biblioteca de OAuth PHP do Google e a microestrutura de PHP Slim. Os exemplos neste artigo apresentaram a você o formato JSON do Google Tasks e mostrou como recuperar listagens de tarefas; adicionar, modificar e excluir tarefas e desenvolver uma interface customizada para as listas de tarefas na conta do Google de um usuário.

Como esses exemplos mostram, a API do Google Tasks é uma ferramenta eficiente e flexível para desenvolver aplicativos novos e criativos ligados ao gerenciamento de tarefas. Brinque um pouco com ele e forme a sua opinião!

Aviso

o conteúdo aqui presente foi traduzido da página IBM Developer US. Caso haja qualquer divergência de texto e/ou versões, consulte o conteúdo original.