Creating the YouTubeActivityViewer application using the PHP client library, memcache and jQuery

Jeff Fisher & Jochen Hartmann, Google Data APIs Team
March 2009


This article is a short tutorial on how to build the YouTubeActivityViewer, a simple application that uses the YouTube Data API's user activity feeds. It explains how to design a simple PHP-based backend using our PHP client library, which handles talking to the YouTube API. To make things more interesting, we also incorporate memcache to cache API responses and to avoid polling the servers for the same information twice. The frontend is written in JavaScript, using jQuery. All of the code samples used here can be found on the ytaviewer project page.


This tutorial assumes you are familiar with or have access to the following resources:

To use the application and see results, it will be necessary to sign up for the following:

For best results, log in to your YouTube account and perform some of the activities that appear in activity feeds. Also, ensure that you have contacts in your account that have performed those some of those activities in the last 60 days.

Note: To use memcache (optional), you need to also install it on the server that is running the PHP backend.

Overview of the tools used in this tutorial

This will be a very brief overview of the tools that are used in building this application. First up is the open-source PHP client library. Our client library provides helper methods and data model classes to make working with the Google Data APIs easier. So, instead of working with raw XML, you can use objects that have properties that mirror our XML structure. For each service (such as YouTube or Google Calendar), you will find many data model classes and a single service class.

In our case, we are using an instance of the Zend_Gdata_YouTube class to handle all of the communications (including authentication) between our application's backend and the YouTube Data API servers. If you don't already have a copy of the client library running, please read our Getting Started Guide for information on how to set up your server and install a copy.

Memcache is a distributed memory object caching system. To use memcache, you need to have it installed and running on your server. In basic terms, you can think of memcache as a memory-only database. We will explain how to use memcache in the Writing the application backend in PHP section. For more details about memcache, please review the documentation and refer to the appropriate section in the PHP Manual.

jQuery is a lightweight JavaScript framework for DOM manipulation and many other useful things. The description on the jQuery site sums things up pretty succinctly: "jQuery is a fast and concise JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development." We will go into more detail in the Writing the frontend in JavaScript section of this tutorial. ThickBox is a UI widget that shows content (images, videos, etc.) through an iFrame. ThickBox was built on top of jQuery.

Writing the backend in PHP

The PHP backend talks to the YouTube API and handles user authentication and video metadata caching. The frontend interacts with the backend by making requests to callbacks that return JSON-encoded information.

The authentication is handled through AuthSub. Once the final session token is retrieved, it is stored in the PHP $_SESSION variable. If your website lets users create their own accounts, you could store this token in a database with other information for a user.

To learn the current user's YouTube username, first retrieve their profile and then extract it from the <yt:username> element:

$yt = getYtService();
$userProfileEntry = $yt->getUserProfile('default');
$username = $userProfileEntry->getUsername()->text;

Note: The getYtService() method is just a wrapper function that instantiates an authenticated Zend_Gdata_YouTube service object. Passing in the string default is valid to retrieve the profile of the currently authenticated user.

Retrieving user and friend activity feeds

Once we have a service object, retrieving activity feeds is quick and painless:

$yt = getYtService();
$activityFeed = $yt->getActivityForUser($username);
// Note that the second parameter to this function 
// will be explained in the next section
echo renderActivityFeed($activityFeed, "useractivity-$username");

The process to retrieve a user's friend activity feed is similar:

$yt = getYtService();
$friendActivityFeed = $yt->getFriendActivityForCurrentUser();
// Note that the second parameter to this function 
// will be explained in the next section
echo renderActivityFeed($friendActivityFeed, "friendactivity-$username");

Creating JSON responses and using Memcache

The renderActivityFeed() function is called when we have retrieved a feed of activity information. It iterates through the feed and creates a compact JSON representation of the video metadata. Additionally it also caches the data in memcache.

Memcache is a lightweight service that can be easily run on your web server to reduce database and API load. Using memcache with PHP is easy. First we define a function to get a reference to the memcache object:

