<?xml version="1.0" encoding="UTF-8" ?>
<Module>
  <ModulePrefs title="Updates V8" height="1000">
    <Require feature="dynamic-height" />
    <Require feature="views" />
    <Require feature="opensocial-0.8" />
    <Require feature="setprefs" />
  </ModulePrefs>
  <UserPref name="debugMode" datatype="hidden" default_value="true" />
  <Content type="html" view="home, canvas">
  <![CDATA[
<style>
body{font-size:80%;font-family:Arial,sans-serif;margin:0;padding:0;line-height:130%}
#filter{float:right}
h1{font-size:1.5em;font-weight:normal}
h1 a{font-size:.7em;margin-left:.3em;white-space:nowrap;}
#filter .strong{font-weight:bold;color:#000}
#loading{margin:10% auto}
.update{border-bottom:1px solid #C5D7EF;margin:.7em 0;padding:0 0 .7em 42px}
.update .nolink{color:#000}
.update .thumb{margin:0 5px 0 -42px;vertical-align:text-top;float:left}
.update .name{font-style:normal;font-weight:bold}
.update-media img{margin:.3em;border:1px solid #999;padding:3px}
.timestamp{float:right;color:#999}
#pagination{background:#ccf;padding:5px;-moz-border-radius:5px;border-radius:5px}
#pagination #more{float:right}
</style>
<a name=top></a>
<div id=filter>
  <a href=# id=activities>All updates</a> -
  <a href=# id=myActivities>My updates</a>
</div>
<h1 id=title>Updates from Friends 
  <a href="http://www.google.com/support/websearch/bin/answer.py?answer=151672"
     target=_blank>
    Learn more
  </a>
</h1>
<div id=updates>
  <div class=update style="display:none">
    <img class=thumb width=32px height=32px>
    <span class=name>Name</span>
    <a href=# class=update-title>update title</a>
    <div class=update-media></div>
    <div class=update-body>
    Body of text. Body of text. Body of text. Body of text. Body of text.
    Body of text. Body of text. Body of text. Body of text. Body of text.
    Body of text. Body of text. Body of text. Body of text. Body of text.
    </div>
    <div class=timestamp>5:11am (a while ago)</div>
    <a class=source-url>&nbsp;</a>
  </div>
</div>
<h1 id=noUpdatesTitle style="display:none">No updates right now</h1>
<div id=noUpdatesText style="display:none">
  Updates are short messages that notify you when someone shares something with
  you through a gadget&mdash;for example, photos.
  <a href="http://www.google.com/support/websearch/bin/answer.py?answer=151672"
     target=_blank>
    Learn more
  </a>
</div>
<div id=pagination style="display:none">
  <a href=# id=more>More</a>
  <a href=#top>Back to Top</a>
</div>
<script type="text/javascript"
        src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js">
</script>
<script>(function(){
//http://code.google.com/apis/opensocial/docs/0.8/reference/
//http://wiki.opensocial.org/index.php?title=Requesting_Data_in_OpenSocial
//http://wiki.opensocial.org/index.php?title=Articles_%26_Tutorials
  /*
   * A data structure that allows a much easier understanding of what
   * is requested from the opensocial server.
   */
  var query = {
    viewer:{
      id: opensocial.IdSpec.PersonId.VIEWER,
      method: 'newFetchPersonRequest',
      accessors: { name:'displayName', id:'id', thumb: 'thumbnailUrl'}
    },
    friends:{
      id: opensocial.newIdSpec({userId:'VIEWER', groupId:'FRIENDS'}),
      method: 'newFetchPeopleRequest',
      params: (function(){
        var params = {};
        params[opensocial.DataRequest.PeopleRequestFields.MAX] = 100;
        // params[opensocial.DataRequest.PeopleRequestFields.FILTER] =
        //   opensocial.DataRequest.FilterType.HAS_APP;
        // params[opensocial.DataRequest.PeopleRequestFields.SORT_ORDER] =
        //   opensocial.DataRequest.SortOrder.NAME;
        return params;
      })(),
      accessors: { name:'displayName', id:'id', thumb: 'thumbnailUrl'}
    },
    myActivities:{
      id: opensocial.newIdSpec({userId:'OWNER', groupId:'SELF'}),
      method: 'newFetchActivitiesRequest'
    },
    activities:{
      id: opensocial.newIdSpec({userId:'OWNER', groupId:'FRIENDS'}),
      method: 'newFetchActivitiesRequest'
    }
  };
/*
 * Executes the request to the opensocial server.
 */
_IG_RegisterOnloadHandler(function load(){
  try {
    var req = opensocial.newDataRequest();
    for (var item in query) {
      req.add(req[query[item].method](query[item].id, query[item].params), item);
    }
    req.send(function(responseData){
      var updates = transformData(responseData);
      render(updates);
    });
  } catch (e) {
     $('#updates, #title, #filter').hide();
     $('#noUpdatesTitle, #noUpdatesText').show();
  }
});
/*
 * Callback to opensocial.DataRequest.send() which extracts a more useable
 * object from the response.
 * @param {Object} data The response object as passed by the request to
 *   the opensocial server.
 */
function transformData(data){
  var updates = {};
  for(var spec in query) {
    var specData = data.get(spec).getData();
    if (!specData) { continue; }
    if ('each' in specData) {
      updates[spec] = [];
      specData.each(function(item){
        updates[spec].push(getProperties(spec, item));
      });
    } else {
      updates[spec] = getProperties(spec, specData);
    }
  }
  /*
   * Inner function to retrieve properties for people or activities.
   */
  function getProperties(spec, data){
    if ('toJsonObject' in data){
      return data.toJsonObject();
    } else {
      var update = {};
      for (var accessor in query[spec].accessors) {
        update[accessor] = data.getField(query[spec].accessors[accessor]);
      }
      return update;
    }
  }
  /*
   * Inner function; Indexes an object by one of its member's properties.
   * In particular, the friends object is indexed by id in this case.
   */
  function transpose(object, property){
    var transposed = {};
    for(var p in object){
      transposed[object[p][property]] = object[p];
    }
    return transposed;
  }
  // remove other users from myActivities (apparently "SELF" doesn't work)
  updates.myActivities = $.map(updates.activities, function(activity){
    return activity.userId == updates.viewer.id ? activity : null;
  });
  updates.friends = transpose(updates.friends, 'id');
  updates.friends[updates.viewer.id] = updates.viewer;
  return updates;
}
/*
 * Prettifies the timestamp into human-readable text.
 */
function formatTime(timestamp){
  // Converting from microseconds to milliseconds:
  var then = Number(new Date().setTime(timestamp / 1000));
  // Seconds since update
  var since = Math.floor(Number(new Date) - then) / 1000;
  var time = (new Date(then)).toLocaleTimeString().replace(/:\d\d /, ' ');
  // Recent activity, today
  if (since < 60)
    return 'Moments ago';
  if (since < 120)
    return time + ' (a minute ago)';
  if (since < 3600)
    return time + ' (' +Math.floor(since / 60) + ' minutes ago)';
  if (since < 7200)
    return time + ' (an hour ago)';
  if (since < 86400)
    return time + ' (' + Math.floor(since / 3600) + ' hours ago)';
  // Before today
  since = Math.floor(since / 86400);
  if (since == 1) return time + ' Yesterday';
  if (since < 7) return since + ' days ago';
  // A long time ago...
  var date = (new Date(then)).toDateString();
  var thisYear = (new Date).getFullYear();
  if (new Date(then).getFullYear() == thisYear)
    date = date.replace(thisYear, '');
  return date;
}
/*
* Inserts the data from updates into the HTML template.
* @param {Object} updates The data containing activity updates and users.
* @param {number} offset The starting page for rendering the list of updates.
*/
function render(updates, offset){
  var pageSize = 10;
  offset = offset || 0;
  $('#pagination').attr('offset', offset);
  if (updates) {
    // Initial render (full)
    arguments.callee.updates = updates;
  }
  else {
    // Partial render (pagination)
    updates = arguments.callee.updates;
  }
  var activities = updates[filter()];
  
  $('#updates, #title, #filter')[activities.length ? 'show': 'hide']();
  $('#noUpdatesTitle, #noUpdatesText')[activities.length ? 'hide': 'show']();
  if (!activities.length){ 
    _IG_AdjustIFrameHeight();
    return;
  }
  
  if (!arguments.callee.master) {
    arguments.callee.master = $('#updates').html();
  }
  var master = arguments.callee.master;
  if (!offset){ $('#updates').html(''); }
  var len = Math.min(activities.length, offset + pageSize);
  for (var i = offset; i < len; i ++){
    var update = activities[i];
    var user = updates.friends[update.userId];
    var template = $(master).clone();
    $(template)
      .find('img')
      .attr('src',
            user.thumb || 'http://www.google.com/ig/images/setup/silhouette.png');
    $(template).find('.name').html(user.name);
    $(template).find('.update-title')
      [update.url ? 'removeClass' : 'addClass']('nolink')
      [update.url ? 'attr' : 'removeAttr']('href', update.url)
      .attr('target', '_blank')
      .html(update.title);
    $(template).find('.update-body').html(update.body);
    if (update.mediaItems.length){
      for(var j = 0, media; j < 3 && (media = update.mediaItems[j]); j++){
        if(!media.url) continue;
        var mediaHtml = $({
          image: '<img>',
          video: '<embed>',
          audio: '<embed>'
       }[media.type]).attr({src: media.url, autostart: false});
        $(template).find('.update-media').append(mediaHtml);
      }
    }
    $(template).find('.timestamp').html(formatTime(update.postedTime));
    $('#updates').append(template);
    $(template).show();
  }
  if (activities.length > offset + pageSize){
    $('#pagination').show();
    $('#more').show().bind('click', more);
  } else { $('#more').hide(); }
  _IG_AdjustIFrameHeight();
 }
 /*
 * Pagination control.
 */
 function more(){
  var pageSize = 10;
  var container = $('#pagination');
  var offset = Number(container.attr('offset')) || pageSize;
  render(null, offset);
  container.attr('offset', offset + pageSize);
  container.show();
  return false;
 }
 /*
  * Filter controls
  * @param {boolean} toggle Whether or not to switch the current filter.
  */
  function filter(toggle){
    var container = $('#filter');
    var currentFilter = container.attr('value') || 'activities';
    $('#' + currentFilter)
      .removeAttr('href')
      .unbind()
     .addClass('strong nolink');
    var newFilter =
      currentFilter == 'activities' ? 'myActivities' : 'activities';
    $('#' + newFilter)
      .attr('href', '#')
      .bind('click', function(){
         container.attr('value', newFilter);
         render(null, 0);
       })
     .removeClass('strong nolink');
    container.attr('value', toggle ? newFilter : currentFilter);
    return toggle ? newFilter : currentFilter;
  }
})();
</script>
  ]]>
  </Content>
</Module>
