Create a mobile-friendly to-do list app with PHP, jQuery Mobile, and Google Tasks

Getting started with the Google Tasks API

10 November 2014
PDF (674 KB)
Share:

Create a mobile-friendly to-do list app with PHP, jQuery Mobile, and Google Tasks

05:42  |  Transcript
author photo - Vikram Vaswani

Vikram Vaswani

Founder and CEO of Melonfire

vikram-vaswani.in

I used to keep track of my to-do list with a Windows desktop application. I'd pop it open every time I remembered something important, type in a brief label, assign a due date, and save it. The application would launch every time I logged in to the system, so I'd immediately know what needed attention and when.

This Windows app worked well, except for two limitations: I couldn't check my to-do list or add tasks when I was away from my computer.

Then I found Google Tasks. It enabled me to check my tasks, and add new ones, while I was on the go.

Google Tasks is integrated with Gmail (though not many people are aware of it), and it lets you create and manage to-do lists online. Since you can access your task list through a browser, it's easy to add, view, and close tasks even while you're on the move.

What you'll need to build your to-do list app

 
  • An Apache/PHP development environment
  • A Google account for testing purposes
  • Slim

    This PHP micro-framework will add structure to your PHP code.

  • Google APIs Client Library for PHP

    This library provides a robust OAuth implementation and wrapper objects that simplify access to all the Google APIs.

  • jQuery Mobile

    This framework gets your application's user interface up and running quickly, with minimal platform and browser compatibility issues.

READ:OAuth 2.0 authentication

What you'll need to know

 

Like many other Google products, Google Tasks exposes a Tasks API that allows third-party applications to connect to it and build custom applications around its data. This API, which follows the REST model, can be accessed through any REST-capable development toolkit, and already has client libraries for many common programming languages, including my favourite, PHP.

To understand the PHP example code I show here, you should know the basics of classes and objects in PHP and be comfortable working with REST. You should also be familiar with HTML, CSS, and jQuery.

Let's get started!

Configure dependent libraries and components

 

First, set up Slim.

If you're not familiar with Slim, it's a PHP micro-framework for rapid development of web applications and APIs. Don't be fooled by the name: Slim includes a sophisticated URL router and support for page templates, flash messages, encrypted cookies, and middleware. It's extremely easy to understand and use, and it comes with great documentation and an enthusiastic developer community.

Step 1. Create the application directory structure with the Slim and Google OAuth libraries

 

Change to the web server's document root directory (typically /usr/local/apache/htdocs on Linux, or C:\Program Files\Apache\htdocs on Windows) and create a new subdirectory for the application. Name this directory tasks.

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

This directory will be referenced in this article as $APP_ROOT.

Assuming you have downloaded the Slim framework and the Google OAuth library described in the previous section, extract these libraries to $APP_ROOT/vendor. Also transfer the index.php and .htaccess files from the Slim download archive to your $APP_ROOT directory, and edit the index.php file to reflect the correct path to the Slim.php file.

Your directory structure should now look like this:

Project directory structure

Step 2. Define a virtual host

 

To make it easier to access the application, it's a good idea to define a new virtual host and set it to the working directory. To do this, edit the Apache configuration file (httpd.conf or httpd-vhosts.conf) and add the following lines to it:

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

These lines define a new virtual host whose document root corresponds to the $APP_ROOT. In the above listing, the name of this host is tasks.melonfire.com. Remember that you will need to change this to either localhost or another domain under your control.

Restart the web server to activate these new settings. Note that it might be necessary to update your network's local DNS server to let it know about the new host as well.

Once you're done, point your browser to your new host. You should see the Slim framework welcome page:

Slim default welcome page

Step 3. Register your application with the Google Apps Platform

 

Before you can use the Google Tasks API, you need to register your web application with Google. To do this, log in to Google using your Google Account credentials and visit the Google Cloud Console. Create a new project, assign it a name, and then turn on access to the Google Tasks API. Your project in the Google Cloud Console should look something like this:

API access in Google Cloud Console

Click to see larger image

Next, register your web application to obtain an OAuth 2.0 client id and secret. Make note of these values, are you will need them for the Google PHP OAuth client.

OAuth credentials in Google Cloud Console

Remember to also set the application redirect URL at this point. This is the URL to which Google will redirect the client browser after completing the OAuth authentication process. In this example, this URL is set to http://tasks.melonfire.com/login:

Redirection URL settings in Google Cloud Console

While you're logged in, you should also visit Gmail, which includes an integrated Google Tasks interface, and add a few sample task lists and tasks to it. This is useful to check that the PHP application is working correctly. The Google Tasks interface in Gmail looks something like this:

Google Tasks interface in Gmail

This probably seems like a lot of hoops to jump through, but the good news is that you only have to do it once.

Understanding the Tasks API

 