// Returns a Memcache object to interact with the memcache servers
function getMemcache() {
  $memcache = new Memcache;
  $server = $GLOBALS[[]'ytaviewer_config'][[]'memcache_server'];
  $port = $GLOBALS[[]'ytaviewer_config'][[]'memcache_port'];
  $memcache->connect($server, $port) or die ("Could not connect to memcache");
  return $memcache;

Note: Please note that for the above code to work, you need to have memcache installed and running on your server.

The data that you want to store in memcache needs to be attached to a special key name. In the case below, we are using the name "friendactivity-$username", where $username corresponds to the name of a YouTube user, to store friend activity feed data. In the snippet below, this key is what we pass to the renderActivityFeed function's $feedId parameter.

Before we construct a JSON representation of a feed, we need to check whether we already have a copy of it in memcache. If that is the case, we simply return it as shown in first few lines of the renderActivityFeed() function below. Otherwise, we create a new $compactFeed array which we then encode into JSON, using the aptly named json_encode() function.

function renderActivityFeed($feed, $feedId) {

  if($GLOBALS[[]'ytaviewer_config'][[]'enable_memcache']) {
    $memcache = getMemcache();
    $compactFeed = $memcache->get($feedId);
  } else {
    $compactFeed = false;

  if(!$compactFeed) {
    $compactFeed = Array();

    // $feed represents a Zend_Gdata_YouTube_ActivityFeed
    foreach($feed as $entry) {
      $cEntry = Array();
      $cEntry[[]'author'] = $entry->getAuthorName();
      $cEntry[[]'activity_type'] = $entry->getActivityType();
      $cEntry[[]'updated'] = $entry->getUpdated()->text;

      switch($cEntry[[]'activity_type']) {
        case 'video_rated':
          $cEntry[[]'rating'] = $entry->getRatingValue();
        case 'video_shared':
        case 'video_favorited':
        case 'video_commented':
        case 'video_uploaded':
          $cEntry[[]'video_info'] = fetchVideoMetadata($entry->getVideoId()->text);
        case 'friend_added':
        case 'user_subscription_added':
          $cEntry[[]'username'] = $entry->getUsername()->text;
      $compactFeed[[]] = $cEntry;
    $compactFeed = json_encode($compactFeed);

    if($GLOBALS[[]'ytaviewer_config'][[]'enable_memcache']) {
      $expiration_time = $GLOBALS[[]'ytaviewer_config'][[]'feed_expiry_time'];
      $memcache->set($feedId, $compactFeed, MEMCACHE_COMPRESSED, $expiration_time);
  return $compactFeed;

Note: When storing information in memcache, you are able to specify an expiration time in seconds. This allows you to expire objects in the cache after a certain time. In the activity viewer, video metadata is stored for 2 hours, whereas activity feeds are stored for 1 minute.

The snippet below shows the fetchVideoMetadata() function, which retrieves video metadata from either memcache or the API and again creates a JSON representation of it:

// Fetches information about a video based upon its YouTube ID and stores
// this data in memcache (if enabled).
function fetchVideoMetadata($videoId) {

  // If we are using memcache, try to find the metadata in the cache first
  if($GLOBALS[[]'ytaviewer_config'][[]'enable_memcache']) {
    $memcache = getMemcache();
    $video = $memcache->get("video-$videoId");
  else {
    $video = false;

  if($video) {
    return $video;

  // If we don't have any cached data, request it from the API
  try {
    $yt = getYtService();
    $videoEntry = $yt->getVideoEntry($videoId);

    $video = Array();
    $video[[]'id'] = $videoEntry->getVideoId();
    $video[[]'title'] = $videoEntry->getVideoTitle();
    $video[[]'view_count'] = $videoEntry->getVideoViewCount();
    $video[[]'rating'] = $videoEntry->getVideoRatingInfo();
    $thumbnails = $videoEntry->getVideoThumbnails();
    $video[[]'thumbnail'] = $thumbnails[[]0][[]'url'];
    $video[[]'player'] = $videoEntry->getFlashPlayerUrl();
    $video[[]'uploader'] = $videoEntry->author[[]0]->name->text;

  } catch(Zend_Gdata_App_HttpException $e){
    // Be sure to always handle potential exceptions
    $httpStatus = $e->getResponse()->getStatus();

    if($httpStatus > 500) {
      $video = 'SERVER_ERROR';
    } else {
      $video = 'NOT_AVAILABLE';
  // If memcache is enable, store the video data for the set expiry time
  if($GLOBALS[[]'ytaviewer_config'][[]'enable_memcache']) {
    $expiration_time = $GLOBALS[[]'ytaviewer_config'][[]'metadata_expiry_time'];
    $memcache->set("video-$videoId", $video, MEMCACHE_COMPRESSED, $expiration_time);
  return $video;

Note: You may want to add more robust error handling logic in your application to deal with a wider range of HTTP status codes. If you are implementing a similar backend in Java or .NET, you may want to consider using the built in support for batch requests which allows a single request to fetch metadata for a number of entries at once.

Handling requests

The remaining functions in the PHP layer are straightforward and should be familiar to PHP web developers. The renderPage() function displays the page HTML. Requests from the frontend are dealt with in handleRequest(), a function that just checks the URL parameters for the type of data that is requested and then dispatches the appropriate call to return it.

That wraps up the PHP layer. You have seen how to use the PHP client library to retrieve and then encode activity feeds. We also reviewed how to use memcache to store video metadata.

Writing the frontend in JavaScript

All we really need to do in our frontend is dispatch some calls to our index.php page requesting various feeds. The list below is a quick overview of the basic things that we can ask for:

  • ?q=userfeed — retrieve the activity feed for the currently authenticated user
  • ?q=userfeed&who=<some_username> — retrieve the activity feed for a specific user (up to 20 usernames can be submitted)
  • ?q=friendfeed — retrieve the activity feed for the currently authenticated user's friends on
  • ?q=whoami — retrieve the YouTube username of the currently authenticated user

All of our frontend logic lives in a single JavaScript file called frontend.js. Let's start by breaking down the tasks that this code needs to be able to handle and then go into detail on each section. The basic flow for each request is as follows:

  1. Figure out whether our user is logged in. If not, direct to the login page.
  2. If the user is logged in, fetch the currently authenticated user's name (if we don't already have it) and dispatch a request for the user's activity feed.

In addition to the standard flow, we also need to handle some events based on the input form. The possible events here are:

  • Fetch the friend activity feed for the currently authenticated user.
  • Fetch the user activity feed for a number of usernames (up to 20) entered in the search box.

Before we delve into the functions that are used to perform these tasks, let's first organize our javascript file. The first step is to add namespacing for our functions and constants. This is accomplished by the following line:

var ytActivityApp = {};

Next, we need to add the 'ready event' function, a simple jQuery technique that allows you to make function calls once the page has completed loading:


  // Check the loggedIn variable that gets set in the PHP script
  if (loggedIn) {
    // Show the search and feed selection options

    // On page reloads use the username saved in PHP's session
    if (authenticatedUsername) {
      ytActivityApp.MY_USERNAME = authenticatedUsername;
      $('#' + ytActivityApp.USER_LOGIN_DIV).html(
        'Logged in as: ' + ytActivityApp.MY_USERNAME +
        ' — <a class="logout_link" href="' +
        ytActivityApp.LOGOUT_URI + '" >log out</a>');
    } else {
      // On the first load, we need to fetch the username directly
      $.get(ytActivityApp.URI, { q: "whoami" },
          ytActivityApp.MY_USERNAME = data.substring(1, data.length-1);
          $('#' + ytActivityApp.USER_LOGIN_DIV).html(
            'Logged in as: ' + ytActivityApp.MY_USERNAME +
            ' — <a class="logout_link" href="' +
            ytActivityApp.LOGOUT_URI + '" >log out</a>');

    // Display the <div> to show that we are loading activity

    // Fetch a feed based on what was selected
    if (ytActivityApp.FEED_REQUESTED == ytActivityApp.USER_ACTIVITY_FEED) {
    } else {
  } else {
    // Not logged in
    $('#top').css({'margin-top': '150px', 'width': '100%',
      'font-weight': 'bold', 'font-size': 'x-large'});
    $('#' + ytActivityApp.USER_LOGIN_DIV).css(
      {'font-size': '200%', 'font-weight': 'bold',
      'background-color': '#F2FF7F', 'font-size': 'large',
      'padding': '15px'});

Note: This code will hopefully make sense to those familiar with JavaScript and jQuery, so please feel free to skip ahead to the next section.

Handling application state in the document.ready event

Checking whether we are logged in

What you see in the snippet above is just a single function that is executed when the document (underlying web page) has been loaded. Inside this function, we first check whether the value of the loggedIn variable has been set. Remember that this variable is created in the PHP layer at line 209 and then plugged into the HTML page at line 224.

If the user is logged in, we use some jQuery magic to show the #options <div>. This is a CSS element that contains the basic menu where a user can select to view user or friend activity feeds and also retrieve activities for specific usernames. In case you are wondering about the odd syntax here, the dollar sign $ can be used to refer to the jQuery object itself. This is done mostly to eliminate extraneous typing. We can then specify CSS selectors directly, such as the #options <div>, and set properties dynamically. Here we are just setting the display property to block. If you notice, this <div> element is initially hidden in our CSS layer by setting the display property to none. So if our user is logged in, we just unhide it.

Dispatching an AJAX request if we don't have a username

Now we check whether the PHP layer has provided us with some data for the authenticatedUsername variable (similar to loggedIn mentioned earlier). Again, if we have some information, we select the <div> element that displays our login information, identified here by the ytActivityApp.USER_LOGIN_DIV constant. We then use jQuery's html function to insert some HTML into this <div> element. In this case, we are displaying the YouTube username for the logged-in user and providing an option to log out. If we don't have a username, we dispatch an asynchronous call to get the current username. jQuery makes this very easy for us by providing a get function. We provide three basic parameters:

  1. the URI that we are making this request to — in our case, this is what we have defined with the ytActivityApp.URI constant
  2. the HTTP query parameters — in our case, we are sending a hash containing { q: "whoami" }, since we want to find the YouTube username
  3. the name of the function to call once the request has been completed — in this case we are defining an anonymous function inline

The inline function only has a data argument, which will contain the username, as returned from the PHP backend. We strip out the leading and trailing double quotes and store this value in the ytActivityApp.MY_USERNAME variable for later use. We then use jQuery to update the <div> element identified by the ytActivityApp.USER_LOGIN_DIV id, and insert some HTML showing the current username and an option to log out.

Dispatching further calls to either activity or friend activity feeds

Now that we have handled authentication, we can concentrate on making requests to the API via the PHP backend. First, we want to show a "Loading ..." status message while we are making more asynchronous requests, so we insert some HTML into the #status <div> element. Then we check whether a user activity feed (default) or friend activity feed was requested. Based on the selection, we then just dispatch a call to either ytActivityApp.getActivityFeed() or to ytActivityApp.getFriendActivityFeed(). Both functions are described in detail in the following sections.

Retrieving a YouTube API activity feed

The code to handle a user activity feed or friend activity feed is actually pretty simple. Let's look at the getActivityFeed function:

ytActivityApp.getActivityFeed = function(username) {
  if (loggedIn == true) {
    var form = document.getElementById(ytActivityApp.FORM_RADIO_SELECTION_ID);

    // Fetch activity for a specific user
    if (username) {
      ytActivityApp.CURRENT_USERNAME = username;
      form[[]0].checked = false;
      form[[]1].checked = false;
      form[[]2].checked = true;
      $('#users_string_input').attr({ value: username});
      $.getJSON(ytActivityApp.URI, { q: "userfeed", who: username },
    } else {
      // Fetch activity for the currently authenticated user
      ytActivityApp.CURRENT_USERNAME = ytActivityApp.MY_USERNAME;
      form[[]0].checked = true;
      form[[]1].checked = false;
      form[[]2].checked = false;
      $('#users_string_input').attr({ value: ''});
      $.getJSON(ytActivityApp.URI, { q: "userfeed" },

In this function, we again check whether the user is logged in by examining the loggedIn variable provided to us in the PHP layer. We show the "Loading..." status message while we are requesting a feed. If a specific username has been requested either by clicking on a username in our own activity feed, or through the form at the top of the page, we pass that as the value of the who query parameter to the application backend. For a consistent user experience, note that we also automatically populate the search form with the value of username and set the radio selection to Activity for user(s) (using form[[]2].checked = true;).

The actual request for the activity feed data is handled with another jQuery function, this time we are using getJSON. The function signature is the same as for the get function. Again we just pass in the URI that we are making a request to, some parameters (in this case either { q: "userfeed", who: username } if we have a username or { q: "userfeed" } if we don't). The third parameter is the callback that we want to be executed once data has been received. This is the ytActivityApp.processJSON() function, which is explained in the next section.

Processing the JSON response into HTML

The processJSON() function consumes the bulk of the javascript file but it's not a very complex function. There are just a good number of cases that we need to handle. In essence, all that we are doing here is transforming the JSON response that is returned from our PHP layer into HTML. We operate primarily on the data variable, which represents the JSON response. Since this function is rather long (250+ lines), we will cover the salient details in separate chunks.

Ensuring that we have results

The first part of the function just ensures that data actually contains some information. If the contents are 'SERVER_ERROR', then the API is unavailable (which is improbable but we should still handle it):

ytActivityApp.processJSON = function(data) {
  // If the API is unavailable, display a message and return
  if (data == 'SERVER_ERROR') {
    $('#' + ytActivityApp.FEED_RESULTS_DIV).html(
      '<span class="' + ytActivityApp.CSS_API_NOT_AVAILABLE_CLASSNAME +
      '">' + ytActivityApp.API_NOT_AVAILABLE_MESSAGE + '</span><br />');

In the above snippet, we just check a condition and then write some HTML into a <div> element identified by ytActivityApp.FEED_RESULTS_DIV. We also hide the "Loading..." status message.

Next we handle the case of no results being available, which can happen if no activity information is found for a particular user. We simply inspect the data element and display a message if no contents are found:

// If there are no results, display a message and return.
if (data.length < 1) {
  $('#' + ytActivityApp.FEED_RESULTS_DIV).html(
    '<span class="' +
    '">No activity found for this user.</span><br />' + 
    '<a class="username_link" ' + 
    'onclick="ytActivityApp.getActivityFeed()">' + 
    'Click to reload your own activity.</a>');

Iterating through the results

Now that we have gotten that out of the way, we can iterate throught the results in our JSON object and process them. Outside of our main loop we set up a variable called video_number which will help us when we use ThickBox to display the video overlay on our page.

// Keep track of each video number for the embedded player
var video_number = 0;
for (var i = 0; i < data.length; i++) {
  var entry = data[[]i];
  // An array that will contain the HTML for this activity entry
  var HTML_string = [[]];
  // If there is a date, decode it using date_magic.js
  if(entry.updated) {
    var updated = new Date();
  else {
    var updated = ytActivityApp.METADATA_UPDATED_TS_NOT_FOUND;
  // function continues ...

Notice that we are using the HTML_string array to build the HTML for each individual activity entry. After the activity entry is processed, we use jQuery's append function to add the HTML to the <div> element which contains the results.

The remaining code handles checking and retrieving metadata from our entry object, which may or may not contain video metadata based on the type of activity that was performed. (Social activities, such as 'adding a friend', don't contain videos, therefore no metadata.)

Note: Note that we have left the task of adding user profile data for non-video activities as an exercise for the reader.

// Switch on the activity type, setting the english string and also
// checking whether it involves a video.
switch(activity_type) {
  case 'video_rated':
    english_string = ' has rated a video';
    is_video_activity = true;
    // function continues ...

The snippet above shows the beginning of our simple switch statement in which we determine whether to look for video metadata later by setting the is_video_activity variable.

We then perform various checks on the type of feed being requested to determine whether to make certain elements clickable. The snippet below shows a check on whether to make the current user's name (represented by clickable.

// If the feed is an activity feed, then don't make the current user's
// name clickable since a click would only direct them to what they are 
// currently looking at.
if (ytActivityApp.FEED_REQUESTED != ytActivityApp.FRIEND_ACTIVITY_FEED) {
  HTML_string.push('<span class="' +
    ytActivityApp.CSS_ENTRY_MY_USERNAME_LINK_CLASSNAME + '">' + + '</span> ');
} else {
  HTML_string.push('<a class="' +
    '" href="#" onclick="ytActivityApp.getActivityFeed(\'' + +
    '\')">' + + '</a> ');

In the next section, we process video metadata if it is available. We first examine the value of the is_video_activity variable. Then we perform a number of checks for video metadata, substituting a constant if the results were not found as seen in the check for uploader below:

// If it is a video activity, process the metadata, if it is available...
if (is_video_activity) {
  if ((entry.video_info) && (entry.video_info != 'NOT_AVAILABLE')) {

    // Initialize all the video_info properties to defaults if not found
    var uploader = entry.video_info.uploader ||

The next part checks whether a player_url is found, which contains the link to the YouTube embedded player. We also note that a 'playable' video has been added to the page with the added_video variable.

// Check if we also have a URL for the SWF player, which also
// indicates that the video is embeddable.
if(player_url) {
  player_url = player_url + '&autoplay=1';
  added_video = true;

In cases where the video is embeddable we add a link to both the thumbnail image and the video title with the id set to play_video_number_<NUMBER>, where NUMBER is set the to the value of the video_number variable. In the few cases where a user has specifically disabled embedding, we just create a link to the YouTube watch page for that video:

// If the video is embeddable, prepare it to be played with ThickBox
if(added_video) {    
  HTML_string.push('<a id="play_video_number_' + video_number +
    '" href="#">');
} else {
  // Video is not embeddable, so prepare a link to the
  // video watchpage
  HTML_string.push('<a class="play_on_youtube" target="_blank" ' +
    'href="' + ytActivityApp.YOUTUBE_VIDEO_URL + id + '" title="' +
    ytActivityApp.EMBEDDING_DISABLED_MESSAGE + '">');

The snippet below shows how to display the events that don't contain video metadata:

} else {
  // This was not an activity that involved a video
  if (activity_type == 'friend_added') {
    HTML_string.push(' <a href="#" class="' +
      '" onclick="ytActivityApp.getActivityFeed(\'' + entry.username +
      '\')">' + entry.username + '</a> as a friend');

Almost done! The next step is to render the HTML_string, which we build for each activity entry into the page. Again we use the jQuery append method.

  // Write the activity entry to the DOM
  $('#' +   ytActivityApp.FEED_RESULTS_DIV).append(HTML_string.join('')).show("slow");
  // here we end the loop

// Done processing all the events, render the HTML and hide the
// 'loading' status <div>
$('#' + ytActivityApp.FEED_RESULTS_DIV).append('</ul></div>').show("slow")

Now on to the last step on how to display videos in ThickBox.

Displaying embeddable videos with ThickBox

To display the YouTube player for a video in this sample, ThickBox was used to show a floating <div> on top of the page content. In addition, a hidden link was used in the page HTML to activate ThickBox from within JavaScript:

<a id="play_video" href="#TB_inline?height=366&width=425&inlineId=videobox" class="thickbox"></a>
<!-- hidden <div> to render the embedded player -->
<div id="videobox"></div>

The JavaScript to display the video takes a SWF URL supplied by the API as a parameter. It then loads the embedded player into the hidden videobox <div> element using SWFObject. The click handlers shown below open ThickBox when a user clicks on the play_video link. The following code is added at the very end of the processJSON() function:

// If it was a video activity and the video is embeddable, create handlers
if(added_video) {
  $("#play_video_number_" + video_number).click(
    ytActivityApp.displayMovie(player_url, title));
  $("#t_play_video_number_" + video_number).click(
    ytActivityApp.displayMovie(player_url, title));
  video_number = video_number + 1;

As you can see, we are checking whether the added_video variable is true, and add some jQuery click handlers to the page JavaScript. As you can see, we call the displayMovie() function if either thumbnail or title are clicked:

ytActivityApp.displayMovie = function(swfUrl, videoTitle) {
  return function() {
    // Write the <div> that the swf will be rendered to into our hidden <div>
    $("#videobox").html('<div id="ytapiplayer"></div>');

    // Set parameters
    var params = { allowScriptAccess: "always" };

    // Use swfObject to render a standard YouTube player
    swfobject.embedSWF(swfUrl, "ytapiplayer", "425", "356", "8", null, null, params);

    // Prepare a link that will be clicked to play the video
    $("#play_video").attr('title', videoTitle);

    // Auto play the video

    // Destroy the player on close
    $("#TB_window").bind('unload',    ytActivityApp.clearVideoBox);

As described earlier, the function takes the URL to the SWF that we are looking to display as well as the video's title. We render the <div> element with an id of ytapiplayer into our hidden #videobox <div> element, set some parameters and then use swfobject to embed the SWF itself. We also use the Javascript Player API to automatically play the video.

To remove the player from our hidden <div> element when it is closed, we bind the clearVideoBox function to the unload event of the ThickBox window:

ytActivityApp.clearVideoBox = function() {

That sums up the basics of how the frontend works. The section below covers how to handle form events.

Handling form input

Now that we have the core functionality working, it's easy to process form events. In our HTML, we have added a handler to the switchFeedURI function once a selection has been made. This function simply checks which radio button has been selected and dispatches a call to fetch either the current user's activity feed, the user's friend activity feed or the activity feed:

ytActivityApp.switchFeedURI = function() {
  var form = document.getElementById(ytActivityApp.FORM_RADIO_SELECTION_ID);
  var selected_button = 0;
  for(i = 0; i < form.length; i++) {
    input = form[[]i];
    if (input.checked) {
      selected_button = i;
  // Check whether we are looking for user activity or friend activity
  // Set the FEED_REQUESTED variable to make decisions later when the HTML
  // is rendered.
  if (selected_button == 0) {
    ytActivityApp.FEED_REQUESTED = ytActivityApp.USER_ACTIVITY_FEED;
  } else if (selected_button == 1) {
    ytActivityApp.FEED_REQUESTED = ytActivityApp.FRIEND_ACTIVITY_FEED;
  } else if (selected_button == 2) {
    // Add a message to the form
    $('#users_string_input').attr({ value: 'Enter usernames...'});

Note: In a production application you would not want to attach this event to the HTML directly but instead follow some guidelines to keep your JavaScript unobtrusive.

If usernames were entered into the search form, we do a little bit of validation to this data and make sure that less than 20 (the current maximum) were submitted. We also check whether the value in the form is a valid YouTube username:

ytActivityApp.cleanFormInputAndRequestActivityFeed = function(usernames) {
  // Return if nothing was submitted
  if ((usernames == null) || (usernames == '')) {
    $('#status').show().html('Oops! Looks like no usernames were entered ...')
      .css('color', 'red');
  // Split usernames by comma and count
  var usernameArray = usernames.split(',');
  var numUsernames = usernameArray.length;
  var validUsernames = [[]];
  if (numUsernames > 20) {
    $('#status').show().html('Hey! You submitted more than 20 usernames, ' + 
      'please try again. A maximum of 20 usernames is supported.')
      .css('color', 'red');
  for (i = 0; i < numUsernames; i++) {
    // Use jQuery's helpful trim() function to remove whitespace
    var username = $.trim(usernameArray[[]i]);
    // Check against illegal (non-alphanumeric) characters.
    var illegalCharacters = /[[]^a-zA-Z0-9]/;
    if (illegalCharacters.test(username)) {
      $('#status').show().html('Ouch! <strong>' + username +
        '</strong> is not a valid YT username! Please try again.')
        .css('color', 'red');
    } else {
      // Collect valid usernames
  var validUsernamesString = validUsernames.join(',');
  ytActivityApp.CURRENT_USERNAME = validUsernamesString;


That sums up our short tutorial on how to construct a PHP and JavaScript based application that displays YouTube API activity feeds. As you have seen it's easy to write a backend using our PHP client library. Using jQuery can help speed up frontend development. To play with the finished application, visit The full source code is also available for download. If you run into problems with this article, check out the documentation or visit us in the YouTube API Developer Forum.

Happy Coding!