By Brice Mason
March 13, 2008
Most people would classify the term Web 2.0 as a buzzword. Although that label is certainly true, Web 2.0 also represents a core set of guiding principles, a road map to the next generation of web application development. In the first part of this article, I will explain and reveal the meaning and purpose behind this overused and underappreciated term and at the same time demonstrate how AOL has effectively implemented Web 2.0 principles from both the developer and user perspectives. In the second half of this article, I will demonstrate how some of the most popular AOL services work together to produce a cohesive Web 2.0 application that uses PHP and the Ext JS JavaScript framework.
Most folks associate Web 2.0 with any number of social networking sites or other user-driven forces on the Internet. This restrictive view of Web 2.0 makes it difficult to define and even appreciate the lessons to be learned by implementing its core concepts. Web 2.0 applications branch out further, specifically meeting five main criteria:
- User-driven development
- Standards-based development
- Data convergence and transformation
- Collaborative classification
- Social networking
Principle One: User-Driven Development
The first concept I'd like to explore is the most common identifier of Web 2.0--user-driven development. The old model of web development consisted of a central entity tasked with the development of a site from both an architecture and content perspective. The user's job was simply to tune in and consume. This has changed dramatically because there has been an explosion in the development of web-based tools whose sole purpose is to be placed in the hands of the user for content generation. This concept is all about giving the user a voice.
AOL continues to support this first and most important Web 2.0 concept through a number of initiatives. Armed with an AOL screen name, users can place their own creative stamp on the world by writing a blog using the People Connection space, sharing pictures and other files using Xdrive, or by contributing video via the AOL Video upload service.
Although this first and most important concept seems trivial to implement, its effectiveness is dependent on the next principle--standards-based development.
Principle Two: Standards-Based Development
To develop a tool or service that allows a user base to generate content is fantastic and obviously the first step in creating a Web 2.0 application. However, for a tool or service to really show its effectiveness, it must be developed as an intuitive and accessible service. The concept of accessibility is complex, but generally accessibility is defined as eliminating any barrier to a user who wants to use your service. The barrier could be anything--including vision, hearing, or dexterity limitations of the user--that is purposely accommodated or adjusted for in the application.
To develop for and implement a standard set of accessibility features involves not only studying guidelines such as the W3C Web Accessibility Initiative or the Section 508 government standards, but learning a new way of thinking about web development. AOL has taken much of the guesswork out of this task with the development of the AXS JavaScript library, a free, lightweight (approximately 2.6 KB compressed, without comments) library that makes it easy to incorporate accessibility. AXS is cross-platform and proven to work on the most popular platforms. With its successful implementation on the AOL WebMail system, it's a proven tool and one of the keys to producing a Web 2.0 application.
Principle Three: Data Convergence and Transformation
So far, I've talked about developing tools and services to be placed in the hands of the user for generating content. This next topic reinforces this concept, although, for a different user--the developer. At the heart of this concept is the availability of mashable data. We've recently seen an explosion in new tools and services, made possible by the simple fact that getting our hands on just about any kind of data and code is easier than ever. By leveraging any number of data streams, developers have been able to rapidly produce fun, creative, and useful services for users like never before.
AOL supports this major principle for the developer audience through its many APIs, not only opening a gateway to its underlying services, but also to its tremendously large user base. Many of the AOL APIs offer a number of access methods to their services, whether it be standards-based XML, JavaScript Object Notation (JSON), or even ActionScript libraries for Adobe Flash development. The availability of these generic data streams fosters a technology-independent approach, giving any developer with nearly any initiative the ability to produce a mashup.
Principle Four: Collaborative Classification
In keeping with the theme of user-driven development, this next concept deals with the classification of content after it's been produced. Until recently, the task of finding content on the web usually has meant using a search engine, with its own internally developed algorithm to return what it calculates are the best results. Today, rather than relying on a system-generated search routine, Web 2.0 applications typically will implement some supporting classification of content through user-contributed methods such as tagging. As the amount of data we produce increases, the methods used to classify and search the content becomes increasingly important.
AOL supports this concept most noticeably through its video upload site. The searchability of this site is flexible and smart. With content classified by location, category, user-created tags, and user opinion, the prospect of finding what you're looking for is great. Further, with the availability of the AOL Video Upload API, developers now have the power to offer this service in their own applications.
Principle Five: Social Networking
Hopefully by now you've noticed the theme behind the principles I've detailed so far. The final concept I'll cover brings it all together. I've been talking about user-driven content--how we produce it, enable it, transform it, and find it. Principle five provides the link between users through the idea of social networking. Social networking is best described as any kind of virtual link between people that makes physical distance no longer of any consequence.
AOL popularized this way of connecting people through its instant messaging platform, AOL Instant Messenger, beginning in the mid 1990s. Although competing technologies existed at the time, AOL truly brought this technology to the masses and popularized the movement. Today, AOL's instant messaging platform is still just as popular and innovative as in its original form. With tools and services available via its Open AIM platform, AOL has effectively enabled user audiences of all abilities to produce useful and engaging web applications.
Putting It Together
So far I've covered all the AOL implementations that foster good Web 2.0 development. Next, you'll use AOL services to develop an example application that demonstrates some of the principles described here. The application you're going to develop is called BuddyFusion, and its concept is born of the idea that with a simple screen name, you can integrate and accomplish quite a lot. Using a buddy list obtained through Open AIM services, you'll create an application that shows a summary of the latest e-mail messages from the buddy, blog listings, and currently enabled Xdrive shares. To see a live example before creating your own, check out the one I created, at http://developer.stegoworks.com/assets/external/aol/2.0/index.php.
The BuddyFusion application is built using PHP on the back end with an Ext JS Asynchronous JavaScript and XML (AJAX) front end, so knowledge of both PHP and JavaScript, although not required, is preferred. Because you'll be using multiple AOL APIs, you'll also need an AOL developer ID. Although not required, knowledge of these AOL services is helpful, as well.
BuddyFusion
The glue that will hold this application together--authentication--is supported by the AOL OpenAuth API, a way for developers to incorporate AOL identity solutions into their own sites. This API facilitates highly personalized content development and simplifies what would normally be the time-consuming process of managing application authentication. Authentication is handled via the following index page:
<?php
// dev id, success url, login url
$devId = "someDevId";
$succUrl = "http://localhost/web-2-0/index.php";
$loginUrl = "http://api.screenname.aol.com/auth/login?f=qs&r=52&devId=" . $devId . "&succUrl=" . $succUrl;
$authToken = "";
$screenname = "";
include( "getInfo.php" );
include( "getPresence.php" );
ob_start();
if( isset( $_GET[ "token_a" ] ) ) {
// capture the auth token
$authToken = $_GET[ "token_a" ];
// get the screenname
$authInfo = getInfo( $devId, $succUrl, $authToken );
$screenname = $authInfo->response->data->userData->loginId;
print "<p>" . $screenname . " logged in</p>";
// define a global script block for javascript
print "<script type=\"text/javascript\">";
print "var devId = \"" . $devId . "\";";
print "var succUrl = \"" . $succUrl . "\";";
print "var authToken = \"" . $authToken . "\";";
print "var presenceInfo = " . getPresence( $devId, $succUrl, $authToken ) . ";";
print "</script>";
// hook in the main javascript
print "<script type=\"text/javascript\" src=\"main.js\"></script>";
}
else {
print "<a href=" . $loginUrl . ">Login</a>";
}
?>
<div id="pnl-main"></div>
This code checks for the existence of an authentication token in the query string. If it's defined, basic authentication data is retrieved via the getInfo method of the OpenAuth API. Supported data used throughout the application, such as the developer ID, application URI, and authentication token are made available to the AJAX front end via print statements. At the center of it all is the getPresence PHP function, which retrieves the buddy list of the authenticated user.
Getting a Buddy List
The Web AIM API offers a rich set of information that can easily be retrieved using any of a number of server- or client-side methods. The getPresence PHP method is a server-side implementation that uses JSON for encoding data and making it available to the AJAX front end as follows:
<?php
function getPresence( $devId, $succUrl, $authToken ) {
// where to query for presence info
$uri = "http://api.oscar.aol.com/presence/get?f=json&bl=1&k=" . $devId .
"&a=" . urlencode( $authToken );
// build and execute the request
$req = curl_init();
curl_setopt( $req, CURLOPT_URL, $uri );
curl_setopt( $req, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $req, CURLOPT_REFERER, $succUrl );
$jsonResponse = curl_exec( $req );
curl_close( $req );
// decode the response
$response = json_decode( $jsonResponse );
// maintains the aggregated buddy list
$presenceInfo = array();
$presenceInfo[ "buddies" ] = array();
// shortcut to the groups array
$groups = $response->response->data->groups;
for( $i = 0; $i < count( $groups ); $i++ ) {
// consolidate the buddy list data
$presenceInfo[ "buddies" ] = array_merge( $presenceInfo[ "buddies" ], $groups[ $i ]->buddies );
}
// encode and return the result.
return json_encode( $presenceInfo );
}
?>
This function accepts an AOL developer ID, the application location to use as the referrer, and a valid OpenAuth authentication token. Using the cURL library for PHP, an HTTP request is crafted according to the specifications of the Web AIM API. The response format you'll use is JSON because it is a valid subset of JavaScript. This makes it easy to massage the data returned to you in PHP's realm into a format to be easily accommodated on the client side. The data returned from this function call is an array of buddy objects that finds its final resting place in a JavaScript variable named presenceInfo. When the buddy list has been retrieved, the main JavaScript file (main.js) is hooked in. This provides the main user interface components that will produce the final product shown in Figure 1.
Figure 1. Sneak peek at the final product
As you can see, the application is a two-column layout consisting of a buddy list and an accordion control used to house the supporting data retrieved as the user clicks each buddy. To represent the buddy list you just retrieved in the user interface, use the following JavaScript:
ds_presenceInfo = new Ext.data.JsonStore(
{
root: "buddies",
fields: [ "aimId", "displayId", "state", "presenceIcon" ],
data: presenceInfo
}
);
grid_presenceInfo = new Ext.grid.GridPanel(
{
store: ds_presenceInfo,
width: 200,
height: 400,
columnWidth: .35,
columns: [
{ header: "Buddy", dataIndex: "aimId" },
{ header: "Status", dataIndex: "state" }
]
}
);
An Ext JSON data store is used to bind to the presenceInfo variable you just populated, which is then hooked to the data grid control demonstrated in the left column of the screenshot in Figure 1. As the user clicks each buddy listed in the grid, functions are fired to update the accordion controls. The first function you'll create is the retrieval of new e-mail.
Getting New E-mail
You'll find that as we move along you'll be using relatively the same process each time to populate the user interface. The first service I'll address is the OpenMail API, which can be used to retrieve new, unread e-mail from your inbox. You'll use it here to retrieve any new e-mail that was sent from the currently selected buddy in your list as follows:
ds_mail = new Ext.data.JsonStore(
{
url: "getMail.php",
root: "messages",
fields: [ "sender", "subject", "sentOn", "messageLink" ]
}
);
grid_mail = new Ext.grid.GridPanel(
{
store: ds_mail,
title: "Mail",
width: 400,
columns: [
{ header: "From", width: 125, dataIndex: "sender" },
{ header: "Subject", width: 275, dataIndex: "subject" }
]
}
);
grid_mail.on( "rowclick", function( g, r, e ) {
window.open( g.getSelectionModel().getSelected().data.messageLink );
} );
This JavaScript code again creates a JsonStore, which is populated from a PHP page whose output is JSON-encoded data. The store is then used in a grid control to output basic information, as well as to provide a clickable interface to retrieve the message in a new window. The PHP code, acting as the data feed, is shown below:
<?php
$devId = $_POST[ "devId" ];
$authToken = $_POST[ "authToken" ];
$succUrl = $_POST[ "succUrl" ];
$screenname = $_POST[ "screenname" ];
// build the uri to the mail service
$uri = "http://api.mail.aol.com/mail/newMailList?f=json&a=" . $authToken ."&devId=" . $devId . "&referer=" . $succUrl;
// build and execute the request
$req = curl_init();
curl_setopt( $req, CURLOPT_URL, $uri );
curl_setopt( $req, CURLOPT_RETURNTRANSFER, true );
$jsonResponse = curl_exec( $req );
curl_close( $req );
// decode the response
$response = json_decode( $jsonResponse );
// buddy messages
$buddyMail = array();
$buddyMail[ "messages" ] = array();
// loop over the inbox messages, filter for screenname
$messages = $response->response->data->mailList->messageList[0]->messages;
for( $i = 0; $i < count( $messages ); $i++ ) {
if( $messages[ $i ]->sender == $screenname . "@aim.com" ) {
array_push( $buddyMail[ "messages" ], $messages[ $i ] );
}
}
// encode and print out the response
print json_encode( $buddyMail );
?>
The JSON-encoded data retrieved from the HTTP request to the OpenMail service is looped over and filtered for any mail objects that have a sender matching the buddy screen name passed in to the function. This data is added to a data structure that is JSON-encoded and printed out. An example mail screen is shown in Figure 2.
Figure 2. Example mail control
Getting a Blog List
Because the client-side implementation is basically the same as I've just demonstrated to retrieve presence information and new e-mail data, I'll leave this part out as we move on to retrieving blog lists for the target buddy. The PHP used to handle this is shown here:
<?php
$screenname = $_POST[ "screenname" ];
$devId = $_POST[ "devId" ];
$baseUrl = $_POST[ "succUrl" ];
$authToken = $_POST[ "authToken" ];
$value = "";
$bypass = false;
$status = "";
$redirectURL = "";
$rid = "";
$blMembers = array();
$blogs = array();
$blogs[ "blogs" ] = array();
$collect = false;
$href = "";
// ----------------------------------------------------
function blogStartElement($parser, $name, $attrs) {
global $value, $collect, $href;
$value = "";
if (!strcmp($name, "COLLECTION")) {
$href = $attrs["HREF"];
$collect = true;
}
}
// ----------------------------------------------------
function blogEndElement($parser, $element) {
global $value, $collect, $href, $blogs;
if (!strcmp($element, "ATOM:TITLE")) {
if ($collect) {
// associative array used to hold basic blog data
$tmp = array();
$tmp[ "title" ] = $value;
$tmp[ "href" ] = $href;
array_push( $blogs[ "blogs" ], $tmp );
}
}
}
// ----------------------------------------------------
function blogCharData($parser, $data) {
global $value;
$value = $data;
}
// ----------------------------------------------------
function checkForBlogs($owner) {
global $collect, $authToken, $devId, $baseUrl;
$checkBlogsUrl = "http://api.blogs.aol.com/" . $owner . "/service.xml";
$req = curl_init();
curl_setopt($req, CURLOPT_URL, $checkBlogsUrl);
curl_setopt($req, CURLOPT_RETURNTRANSFER, 1 );
$header[] = "X-AOL-DEVID: " . $devId;
$header[] = 'Authorization: OpenAuth token="' . $authToken . '"';
curl_setopt($req, CURLOPT_HTTPHEADER, $header);
curl_setopt($req, CURLOPT_REFERER, $baseUrl);
//curl_setopt($req, CURLOPT_HEADER, 1);
$responseXml = curl_exec( $req );
curl_close($req);
$collect = false;
$responseXml = strstr($responseXml, "<?");
$blogs_xml_parser = xml_parser_create();
xml_set_element_handler($blogs_xml_parser, 'blogStartElement', 'blogEndElement');
xml_set_character_data_handler($blogs_xml_parser, 'blogCharData');
xml_parse($blogs_xml_parser, $responseXml, true);
xml_parser_free($blogs_xml_parser);
}
// ----------------------------------------------------
checkForBlogs( $screenname );
print json_encode( $blogs );
?>
Much of this code was obtained and modified from the gallery of examples available on dev.aol.com. It should be noted that this area can serve as a great source of information and inspiration when deciding what mashup you're looking to develop. With this logic already developed and freely available, it made sense to reuse it and modify it for this article.
Retrieving a lisg of blogs for a given user relies on the processing of service documents formatted to the Atom syndication 1.0 standard specification. Because PHP uses an event-based model of XML processing, you need to create a couple of event handlers to process the expected XML schema. The implementation is simple in that you only want to retrieve the title and location of blogs for a given buddy's screen name. This data is formatted in your own data structure, serialized to JSON, and output to the screen for consumption by the UI.
Getting Xdrive Shares
The final control I'll explore uses the Xdrive API to produce a dataset that contains all root folder names that are shared with the target buddy screen name. This is accomplished server-side first by authenticating with the Xdrive service using your already obtained authentication token as follows:
<?php $devId = $_POST[ "devId" ]; $succUrl = $_POST[ "succUrl" ]; $authToken = $_POST[ "authToken" ]; $screenname = $_POST[ "screenname" ]; $screennameEmail = $screenname . "@aim.com"; $uri = "https://plus.xdrive.com/json/v1.2/"; $loginRequest = $uri . "member.aollogin"; $jsessionid = ""; $recoveryToken = ""; $rootFolderId = ""; // create an open auth object to post in $data = array(); $data[ "user" ] = array(); $data[ "user" ][ "type" ] = "OpenAuthObject"; $data[ "user" ][ "devId" ] = $devId; $data[ "user" ][ "referer" ] = $succUrl; $data[ "user" ][ "token" ] = $authToken; // encode the data $jsonData = json_encode( $data ); // prepare the data for posting $postData = array(); $postData[ "data" ] = $jsonData; $req = curl_init(); curl_setopt( $req, CURLOPT_URL, $loginRequest ); curl_setopt( $req, CURLOPT_RETURNTRANSFER, true ); curl_setopt( $req, CURLOPT_SSL_VERIFYPEER, false ); curl_setopt( $req, CURLOPT_POSTFIELDS, $postData ); curl_setopt( $req, CURLOPT_HEADER, false ); $response = curl_exec($req); curl_close( $req ); // decode the response $loginResponse = json_decode( $response ); $recoveryToken = $loginResponse->recoveryToken; $jsessionid = $loginResponse->jsessionid; $rootFolderId = $loginResponse->results->user->rootFolderId; ?>
A request is built to query the member.aollogin method of the Xdrive API. This method returns a user object that contains some of the interesting data you want to cache, such as the recovery token and session ID, for maintaining session state, and the Xdrive root folder ID used to query folders and files for the share data. The following PHP code obtains the share configurations for a given buddy's screen name:
<?php
// get a file listing
$fileRequest = $uri . "file.getlisting;jsessionid=" . $jsessionid . "?recoveryToken=" . $recoveryToken;
// prepare the FileObject
$fileObject = array();
$fileObject[ "srcFile" ] = array();
$fileObject[ "srcFile" ][ "type" ] = "FileObject";
$fileObject[ "srcFile" ][ "id" ] = $rootFolderId;
$jsonFileObject = json_encode( $fileObject );
$postData = array();
$postData[ "data" ] = $jsonFileObject;
$req = curl_init();
curl_setopt( $req, CURLOPT_URL, $fileRequest );
curl_setopt( $req, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $req, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $req, CURLOPT_POSTFIELDS, $postData );
curl_setopt( $req, CURLOPT_HEADER, false );
$response = curl_exec($req);
curl_close( $req );
$fileResponse = json_decode( $response );
// define a shortcut to the file children
$childFiles = $fileResponse->results->children;
// prepare the return file listing
$fileListing = array();
$fileListing[ "shares" ] = array();
for( $i = 0; $i < count( $childFiles ); $i++ ) {
$sharePermissions = getShare( $screennameEmail, $childFiles[$i]->id );
if( count( $sharePermissions ) > 0 ) {
$sharePermissions[ "filename" ] = $childFiles[$i]->filename;
array_push( $fileListing[ "shares" ], $sharePermissions );
}
}
print json_encode( $fileListing );
?>
After a file listing of the root folder is retrieved, the child files are enumerated to filter based on share data obtained through the getShare function (see the following code listing). The share data consists of folder names and their corresponding permissions assigned to the buddy. An example screenshot is shown in Figure 3.
function getShare( $screennameEmail, $fileId ) {
global $uri, $jsessionid, $recoveryToken;
$shareRequest = $uri . "share.listoutboundshares;jsessionid=" . $jsessionid . "?recoveryToken=" . $recoveryToken;
// prepare the FileObject
$fileObject = array();
$fileObject[ "srcFile" ] = array();
$fileObject[ "srcFile" ][ "type" ] = "FileObject";
$fileObject[ "srcFile" ][ "id" ] = $fileId;
$jsonFileObject = json_encode( $fileObject );
$postData = array();
$postData[ "data" ] = $jsonFileObject;
$req = curl_init();
curl_setopt( $req, CURLOPT_URL, $shareRequest );
curl_setopt( $req, CURLOPT_RETURNTRANSFER, true );
curl_setopt( $req, CURLOPT_SSL_VERIFYPEER, false );
curl_setopt( $req, CURLOPT_POSTFIELDS, $postData );
curl_setopt( $req, CURLOPT_HEADER, false );
$response = curl_exec($req);
curl_close( $req );
$shareResponse = json_decode( $response );
$p = $shareResponse->results->permissions;
for( $i = 0; $i < count( $p ); $i++ ) {
if( $screennameEmail == $p[$i]->grantee->nickname ) {
$tmp = array();
$tmp[ "hasDelete" ] = $p[$i]->hasDelete;
$tmp[ "hasRead" ] = $p[$i]->hasRead;
$tmp[ "hasCreate" ] = $p[$i]->hasCreate;
$tmp[ "hasModify" ] = $p[$i]->hasModify;
$tmp[ "hasWrite" ] = $p[$i]->hasWrite;
$tmp[ "hasShare" ] = $p[$i]->hasShare;
return $tmp;
}
}
return array();
}
Figure 3. Xdrive shares
The final step you can take with your BuddyFusion application is to apply a custom Ext JS theme to brand it to your liking, then it's ready to go.
Conclusion
Hopefully the ideas presented this article and the creation of an AOL Web 2.0 application have hit home for you as you develop your Web 2.0 application. The next age of development is simple--it's just about you and your users, and AOL can take you there. For more information on making the most of your next app, check out the following resources:
AOL API ListingAOL AXS JavaScript library
"Creating Accessible Web 2.0 Application with AXS"
Section 508 Standards
W3C Web Accessibility Initiative
Ext JS JavaScript Framework