The Google Tasks API works by accepting REST requests for actions on resources and responding with the requested information. A resource is simply a URL referencing the object or entity on which the actions are to be performed — such as /lists or /users. An action is one of the four HTTP "verbs" — such as GET (retrieve), POST (create), PUT (update), and DELETE (remove).

The Google Tasks API includes two primary resources: tasks and task lists. A user can have multiple task lists, each of which can have multiple tasks. Tasks always exist within a task list. The Google Tasks API considers the first task list created by a user to be his or her "default" task list.

The Google Tasks API encodes responses in JSON format. Below is an example of an API response, which issues an authenticated GET request to https://www.googleapis.com/tasks/v1/lists/@default/tasks, the API endpoint for retrieving the user's default task list.

Listing 1. Sample Google Tasks API response
{
 "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"
  },
  {
       ...
  }
 ]
}

As Listing 1 shows, the Tasks API generates a JSON-encoded response containing a list of tasks. Each task entry contains some useful meta-data, such as the task title, due date, self URL, and status. It's now quite easy to decode this JSON and convert it into an HTML representation suitable for display in a web browser. Most of the time, though, you won't be issuing raw GET and POST requests to the Tasks API. The Google PHP OAuth client and its service objects provide a convenient wrapper around such requests, encapsulating all related functionality into PHP objects and methods.

Listing tasks

 

Listing 2 uses the Google OAuth library with the Slim framework to connect, authenticate and display a summary of task lists and tasks.

Listing 2. OAuth authentication flow and task list retrieval
<?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');
  }
}

Listing 2, which should be saved as $APP_ROOT/index.php, begins by loading the Slim and Google OAuth client libraries, together with the Google Tasks service object. It initializes a new Slim application object and a new Google_Client object. Needless to say, the Google_Client object must be configured with the same client id, client secret, and redirect URL defined earlier in the Google Cloud Console. A Google_TasksService service object is also initialized; this serves as the primary control point for interacting with the Google Tasks API through PHP.

Slim works by defining router callbacks for HTTP methods and endpoints. This is done simply by calling the corresponding method —get() for GET requests, post() for POST requests, and so on — and passing the URL route to be matched as the first argument to the method. The second argument to the method is a function, which specifies the actions to be taken when the route is matched to an incoming request. Listing 2 sets up three such router callbacks: /index, /login, and /logout. Let's look at each of these in turn:

  • The /login callback handles the OAuth authentication flow. A complete discussion of this flow is outside the scope of this article, although you can get exhaustive details through the Google API documentation. Briefly, this callback uses the Google_Client object's createAuthUrl() method to generate a URL to the Google authentication page (see the next figure) and then redirects the client browser to this URL. Once the user authenticates the application and confirms the data it has access to, Google redirects the client back to the /login URL, which retrieves an access token and stores it in the session. This access token gives the client access to the Google Tasks API.
  • Successful OAuth authentication redirects the client to the application's index page, located at /index. This callback uses the configured Google_TasksService object and its listTasklists() method to retrieve a list of the authenticated user's task lists. The code then iterates over this collection of task lists and for each list, it invokes the service object's listTasks() method to retrieve the individual tasks within the list. This information is then transferred to the view, which takes care of rendering it to the user. I'll show you the view script further along.
  • The /logout method destroys the session, thereby nullifying the access token stored in it. For additional security, it also invokes the client object's revokeToken() method, which also invalidates the token on Google's servers.
Google API authorization

You've already seen that the /index callback takes care of retrieving the user's task lists and the tasks for each list. This information is stored in PHP variables and transferred to the view, which is responsible for formatting it into a human-readable list. The view script should be located at $APP_ROOT/templates/index.php and it looks like this:

Listing 3. Index page
<!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>

Listing 3 sets up a listview page formatted according to standard jQuery Mobile conventions. The primary page element is a <div> element with a data-role="page" attribute. Within this are separate <div> elements for the page header, footer, and content. The page content consists of a series of collapsible <div> elements, with each such element representing one of the user's task lists. Clicking the title of a list unfolds its tasks.

To see how this works, visit http://tasks.melonfire.com/index (replace the URL with that of your own virtual host) in your browser. You should see a task listing like this:

Task listing

Creating and deleting task lists

 

Of course, displaying tasks is just the first step; you'd also like users to be able to add new tasks and task lists. So, let's define a new route in $APP_ROOT/index.php:

<?php

// ... other routes 

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

With this, a request to /add-list will result in the $APP_ROOT/templates/add-list.php template being rendered to the user. The next listing shows the contents of this template. The authenticate() function is a custom function that is executed before the route callback is executed; look in Listing 2 and you'll see that it checks for an access token and redirects the client to the login page if no such token is found, prompting a re-login.

Listing 4. Task list creation form
<!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>

