Your browser is very old. You might enjoy surfing the web more if you used something newer like:

Google Chrome

Even Firefox would be OK.

If you're being forced at gunpoint to use Internet Explorer, you should at least upgrade it. Version 8 is tolerable and 9 will be OK when it comes out.

Posts tagged “google”

Adding/Deleting Events with the Google Calendar API

I recently had a chance to fool around with the Google Calendar API, specifically adding and deleting events.

I’ve talked in the past about using Google Client Logins to access the Google Data API and we’ll use that same ClientLogin code to authenticate our requests to the Calendar API.

Authenticating

There are different ways to authenticate requests to Calendar depending on the type of application you’re writing. If you’re writing a front-end that a user is going to interact with, it’s a good idea to use AuthSub proxy authentication which will redirect the user, let them login with their Google Account, and send them back to your application.

In our case, we’re writing PHP code that will modify the calendar behind the scenes. For that scenario, we’ll use ClientLogin authentication and use the class we wrote last time

In additional to needing a Google Account for authenticating, you’ll also need the Calendar ID if the calendar being used is not the default calendar. The Calendar ID can be found near the bottom of the page in the Settings for the calendar and is just a special, randomly generated email address.

Adding an Event

Adding an event is pretty straightforward. It’s just a matter of sending an authenticated <entry> XML packet via HTTP POST to the calendar URL which is the following:


http://www.google.com/calendar/feeds/userId/private/full

The userId is either your Google Account email address (if using the default calendar) or the Calendar ID mentioned above (if using any other calendar). You also need to make sure the Content-Type of the POST request is application/atom+xml.

The event XML looks like this:

$xml = "
          
          {$params["title"]}
          {$params["content"]}
          
          
          
          
          
          
        ";

A request is authenticated by including the Authorization: GoogleLogin auth=”authToken” header in the request. The authToken value comes back with the initial ClientLogin request. If you’re using my GoogleClientLogin class, the authToken can be retrieved with the getAuth() method.

One thing to watch for is the initial POST request may come back with an HTTP 302 redirect. If it does, the redirect url will contain a gsessionid. In that case, append the gesessionid to the calendar URL and resend the exact same POST request.

If the entry is successfully added, you’ll get back an XML response containing the entry as well as HTTP headers with the ETag (more on this in a minute) and the entry’s edit link (more on that too).

Deleting an Event

Deleting an event is even easier. Just send an HTTP DELETE request to the edit link you got back when you added the event. This can be pulled from either the response XML when you add the event or from the response “Location” header.

If want to make sure you don’t delete an event that’s been modified by someone else, include the If-Match: etag header where etag is the ETag returned when you added the event. If you don’t care, just send If-Match: *

Miscellaneous

I didn’t know how to send an HTTP DELETE with PHP but it turns out to be pretty simple. Just set the curl option CURLOPT_CUSTOMREQUEST to ‘DELETE’ and off you go.

Also, there are currently two versions of the Google Data Protocol. The current is Version 2 and Version 1 is slowly being phased out. All of the above refers to Version 2 and you want to make sure Google Calendar knows that’s the version you’re using. To that end, make sure to include the header GData-Version: 2 with every request.

Code!

And, to illustrate all of the above, here’s a class for encapsulating some of this stuff.

class GoogleCalendar {
    public $data;
    public $xml;

    public function __construct($login=null, $magicCookie="") {
        $this->data = array();

        if(!is_null($login)) {
            $this->login = $login;
        }
        $this->magicCookie = $magicCookie;
    }

    //Return the authorization header used to authenticate all requests after the first one
    protected function getAuthHeader() {
        return 'Authorization:  GoogleLogin auth="' . $this->login->getAuth() . '"';
    }

    //If the calendar we are accessing is the default, the email address is the same as the email used to login.
    //If the calendar is NOT the default, the email address can be found in the Calendar Settings
    //and should be used as the altEmail
    protected function getFeedEmail() {
        return $this->altEmail ? $this->altEmail : $this->login->email;
    }

