Google TV

Using Google TV jQuery UI Library Controls

Steven Hines, Google TV Developer Relations
January 2011

The Google TV jQuery UI Library provides a keyboard navigation controller as well as a variety of custom controls to simplify the task of developing for Google TV. The following article walks through creating a page to display photos from a Picasa photo feed, including a pop-up thumbnail display.

You can jump to any of the sections in this article using this table of contents:

  1. Create a RowControl
  2. Hook in a Picasa Feed
  3. Show the Photo
  4. Add a SideNav Menu

Scrolling Row

To start off, we'll create a scrolling row that will be used to display thumbnails from the photo feed. For the moment, we will just put a number in each item to make the example easier to read.

The RowControl is simplest control to use. It requires only a collection of items, a few CSS classes, and a KeyController object. The following code creates a row control with 50 elements with their index as the contents:

As a preliminary step, make sure we have the CSS styles we'll need and all the JavaScript files necessary:

<link rel="stylesheet" href="../source/css/controls.css" />

<style>
body {
  overflow: hidden;
}
.scroll-row-style {
  overflow: hidden;
  width: 99%;
  background-color: #8bd;
  border: 6px solid #999;
  position: relative;
}
.scroll-div-style {
  margin: 5px;
}
.scroll-items-div-style {
}
.scroll-item-style {
  background-color: #58b;
  border-radius: 10px;
  color: #ddd;
  padding: 10px;
  font-family: sans-serif;
  font-size: 20pt;
  border: #58b solid 6px;
}
.item-hover {
  border: #dd0 solid 6px !important;
}
.photo {
  position: absolute;
  left: 0;
  top: 0;
}
</style>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript" src="../source/js/scrollrow.js"></script>
<script type="text/javascript" src="../source/js/gtvcore.js"></script>
<script type="text/javascript" src="../source/js/keycontrol.js"></script>

Now to create our page content. As a first early step, let's try the control library with a basic array of 50 elements and make the text the index number:

var items = [];
for (var i = 0; i < 50; i++) {
  var item = $('<p></p>').text(i.toString());
  items.push(item);
}

Next, we create a styles object to provide styles for the various components of the row:

var styles = {
  row: 'scroll-row-style',
  itemsDiv: 'scroll-items-div-style',
  itemDiv: 'scroll-div-style',
  item: 'scroll-item-style',
  hover: 'item-hover'
};

Finally, we create a Row Control object, add the row to the parent, and enable key navigation for it:

var keyController = new gtv.jq.KeyController();
keyController.start();

var rowControl = new gtv.jq.RowControl({
  containerId: 'row-container',
  styles: styles,
  keyController: keyController
});

rowControl.showControl({
  topParent: topParent,
  contents: {
    items: items
  }
});

rowControl.enableNavigation(keyController);

See how it looks

Check out a working demo of the code so far.

Feed Processing

The GTV library contains a convenience routine for processing JSON/JSONP Feeds. Use this to quickly create a RowControl with contents from a feed.

The following code uses thumbnails from the recent photo feed from Picassa for the RowControl contents.

First, define the feed we’re going to be reading from:

var FEED_URL = 'http://picasaweb.google.com/data/feed/base/featured?' +
               'alt=json-in-script&kind=photo&access=public&' +
               'slabel=featured&hl=en_US&max-results=25';

Next, create a makeItem callback function. This will be called for each entry in the feed (with that entry as a parameter). It should return either a new element created for that entry, or null if entry should be skipped. In the callback below, we create a an <img> element to display a thumbnail (for each entry that has a thumbnail):

function makeItem(entry) {
  var thumb = entry.media$group.media$thumbnail[0].url;
  if (!thumb) {
    return null;
  }

  var item = $('<img></img>');
  item.css({
    height: '150px',
    display: 'block'
  })
  item.attr('src', thumb);

  return item;
}

Below is the code used in the first part to create the rows, but this time it is placed in a callback function. This callback receives an array of the elements that were created in makeItem. In this case, we want to create a row of elements from that array (which are thumbnail images):

var rowControl;
function makeRow(items) {
  var keyController = new gtv.jq.KeyController();
  keyController.start();

  var photo = $('')
    .addClass('photo')
    .css('height', $(window).height());
  topParent.append(photo);

  var styles = {
    row: 'scroll-row-style',
    itemsDiv: 'scroll-items-div-style',
    itemDiv: 'scroll-div-style',
    item: 'scroll-item-style',
    hover: 'item-hover'
  };

  var rowControl = new gtv.jq.RowControl({
    containerId: 'row-container',
    styles: styles,
    keyController: keyController
  });

  rowControl.showControl({
    topParent: topParent,
    contents: {
      items: items
    }
  });

  rowControl.enableNavigation(keyController);
}