Listing 4 consists of a form with a single field, for the title of the new task list. On submission, the form data will be POST-ed back to the /add-list route, which now needs to be extended to handle the form input. Here is the additional code:

Listing 5. Task list creation
<?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');
  } 
});

Listing 5 sanitizes the title submitted through the form and then creates a new Google_TaskList object. This object represents a task list resource in the Google Tasks API. The object's setTitle() method is used to assign a title, and the service object's insert() method is then used to save the new task list to Google Tasks.

Here is the form, as well as the results of submitting it:

Task list creation

If you now check the Google Tasks interface within Gmail, you should see your newly-added task list there as well. Try it for yourself and see!

If you're allowing users to add lists, you also need to give them a way to delete them. The inverse of the service object's insert() method is its delete() method, which accepts a task list identifier and deletes the corresponding list from Google Tasks. Here is the route definition:

Listing 6. Task list deletion
<?php

// ... other routes 

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

Listing 6 sets up a new route, /delete-list, which accepts a list identifier as part of the request URL. This request URL is then parsed by Slim's routing framework, the list identifier is extracted, and the service object's delete() method is used to remove the corresponding list from Google Tasks.

All that's left now is to update the index page listing with additional buttons to add and remove lists. Here is the code for the revised index page:

Listing 7. Index page
<!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>

In addition to new buttons to add and delete lists, this version of the view script also has some additional enhancements. Due dates are now displayed for tasks, and completed tasks have a line drawn through them, while tasks that are still due (status="needsAction") don't. Here's what it looks like.

Task listing

Creating and deleting tasks

 

Just as it's possible to add and delete task lists, so too is it possible to add and delete tasks within a list. Here are new /add-item and /delete-item routes for this purpose:

Listing 8. Task addition and deletion
<?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');
});

Every task must be associated with a task list and so, the /add-task route callback is set up to receive a task list identifier as a GET request parameter. It then renders the $APP_ROOT/templates/add-task.php template, which contains a form to add new tasks and is described in this listing:

Listing 9. Task creation form
<!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>

Listing 9 contains a form with two visible fields, one for the task title and the other for the task due date. To simplify date entry, the date input field is configured to use the jQuery Mobile DateBox plugin, which displays a graphical date picker for point-and-click date entry. Since tasks must be associated with a task list, the task list identifier received as a GET parameter is also specified in the form as a hidden field.

Once the form is submitted, the data entered into it is sanitized and used to initialize a Google_Task object. This object, together with the hidden task list identifier, is then passed to the service object's insert() method, which takes care of adding it to the Google Tasks system via a REST invocation. And finally, the /delete-task route callback, like the /add-task callback, is configured to receive both a task list identifier and a task identifier. It then uses the service object's delete() method to remove the specified task from the specified task list.

With the routes and business login in place, all that's left is to update the index page with additional buttons for task addition and deletion. Since I'll soon explain how to update a task's status, this is a good time to also add a button for this functionality. Here is the revised template:

Listing 10. Index page
<!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>

As shown in Listing 10, the markup for each task list has been updated to turn each list into a two-column grid. The left column contains the task title and due date. The right column contains actions possible for that task, such as updating it or deleting it. Finally, a new button at the end makes it possible to update the list with new tasks.

The process of adding a new task to a task list looks like this:

Task creation

Updating task status

 

So, your application now supports adding and deleting tasks and task lists. The last bit of functionality you need is to mark tasks as complete. Listing 10 already includes a button for this functionality, linked to the /update-task route. Listing 11 completes the circle, by specifying the business logic for this route.

Listing 11. Task update
<?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');
});

The /update-task route callback receives both list and task identifiers and uses this information to retrieve task information from the Google Tasks API. This information is then used to populate a new Google_Tasks object, and the object's setStatus() method is used to change the task status to "completed." The service object's update() method is then used to push the new task entry to Google's servers.

The process of marking a task as complete looks like this.

Task status update

Summary

 

You're done! You got a crash course in how to integrate data from the Google Tasks API with a PHP application using a combination of jQuery Mobile, the Google PHP OAuth library, and the Slim PHP micro-framework. The examples in this article introduced you to the Google Tasks JSON format and showed you how to retrieve task listings; add, modify and delete tasks; and build a customized interface to the task lists in a user's Google Account.

As these examples illustrate, the Google Tasks API is a powerful and flexible tool when you want to build creative new applications around task management. Play with it sometime, and see what you think!


RELATED TOPICS:PHPjQuery MobileGoogle Tasks

Add a comment

Note: HTML elements are not supported within comments.


1000 characters left

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Mobile development, Cloud computing
ArticleID=956647
ArticleTitle=Create a mobile-friendly to-do list app with PHP, jQuery Mobile, and Google Tasks
publish-date=11102014