    //Adding an entry returns the ETag value as part of the HTTP header
    //This function parses the header and attempts to find the ETag and return it
    //$retFields should be the response exploded using \n as the delimeter
    protected function getETagFromHeader($retFields) {
        return $this->getHeaderFromRegex($retFields, "/^ETag:\s*(.*?)$/");
    }

    protected function getEditLinkFromHeader($retFields) {
        return $this->getHeaderFromRegex($retFields, "/^Location:\s*(.*?)$/");
    }

    protected function getHeaderFromRegex($retFields, $regex) {
        if(is_array($retFields)) {
            foreach($retFields as $header) {
                $matches = array();

                if(preg_match("$regex", $header, $matches)) {
                    return $matches[1];
                }
            }
        }else {
            throw new Exception("The header could not be found because the header array was invalid.");
        }

    }

    //Adds an event to the calendar
    public function addEvent($params) {
        $url = "http://www.google.com/calendar/feeds/{$this->getFeedEmail()}/private/full";

        //startTime should be a time() value so we can convert it into the correct format
        $params["startTime"] = date("c", $params["startTime"]);

        //If no end-time is specified, set the end-time to 1 hour after the start-time
        if(!array_key_exists("endTime", $params)) {
            $params["endTime"] = date("c", strtotime($params["startTime"])+60*60*1);
        }

        $xml = "
                  
                  {$params["title"]}
                  {$params["content"]}
                  
                  
                  
                  
                  
                  
                ";

        //Do the initial POST to Google
        $ret = $this->calPostRequest($url, $xml);

        //If Google sends back a gsessionid, we need to make the request again
        $matches = array();
        if(preg_match('/gsessionid=(.*?)\s+/', $ret, $matches)) {
            $url .= "?gsessionid={$matches[1]}";
            $ret = $this->calPostRequest($url, $xml);
        }

        //Parse the XML response (which contains the newly added entry)
        $retFields = explode("\n", $ret);
        //print_r($retFields);
        $entryXML = simplexml_load_string($retFields[count($retFields)-1]);

        //Return an array containing the entry id (url) and the etag
        return array(
                "id"=> (string)$entryXML->id,
                "etag"=> $this->getETagFromHeader($retFields),
                "link"=> $this->getEditLinkFromHeader($retFields)
                );
    }

    public function deleteEvent($url) {
        return $this->calDeleteRequest($url);
    }

    public function calGetRequest($url) {
        $curlOpts = array();
        return $this->calCurlRequest($url, $curlOpts);
    }

    public function calPostRequest($url, $data) {
        $curlOpts = array(
            CURLOPT_POST=> true,
            CURLOPT_POSTFIELDS=> $data,
            CURLOPT_HEADER=> true,
            CURLOPT_HTTPHEADER=> array('GData-Version:  2', $this->getAuthHeader(), 'Content-Type:  application/atom+xml')
        );
        return $this->calCurlRequest($url, $curlOpts);
    }

    public function calDeleteRequest($url) {
        $curlOpts = array(
            CURLOPT_CUSTOMREQUEST=> "DELETE",
            CURLOPT_HTTPHEADER=> array('GData-Version:  2', $this->getAuthHeader(), 'If-Match:  *')
        );
        return $this->calCurlRequest($url, $curlOpts);
    }