The last part of the code is the first part that runs. The code below calls the utility function that will request the feed data from FEED_URL. It finds the array of entries using the array in the last parameter, passes each entry in turn to makeItem, and then the full array to makeRow:

gtv.jq.GtvCore.processJsonpFeed(
  FEED_URL,
  makeItem,
  makeRow,
  ['feed','entry']);

Row Item Selection

Now that we have a page with thumbnails from a Picasa feed in a row control, the next step is to do something when the user selects one of the thumbnails, such as display the full-size photo that the thumbnail represents.

But we also can present the row of photos in a more user-friendly way. Instead of having the row show up on the screen permanently, we can put it inside a SideNav Control, and have that control pop-up when selected and display the row of thumbnails.

Showing the Photo

For the first step, showing the full-size photo, we have to store the URL to the photo with the item we create to display the thumbnail. jQuery provides a $.data() method to do this, so for convenience we’ll use that. Add this code to makeItem():

var content = entry.media$group.media$content[0].url;
item.data('url', content);

The Row Control allows us to supply a callback to be called when the user clicks or presses the ENTER key when an item is highlighted. We can use this to display the photo on the page.

function choiceCallback(item) {
  // the img we created was placed inside a container
  // by Row Control
  var image = item.children.first();
  var url = image.data('url');

  photo.attr('src', url);
}

See how it looks

Check out a working demo of the code so far.

Creating the SideNav

First, we need to add a few more CSS classes for the SideNav control:

.sidenav-item-div-style {
  overflow: hidden;
}
.sidenav-item-style {
  margin: 0;
  padding: 0;
}
.sidenav-holder {
  position: absolute;
  top: 0;
  left: 0;
}
.sidenav-row-holder {
  position: relative;
}

addNavItem Callback

For the next step, creating the SideNav Control to hold the Row Control, we have to make a minor structural change to the code. When the SideNav Control is created, it can accept a function to create and add a new item as well as an array of items. In this case, we’re going to use the callback method to add our item, which in this case is will be a Row Control.

The callback receives a parent object as a parameter. The function should create and add a new element to the parent object and return true, or return false if there are no more items to add.

The code that actually creates the Row Control in the callback is largely the same.

The SideNav Control

The SideNav Control offers several menu behaviors and orientations. They can be static on the page; pop out from the top, bottom, left or right; or fade in and out of the page. In this case, we want to have the menu pop out from the bottom of the page, and be horizontally oriented.

var behaviors = {
  popOut: 'bottom',
  orientation: 'horizontal'
};

Now we create a container for the SideNav control:

var sidenavHolder = $('<div></div>').addClass('sidenav-holder');
topParent.append(sidenavHolder);

Then we create the control with the usual parameters:

var styles = {
  item: 'sidenav-item-style',
  itemDiv: 'sidenav-item-div-style',
  row: 'sidenav-row-style',
  chosen: 'sidenav-item-chosen',
  normal: 'sidenav-item-normal',
  selected: 'sidenav-item-hover'
};

sidenavControl = new gtv.jq.SideNavControl({
   createParams: {
     containerId: 'scrollnav',
     styles: styles,
     keyController: keyController
   },
   behaviors: behaviors
 });

scrollnavControl.showControl({
  topParent: sidenavHolder,
  contents: {
    itemsGenerator: addNavItem
  }
});

(The SideNav Control defines a couple additional style classes used to distinguish the item that is chosen from the others (since its a menu, it provides a means, as an option, to visually track what selection the user has made). But since the SideNav is just housing another control in this example, these don’t apply, so we don’t need to provide them.)

Since we want to navigate from the photo to the SideNav and back, let's put the photo in a container add it to a zone:

var photoContainer = $('<div></div>').attr('id', 'photo-container');
topParent.append(photoContainer);

var photo = $('<img></img>')
  .addClass('photo')
  .css('height', $(window).height());
photoContainer.append(photo);

var photoZone = new gtv.jq.KeyBehaviorZone({
    containerSelector: '#photo-container',
    navSelectors: {
      item: '.photo'
    }
  });
keyController.addBehaviorZone(photoZone);

See how it looks

Check out a working demo of the full code.

Authentication required

You need to be signed in with Google+ to do that.

Signing you in...

Google Developers needs your permission to do that.