/*
 * UDWebEventFeed
 *
 * An AJAX-driven RSS feed fetcher for use with WebEvent.
 *
 * Copyright (c) 2009
 * Jeffrey T Frey
 * Network & Systems Services, University of Delaware
 *
 * $Id: UDWebEventFeed.js,v 1.4 2009/10/14 16:04:28 frey Exp frey $
 *
 */

/*!
  @class UDWebEventFeed
  
  Instances of the UDWebEventFeed Javascript class use an AJAX agent to
  asynchronously fetch a WebEvent RSS feed from a caching proxy mechanism
  that must be available on the server from which your page is being
  served.  AJAX does not allow a page to retrieve content from any server
  except the one from which the page was served, so we can't directly
  retrieve the feed from webevent.nss.udel.edu -- hence, the need for a
  proxy agent.
  
  An instance should be created using the "new" operator:
  
    var     myFeed = new UDWebEventFeed('1021');
    
  where the single argument to the constructor function is the desired
  calendar identifier (calId).  In the background, a UDAJAXAgent will
  then attempt to retrieve the XML feed from the server.  When the data
  is retrieved, the instance will immediately parse the WebEvent RSS
  content.
  
  It is also permissible to pass two additional arguments to the
  constructor function:
  
    var     myFeed = new UDWebEventFeed(
                            '1021',       // calId
                            3,            // maximum number of events (default 3)
                            myDiv         // element in which to display the data
                          );
  
  As far as displaying the content goes, that third argument hints at what the
  class can do.  Displaying the feed is most easily accomplished by associating a
  display element (a <div> for example) with the UDWebEventFeed object, either via
  the constructor (second example above) or using the setElementForBasicHTMLOutput()
  member function:
  
    myFeed.setElementForBasicHTMLOutput( document.getElementById('myFeedDiv') );
    
  UDWebEventFeed will construct a simple HTML content area with the data it
  fetched and set the display element's "innerHTML" to that HTML.  The HTML
  generated consists of <div> elements with distinct CSS class names:
  
    <div class="UDWebEventFeed">
      <div class="UDWebEventFeedTitle">
        <a href="[link to calendar]">[calendar description]</a>
      </div>
      <div class="UDWebEventFeedItems">
        <div class="UDWebEventFeedItem">
          <div class="UDWebEventFeedItemTitle">
            <a href="[link to event]">[event description]</a>
          </div>
          <div class="UDWebEventFeedItemDate">[event date]</div>
          <div class="UDWebEventFeedItemTime">[event time]</div>
          <div class="UDWebEventFeedItemDescription">[event description]</div>
        </div>
          :
      </div>
    </div>
  
  The anchor tags are only added when URL linkage is actually available in the
  RSS feed (though this seems to be always based on the feeds I've looked at).
  The event time and description fields are not always present in the RSS feeds,
  but their corresponding <div> elements are _always_ added (even when empty)
  in case the CSS requires them as a space-filler for proper page layout.
  
  You are also free to modify the manner by which the feed is displayed by
  overriding the object's updateDisplay() member function with one you've
  written yourself.  Take a look at the _generateBasicHTML() member function
  for an example of how to access the parsed data in your own display
  function.
  
  The "retrieveFeed()" member function can be invoked to re-fetch and redisplay
  the feed, either via an on-page UI control or periodically by means of a
  Javascript timer.
  
  -----------------------------------------------------------------------------
  Notes on changing the presentation of the feed summary:
  -----------------------------------------------------------------------------
  
  One may think that in order to "hide" a particular piece of the generated
  HTML feed summary this source needs to be modified to simply not emit the
  HTML in question.  However, this implies that you need to make your own
  copy of this source and maintain it parallel to the copy available in
  /www/htdocs/modules.
  
  Instead, leverage CSS!  All of the generated HTML has distinct CSS classes
  attached to it, so find the CSS class of the <div> you wish to hide and
  somewhere after you include the base CSS for this module, override that
  class with a "display:none" property, for example.  The "cascading" part
  of the acronym CSS means that later CSS properties will override those which
  were defined "earlier" on the page.
*/
function UDWebEventFeed(
  calId,
  eventLimit,
  htmlElement
)
{
  this.init(calId, eventLimit, htmlElement);
}