    //This is a generic function for doing curl requests
    //It expects a url and an array of CURLOPT values.  Certain defaults are set if not provided
    private function calCurlRequest($url, $curlOpts) {
        if(!array_key_exists(CURLOPT_FOLLOWLOCATION, $curlOpts)) {
            $curlOpts[CURLOPT_FOLLOWLOCATION] = true;
        }
        if(!array_key_exists(CURLOPT_RETURNTRANSFER, $curlOpts)) {
            $curlOpts[CURLOPT_RETURNTRANSFER] = true;
        }
        if(!array_key_exists(CURLOPT_HEADER, $curlOpts)) {
            $curlOpts[CURLOPT_HEADER] = false;
        }
        if(!array_key_exists(CURLOPT_HTTPHEADER, $curlOpts)) {
            $curlOpts[CURLOPT_HTTPHEADER] = array('GData-Version:  2', $this->getAuthHeader());
        }

        $ch = curl_init($url);
        curl_setopt_array($ch, $curlOpts);
        $ret = curl_exec($ch);
        curl_close($ch);

        return $ret;
    }

    public function __get($name) {
        return $this->data[$name];
    }

    public function __set($name, $val) {
        $this->data[$name] = $val;
    }
}

Here’s a simple example that adds an entry. The GoogleClientLogin class can be found here:

define("APP_NAME", "MY APP");
$email = "my.account@gmail.com";
$password = "mypassword";
$altEmail = "this_is_the_random_email_from_the_calendar_settings";
$login = new GoogleClientLogin($email, $password, GoogleClientLogin::$CALENDAR_SERVICE, APP_NAME);

$cal = new GoogleCalendar($login);
$cal->altEmail = $altEmail;

$entryData = $cal->addEvent(array(
                "title"=> "Auto Test event",
                "content"=> "This is a test event",
                "where"=> "Test location",
                "startTime"=> time()+60*60*24*1
            ));
print_r($entryData);

Where am I? Where are you?

Today, we’re going to cover the very basics of the Google Maps API which, like most of the other Google APIs I’ve been playing with, is very full-featured and easy to use.

I’ll show the basic code to embed a map on a web page and place a marker at a specific address.

Map of Washington, DC

The first thing to do is add the appropriate script tags to the <head> tags of your page.


    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script>

    <script src="http://maps.google.com/maps?file=api&v=2&sensor=false
        &key=ABQIAAAAJKAoDxf0DXpmiPNGeIJ5_BTGeqkvninRPqN0VBb3AwYRVTSEZxQ5doci0or7L4Elev6DsRR4ertl1A"
        type="text/javascript">
    </script>

Notice the key parameter. That’s your API key (which is domain-specific). You can get your own by signing up here.

The first thing to do is create a couple of global variables for the map & geocoding objects. We initialize the map (giving it a <div> to put the map content in), add the zoom control, and enable scroll wheel zooming. The home function centers the map over the middle of the United States and sets the zoom level so the whole country is visible.

  var geocoder;
  var map;

    function home() {
                        map.setCenter(new GLatLng(37.0625, -95.677068), 3);
                    }

    Event.observe(window, 'load', function() {
                        map = new google.maps.Map2($('map_canvas'));
                        geocoder = new GClientGeocoder();

                        map.addControl(new GSmallZoomControl3D());
                        map.enableScrollWheelZoom();

                        home();
                    })

Here’s the html of the <div> where the map content gets placed.


        <div id="map_canvas" style="border:1px solid #979797; background-color:#e5e3df; width:400px; height:300px; margin-bottom:  10px;">
            <div style="padding:1em; color:gray;">Loading...</div>
        </div>

The code for placing a marker is pretty simple. The point variable is a GPoint object which is the Latitude and Longitude of where to place the marker.


    var marker = new GMarker(point);

    map.addOverlay(marker);