UDWebEventFeed.prototype = {

  init:function(
    calId,
    eventLimit,
    htmlElement
  )
  {
    this._calId = calId;
    this._eventLimit = ( eventLimit != null ? ( eventLimit <= 0 ? 0 : eventLimit ) : 3 );
    this._basicHTMLElement = htmlElement;
    this._ajaxAgent = UDSharedAJAXAgent();
    this.retrieveFeed();
  },
  
  retrieveFeed:function()
  {
    var   uri = '/rss/webevent/' + this._calId;
    
    if ( this._ajaxAgent ) {
      this._populating = true;
      this._ajaxAgent.request(
                    uri,
                    true,
                    function(uri, response, isXML, context) {
                      context._parseFeed(response);
                    },
                    this
                  );
    }
  },
  
  elementForBasicHTMLOutput:function()
  {
    return this._basicHTMLElement;
  },
  
  setElementForBasicHTMLOutput:function(
    element
  )
  {
    if ( (this._basicHTMLElement = element) ) {
      this.updateDisplay();
    }
  },
  
  updateDisplay:function()
  {
    if ( this._basicHTMLElement ) {
      this._generateBasicHTML();
      this._basicHTMLElement.innerHTML = this._basicHTML;
    }
  },
  
//
// Private member functions:
//
  
  _generateBasicHTML:function()
  {
    this._basicHTML = null;
    
    if ( this._longName ) {
      //
      // Generate basic HTML for the data:
      //
      if ( this._feedLink ) {
        this._basicHTML = '<div class="UDWebEventFeed"><div class="UDWebEventFeedTitle"><a href="' + this._feedLink + '">' + this._longName + '</a></div>';
      } else {
        this._basicHTML = '<div class="UDWebEventFeed"><div class="UDWebEventFeedTitle">' + this._longName + '</div>';
      }
      
      if ( this._events ) {
        var i = 0, iMax = this._events.length;
        
        if ( iMax ) {
          if ( (this._eventLimit > 0) && (iMax > this._eventLimit) ) {
            iMax = this._eventLimit;
          }
          this._basicHTML += '<div class="UDWebEventFeedItems">';
          while ( i < iMax ) {
            if ( this._events[i]['link'] ) {
              this._basicHTML += '<div class="UDWebEventFeedItem"><div class="UDWebEventFeedItemTitle"><a href="' + this._events[i]['link'] + '">' + this._events[i]['title'] + '</a></div>';
            } else {
              this._basicHTML += '<div class="UDWebEventFeedItem"><div class="UDWebEventFeedItemTitle">' + this._events[i]['title'] + '</div>';
            }
            this._basicHTML += '<div class="UDWebEventFeedItemDate">' + ( this._events[i]['date'] ? this._events[i]['date'] : '' ) + '</div>' + 
                               '<div class="UDWebEventFeedItemTime">' + ( this._events[i]['time'] ? this._events[i]['time'] : '' ) + '</div>' + 
                               '<div class="UDWebEventFeedItemDescription">' + ( this._events[i]['description'] ? this._events[i]['description'] : '' ) + '</div>' + 
                               '</div>';
            i++;
          }
          this._basicHTML += '</div>';
        }
      }
      this._basicHTML += '</div>';
    } else {
      this._basicHTML = '<div><b>ERROR:</b> Unable to parse feed!</div>'
    }
  },
  
  _parseFeed:function(
    xmlData
  )
  {
    var     rootNode = xmlData.documentElement;
    var     stripWhitespace = /^\s*|\s*$/g;
    
    this._basicHTML = null;
    this._events = Array();
    this._longName = null;
    this._shortName = null;
    this._feedLink = null;

    if ( rootNode && (rootNode.nodeName == 'rss') ) {
      var   channels = rootNode.getElementsByTagName('channel');
      
      if ( channels.length > 0 ) {
        var elements = channels[0].childNodes;
        var iMax;
        
        if ( elements && (iMax = elements.length) ) {
          var   i = 0;
          
          while ( i < iMax ) {
            switch ( elements[i].nodeName ) {
            
              case 'title': {
                this._longName = elements[i].firstChild.nodeValue;
                if ( this._longName ) {
                  this._longName = this._longName.replace(/^\s*University\s*of\s*Delaware\s*/, '');
                  this._longName = this._longName.replace(stripWhitespace, '');
                }
                break;
              }
              
              case 'link': {
                this._feedLink = elements[i].firstChild.nodeValue;
                if ( this._feedLink ) {
                  this._feedLink.replace(stripWhitespace, '');
                }
                break;
              }
              
              case 'description': {
                this._shortName = elements[i].firstChild.nodeValue;
                if ( this._shortName ) {
                  this._shortName = this._shortName.replace(stripWhitespace, '');
                }
                break;
              }
              
              case 'item': {
                var     itemElements = elements[i].childNodes;
                var     j = 0, jMax = itemElements.length;
                var     eventItem = Array();
                
                while ( j < jMax ) {
                  switch ( itemElements[j].nodeName ) {
                    
                    case 'title': {
                      eventItem['title'] = itemElements[j].firstChild.nodeValue;
                      if ( eventItem['title'] ) {
                        eventItem['title'].replace(stripWhitespace, '');
                      }
                      break;
                    }
                    
                    case 'guid': {
                      eventItem['link'] = itemElements[j].firstChild.nodeValue;
                      if ( eventItem['link'] ) {
                        eventItem['link'].replace(stripWhitespace, '');
                      }
                      break;
                    }
                    
                    case 'description': {
                      var     desc = itemElements[j].firstChild.nodeValue;
                      
                      // We need to manually parse the date, time, and description
                      // out of this text, *sigh*
                      var     match;
                      
                      match = (/Date:\s*(.*)/).exec(desc);
                      if ( match && match.length ) {
                        eventItem['date'] = match[1];
                      }
                      match = (/Time:\s*(.*)/).exec(desc);
                      if ( match && match.length ) {
                        eventItem['time'] = match[1];
                      }
                      match = desc.indexOf('Description:', desc); 
                      if ( match >= 0 ) {
                        eventItem['description'] = desc.slice(match + 12);
                      }
                      break;
                    }
                  }
                  j++;
                }
                this._events.push( eventItem );
                break;
              }
              
            }
            i++;
          }
        }
      }
    }
    
    //
    // All done:
    //
    this._populating = false;
    this.updateDisplay();
  }
  
}