This page shows the above in action by letting the user enter an address and place a marker on the map for that address.

 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>Where am I?  Where are you?</title>

        <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.3/prototype.js"></script>

        <script src="http://maps.google.com/maps?file=api&v=2&sensor=false
        &key=ABQIAAAAJKAoDxf0DXpmiPNGeIJ5_BTGeqkvninRPqN0VBb3AwYRVTSEZxQ5doci0or7L4Elev6DsRR4ertl1A"
        type="text/javascript">
        </script>

        <script type="text/javascript">
                    var geocoder;
                    var map;

                    function showAddress(address) {
                        if (geocoder) {
                            geocoder.getLatLng(
                                address,
                                function(point) {
                                    if (!point) {
                                        alert(address + " not found");
                                    } else {
                                        var marker = new GMarker(point);
                                        map.addOverlay(marker);
                                        marker.openInfoWindowHtml(address);
                                    }
                                }
                            );
                        }
                    }

                    function home() {
                        map.setCenter(new GLatLng(37.0625, -95.677068), 3);
                    }

                    Event.observe(window, 'load', function() {
                        Event.observe($('mapit'), 'click', function(event) {
                            Event.stop(event);

                            if($('address').value) {
                                showAddress($('address').value);
                            }
                        });

                        map = new google.maps.Map2($('map_canvas'));
                        geocoder = new GClientGeocoder();

                        map.addControl(new GSmallZoomControl3D());
                        map.enableScrollWheelZoom();

                        home();
                    })

        </script>
    </head>
    <body>
        <div id="map_canvas" style="border:1px solid #979797; background-color:#e5e3df; width:400px; height:300px; margin-bottom:  10px;">
            <div style="padding:1em; color:gray;">Loading...</div>
        </div>

        <textarea id="address" rows="3" cols="35"></textarea><br />
        <input type="submit" id="mapit" value="Map It!" />
    </body>
</html>

Javascript awesomeness with Prototype

I’ve talked a bit about the Ajax features of Prototype in a previous post. Now I want to talk about some of the other fun things Prototype can do.

The first thing to do is add the appropriate Prototype includes to your HTML file. You can do this by downloading the latest version from http://www.prototypejs.org and hosting it yourself.

Or you can let Google do the hard work by adding this to your HTML header:

    

The first thing I’ll mention is how fantastic the Prototype API Documentation is. Once you’ve covered the basics, I highly recommend spending some time reading through it.

The second thing we’re going to do is set up a handler for the window onload event. You might normally do it like this <body onload=”someFunction();”> but I like the method below because it keeps all of our javascript separate from the HTML.

Event.observe(window, 'load', function() {
    alert('Hello World!');
});

The basic format of Event.observe is Event.observe(<element>, ‘<event>’, <event-handling-function>)

We could just as easily have done this

function HelloWorld() {
    alert('Hello World!');
}

Event.observe(window, 'load', HelloWorld);

but I generally use the first version. I think having the handling function inline makes it a little easier to read.

Now let’s talk about how easy it is to get objects out of the DOM. Here’s some HTML


Let’s set up an event handler for the click event on that submit button. We just need to add a few lines to our existing window load event handler.

Event.observe(window, 'load', function() {
    Event.observe( $('dostuff'), 'click', function() {
        alert('Hello World!');
    });
});

As you can see, we’ve just added an Event.observe call inside the window load event handler. The key thing to note is $(‘dostuff’). The $ is essentially a shortcut to document.getElementById but it’s a lot less to type and the element you get back has loads of handy methods added to it by Prototype.

Now that we’ve seen how easy it is to use $ to get back a single object, let’s talk about dealing with multiple objects.

Here’s some more HTML







Let’s update our window load javascript code

Event.observe(window, 'load', function() {
    Event.observe( $('dostuff'), 'click', function() {
        $$('input[type=checkbox]').each( function(elem) {
            alert(elem.id);
        });
    });
});

The example above shows a simple example of the $$ utility method which takes a CSS rule as an argument and returns a list of Prototype-enhanced elements.

In our example, we’re using the each method to apply a function to every item in the list returned by $$.

Here are a few more simple examples of $$:

  • $$(‘input’) returns a list all input elements.
  • $$(‘td.even’) returns a list of all td elements that have a class of even
  • $$(‘table#stuff td’) returns a list of all td elements that are inside the table element that has an id of stuff

So those are few simple examples of using Prototype to retrieve DOM elements. I’ll come back and cover some other Prototype fun in a later post.