/**
 * Copyright (c) 2008 Google Inc.
 *
 * You are free to copy and use this sample.
 * License can be found here: http://code.google.com/apis/ajaxsearch/faq/#license
*/

/**
 * @fileoverview A feed gadget based on the AJAX Feed API.
 * @author dcollison@google.com (Derek Collison)
 */

/**
 * GFdynamicFeedControl
 * @param {String} feed The feed URL.
 * @param {String|Object} container Either the id string or the element itself.
 * @param {Object} options Options map.
 * @constructor
 */

function GFdynamicFeedControl(feedUrls, container, options) {
  // node elements.
  this.nodes = {};
  this.collapseElements = [];
  
  // the feeds.
  this.feeds = [];
  this.results = [];

  if (typeof feedUrls == 'string') {
    this.feeds.push({url:feedUrls});
  } else if (typeof feedUrls == 'object') {
    for (var i=0; i<feedUrls.length; i++) {
      var entry = feedUrls[i];
      var o = {};
      var feedUrl;
      if (typeof entry == 'string') {
        o.url = feedUrls[i];
      } else if (typeof entry == 'object') {
        o = feedUrls[i];
	if (o && o.title) {
	  var s = o.title;
	  o.title = s.replace(/</g,'&lt;').replace(/>/g, '&gt;');
	}
      }
      this.feeds.push(o);
    }
  }

  if (typeof container == "string") {
    container = document.getElementById(container);
  }
  this.parseOptions_(options);
  this.setup_(container);
}

/*
 * Default time in milliseconds for the feed to be reloaded.
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_NUM_RESULTS = 4;
/*
 * Default time in milliseconds for the feed to be reloaded.
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_FEED_CYCLE_TIME = 1800000;
/*
 * Default display time in milliseconds for each entry.
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_DISPLAY_TIME = 5000;
/*
 * Default fadeout transition time in milliseconds for each entry.
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_FADEOUT_TIME = 1000;
/*
 * Default time between transition steps in milliseconds
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_TRANSISTION_STEP = 40;
/*
 * Default hover time in milliseconds for each entry.
 * @type Number
 */
GFdynamicFeedControl.DEFAULT_HOVER_TIME = 100;

/**
 * Setup default option map and apply overrides from constructor.
 * @param {Object} options Options map.
 * @private
 */
GFdynamicFeedControl.prototype.parseOptions_ = function(options) {
  // Default Options
  // TODO(dcollison) - implement Feed Cycle.
  this.options = {
    numResults : GFdynamicFeedControl.DEFAULT_NUM_RESULTS,
    feedCycleTime : GFdynamicFeedControl.DEFAULT_FEED_CYCLE_TIME,
    linkTarget : google.feeds.LINK_TARGET_BLANK,
    displayTime : GFdynamicFeedControl.DEFAULT_DISPLAY_TIME,
    transitionTime : GFdynamicFeedControl.DEFAULT_TRANSISTION_TIME,
    transitionStep : GFdynamicFeedControl.DEFAULT_TRANSISTION_STEP,
    fadeOutTime: GFdynamicFeedControl.DEFAULT_FADEOUT_TIME,
    scrollOnFadeOut : true,
    pauseOnHover : true,
    hoverTime : GFdynamicFeedControl.DEFAULT_HOVER_TIME,
    autoCleanup : true,
    transitionCallback : null,
    feedTransitionCallback : null,
    feedLoadCallback : null,
    collapseable : false,
    sortByDate : false,
    horizontal : false,
    stacked : false,
    title : null,
    
    //Custom code
    //---------------------------//
    customHeaderUrl : null,
    customHeaderText : '',
    evenRowStyle : '',
    alternatingRowStyle : '',
    newEntryRowStyle : '',
    highligthSyteDisplay : 5,
    maximumDescriptionChars : 50,
    generalErrorMessage : '',
    errorMessagesStyle : '',
    headerStyle : '',
    feedContainerStyle : '',
    entryDateLabelStyle : '',
    readMoreLinkText : '',
    readMoreLinkStyle : '',
    baseFeedLoaderPath : '',
    loadingMessageText : '',
    loadingMessageStyle : '',
    noDataMessage : '',
    noDataStyle : ''
    //---------------------------//
  };

  if (options) {
    for (var o in this.options) {
      if (typeof options[o] != 'undefined') {
        this.options[o] = options[o];
      }
    }
  }
  
  // Cant be collapseable unless stacked
  if(!this.options.stacked) {
    this.options.collapseable = false;
  }
  
  // Override strange/bad options
  this.options.displayTime = Math.max(200, this.options.displayTime);
  this.options.fadeOutTime = Math.max(0, this.options.fadeOutTime);

  // Calculated
  var ts = this.options.fadeOutTime / this.options.transitionStep;
  this.fadeOutDelta = Math.min(1, (1.0/ts));

  // Flag to start
  this.started = false;
  
  //Custom code
  //-------------------------//
  //used to know whether we are on the first load
  this.haveBeenFilled = false;
  //-------------------------//
};

/**
 * Basic setup.
 * @private
 */
GFdynamicFeedControl.prototype.setup_ = function(container) {
  if (container == null) return;
  this.nodes.container = container;

  // Browser fun.
  if (window.ActiveXObject) {
    this.ie = this[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
  } else if (document.childNodes && !document.all && !navigator.taintEnabled) {
    this.safari = true;
  } else if (document.getBoxObjectFor != null) {
    this.gecko = true;
  }
  // The feedControl instance for generating entry HTML.
  this.feedControl = new google.feeds.FeedControl();
  this.feedControl.setLinkTarget(this.options.linkTarget);

  // The feeds
  this.expected = this.feeds.length;
  this.errors = 0;

  //Custom code
  //----------------------------------------//
  
  //creates general divs
  this.clearNode_(container);
  if (this.options.horizontal) {
    container = this.createDiv_('gfg-horizontal-container');
    this.nodes.root = this.createDiv_('gfg-horizontal-root');
    this.nodes.container.appendChild(container);
  } else {
    this.nodes.root = this.createDiv_(this.options.feedContainerStyle);
  }
  container.appendChild(this.nodes.root);
  
  //creates the div used to show error messages
  this.messagesDiv = this.createDiv_('');
  this.messagesDiv.style.display = 'none';
  
  //creates control's structure
  this.createSubContainers_();
  
  //sets title
  if(this.options.title != null)
    this.setTitle_(this.options.title);
  
  //show loading message
  this.loadingDiv = this.createDiv_(this.options.loadingMessageStyle);
  this.loadingDiv.innerHTML = this.options.loadingMessageText;
  this.nodes.root.appendChild(this.loadingDiv);
  
//dr
  this.loadFeeds();
//window.This = this;
//  window.setTimeout('window.This.loadFeeds();', 1); 
  //----------------------------------------//
};

//Custom code
//----------------------------------------//
GFdynamicFeedControl.prototype.loadFeeds = function()
{
    for (var i = 0; i < this.feeds.length; i++) {
        /*
        var feed = new google.feeds.Feed(this.feeds[i].url);
        feed.setResultFormat(google.feeds.Feed.JSON_FORMAT);
        feed.setNumEntries(this.options.numResults);
        feed.load(this.bind_(this.feedLoaded_, i));
        */
        this.feedLoaded_(i, LoadFeed(this.feeds[i].url, this.options.numResults, this.options.baseFeedLoaderPath));
    }
}
//----------------------------------------//


/**
 * Helper method to bind this instance correctly.
 * @param {Object} method function/method to bind.
 * @return {Function}
 * @private
 */
GFdynamicFeedControl.prototype.bind_ = function(method) {
  var self = this;
  var self = this;
  var opt_args = [].slice.call(arguments, 1);
  return function() {
    var args = opt_args.concat([].slice.call(arguments));
    return method.apply(self, args);
  }
};

/**
 * Callback associated with the AJAX Feed api after load.
 * @param {Object} result Loaded result.
 * @private
 */
GFdynamicFeedControl.prototype.feedLoaded_ = function(index, result) {

  if(this.loadingDiv != null)
  {
    this.nodes.root.removeChild(this.loadingDiv);
    this.loadingDiv = null;
  }

  if (this.options.feedLoadCallback) {
    this.options.feedLoadCallback(result);
  }

  if (result.error == true) 
  {
    // Ignore failed feeds for the most part.
    // The user has control through the feedLoadCallback above
    // if they choose to do something more createive.
    // Only complain if we can't load anything.
    if (++this.errors >= this.expected) {
      //Custom code
      //----------------------------------------//
      //this.nodes.container.innerHTML = 'Feed' + ((this.expected > 1)?'s ':' ') +
      //                                 'could not be loaded.';
      this.createSubContainers_();
      this.ToggleMessageDivDisplay(true, this.options.generalErrorMessage, this.options.errorMessagesStyle);
      //----------------------------------------//                                       
    }
  }
  else
  {
      this.ToggleMessageDivDisplay(false);
    
      // Override of title option.
      if (this.feeds[index].title) {
        result.feedTitle = this.feeds[index].title;
      }
      this.results[0] = result;

      if (!this.started) {
        this.createSubContainers_();
        this.displayResult_(0);
      } else if (!this.options.horizontal && this.options.stacked) {
        this.addResult_(this.results.length-1);
      }
  }
    
  this.setTitle_(this.options.title != null ? this.options.title : result.feed);
  
};

/**
 * Insert blog in correct place
 * @private
 */
GFdynamicFeedControl.prototype.sortByDate_ = function(resultIndex, newTitle,
                                                      newList) {
  // if sorting by date, insert it into the correct spot
  var newEntryDate = this.results[resultIndex].entries[0].publishedDate;
  var newEntryDateMS = Date.parse(this.fixDateString(newEntryDate));
  var insertIndex = null;

  for (var i = 0; i < this.results.length; i++) {
    var mostRecentPost = this.results[i].entries[0].publishedDate;
    var mostRecentPostMS = Date.parse(this.fixDateString(mostRecentPost));
    if(newEntryDateMS > mostRecentPostMS) {
      insertIndex = i;
      break;
    }
  }

  // If it's most stale blog, just append as usual
  if(insertIndex == null) {
    this.nodes.root.appendChild(newTitle);
    this.nodes.root.appendChild(newList);
    this.createListEntries_(resultIndex, newList);
    return;
  }

  // If it is fresher than a blog, insert to correct position
  var insertBeforeIndex = 2 + (insertIndex * 2);
  var swapToIndex = insertBeforeIndex + 2;
  var tempSwap = null;
  var myResultIndex = resultIndex + 1;

  var sectionsToChange = this.nodes.root.childNodes;
  var nodeToInsertBefore = sectionsToChange[insertBeforeIndex];

  this.nodes.root.insertBefore(newTitle, nodeToInsertBefore);
  this.nodes.root.insertBefore(newList, nodeToInsertBefore);

  this.results.splice(insertIndex, 0, this.results[resultIndex]);
  this.results.splice(myResultIndex, 1);
  
  var nodesToChangeClick = sectionsToChange[swapToIndex].nextSibling.childNodes;
  
  this.createListEntries_(insertIndex, newList);

  // Keep freshest blog open first
  if(insertIndex == 0) {
    this.displayResult_(0);
  }

  insertIndex += 1;
  // Reset all of the onmousehover listeners to highlight corect index
  for (var i = swapToIndex; i < sectionsToChange.length; i += 2) {
    var nodesToChangeClick = sectionsToChange[i].nextSibling.childNodes;
    for (var j=0; j < nodesToChangeClick.length; j++) {
      nodesToChangeClick[j].onmouseover = this.bind_(this.listMouseOver_, insertIndex, j);
      nodesToChangeClick[j].onmouseout = this.bind_(this.listMouseOut_, insertIndex, j);
    }
    insertIndex++;
  }
};

/**
 * Setup to display the Result for stacked mode
 * @private
 */
GFdynamicFeedControl.prototype.addResult_ = function(resultIndex) {
  var result = this.results[resultIndex];
  var newTitle = this.createDiv_('gfg-subtitle');
  this.setTitle_(result.feed, newTitle);
  var newList = this.createDiv_('gfg-list');

  // add a collapseable button
  if(this.options.collapseable) {
    var collapseLink = document.createElement('div');
    newList.style.display = 'none';
    collapseLink.className = 'gfg-collapse-closed';
    newTitle.appendChild(collapseLink);
    collapseLink.onclick = this.toggleCollapse(this, newList, collapseLink);
    this.collapseElements.push({
      list : newList,
      collapse : collapseLink
    });
  }


  var clearFloat = document.createElement('div');
  clearFloat.className = 'clearFloat';
  newTitle.appendChild(clearFloat);

  // If not sorting by date, add them as usual
  if(!this.options.sortByDate) {
    this.nodes.root.appendChild(newTitle);
    this.nodes.root.appendChild(newList);
    this.createListEntries_(resultIndex, newList);
  } else {
    this.sortByDate_(resultIndex, newTitle, newList);
  }
  
};

/**
 * Setup to display the Result
 * @private
 */
GFdynamicFeedControl.prototype.displayResult_ = function(resultIndex) {

  this.resultIndex = resultIndex;
  var result = this.results[resultIndex];
  if (this.options.feedTransitionCallback) {
    this.options.feedTransitionCallback(result);
  }
  
  //Custom code
  //-----------------------------//
  /*
  if (this.options.title) {
    this.setPlainTitle_(this.options.title);
  } else {
    this.setTitle_(result.feed);
  }*/
  //-----------------------------//
  this.clearNode_(this.nodes.entry);

  if (this.started && !this.options.horizontal && this.options.stacked) {
    this.entries = result.entries;
  } else {
    this.createListEntries_(resultIndex, this.nodes.list);
  }
  this.displayEntries_();
}

/**
 * Set the Title to just plaintext
 * @private
 */
GFdynamicFeedControl.prototype.setPlainTitle_ = function(title, opt_element) {
  var el = opt_element || this.nodes.title;
  el.innerHTML = title;
}

/**
 * Set the Title
 * @private
 */
GFdynamicFeedControl.prototype.setTitle_ = function(resultFeed, opt_element) {
  var el = opt_element || this.nodes.title;
  this.clearNode_(el);
  var isLink = (this.options.customHeaderUrl != null);
  var element;
  
  if(isLink)
  {
      element = document.createElement('a');
      element.target = google.feeds.LINK_TARGET_BLANK;
      element.href = this.options.customHeaderUrl;
      element.className = 'gfg-collapse-href';
  }
  else
  {
      element = document.createElement('label');
  }
  
  element.innerHTML = (typeof(resultFeed) == 'string' ? resultFeed : resultFeed.title);
  el.appendChild(element);
}

GFdynamicFeedControl.prototype.toggleCollapse = function(feedControl, 
                                                         listReference, 
                                                         collapseLink) {
  return function() {
    var els = feedControl.collapseElements;
    for (var i=0; i < els.length; i++) {
      var el = els[i];
      el.list.style.display = 'none';
      el.collapse.className = 'gfg-collapse-closed';
    }
    listReference.style.display = 'block';
    collapseLink.className = 'gfg-collapse-open';
  }
}

/**
 * Create the list Entries
 * @private
 */
GFdynamicFeedControl.prototype.createListEntries_ = function(resultIndex, node) {
  
  var entries = this.results[resultIndex].entries;
  this.clearNode_(node);
  
  //Custom code
  //--------------------------------------//
  //used to determine whether the first entry of the
  //previous load was found.
  var firstEntryFound = false;
  //used to know whether the previous first entry
  //has been found on the list of entries been processed
  var previousFirstEntryFound = false;
  //--------------------------------------//
  
  if(entries.length == 0)
  {
      this.ToggleMessageDivDisplay(true, this.options.noDataMessage, this.options.noDataStyle);
  }
  else
  {  
      this.ToggleMessageDivDisplay(false);
      
      for (var i = 0; i < entries.length; i++) 
      {
    this.feedControl.createHtml(entries[i]);
    var className = '';
    //Custom code
    //--------------------------------//
    var secondClassName = (i%2) ? this.options.alternatingRowStyle : this.options.evenRowStyle;
    var listEntry;
    
    //determines if we are processing a new entry. If so, for X seconds, a highlighting
    //style has to be shown, and so the class has to be restored to a normal one.
    if(this.haveBeenFilled && this.previousFirstEntry != null && !previousFirstEntryFound)
    {
        previousFirstEntryFound = (entries[i].title == this.previousFirstEntry.title);
    }
    
    if(this.haveBeenFilled && !previousFirstEntryFound)
    {
        listEntry = this.createDiv_(className + this.options.newEntryRowStyle);
        //registers the current entry to be automatically changed when
        //the needed time span elapses
        this.RegisterForStyleChange(listEntry, this.options.highligthSyteDisplay, className + secondClassName);
    }
    else
    {
        className += secondClassName;
        listEntry = this.createDiv_(className);
    }
    //--------------------------------//
     
        var titleElement;
        
        if(entries[i].link != null)
        {
            titleElement = this.createLink_(entries[i].link,
                                entries[i].title,
                                this.options.linkTarget);
        }
        else
        {
            titleElement = document.createElement('label');
            titleElement.innerHTML = entries[i].title;
        }
        
        listEntry.appendChild(titleElement);
    
    //creats the label with the date
    var dateLabel = this.createDateLabel(entries[i].publishedDate, this.options.entryDateLabelStyle);
    listEntry.appendChild(dateLabel);
    
    //Custom code
    //------------------------------------//
    var content = this.createDiv_("");
    content.innerHTML = this.GetTrimmedContent(entries[i].content, this.options.maximumDescriptionChars);
    listEntry.appendChild(content);
    
    if(entries[i].content != null && entries[i].content.length > this.options.maximumDescriptionChars)
    {
        content.innerHTML += '&nbsp;';
        //creates the link to continue reading
        var link = this.createLink_(entries[i].link, this.options.readMoreLinkText, this.options.linkTarget);
        link.className = this.options.readMoreLinkStyle;
        content.appendChild(link);
    }
    //------------------------------------//
    
    if (this.options.pauseOnHover) {
      listEntry.onmouseover = this.bind_(this.listMouseOver_, resultIndex, i);
      listEntry.onmouseout = this.bind_(this.listMouseOut_, resultIndex, i);
    }
    entries[i].listEntry = listEntry;
    node.appendChild(listEntry);
  }
  }
  
  //Custom code
  //----------------------------------//
  //saves the first node received for futher comparisson
  this.previousFirstEntry = (entries.length > 0 ? entries[0] : null);
  //----------------------------------//
  
  if (node == this.nodes.list) {
    this.entries = entries;
  }
  
  this.haveBeenFilled = true;
}

/**
 * Begin to display the entries.
 * @private
 */
GFdynamicFeedControl.prototype.displayEntries_ = function() {
  //Custom code
  //------------------------------//
  /*
  this.entryIndex = 0;
  this.displayCurrentEntry_();
  this.setDisplayTimer_();
  this.started = true;*/
  //------------------------------//
}

/**
 * Display next entry.
 * @private
 */
GFdynamicFeedControl.prototype.displayNextEntry_ = function() {
  // Check to see if we have been orphaned and need to cleanup..
  if (this.options.autoCleanup && this.isOrphaned_()) {
      this.cleanup_();
      return;
  }

  if (++this.entryIndex >= this.entries.length) {
    // End of list, see if we should rotate feeds..
    if (this.results.length > 1) {
      if (++this.resultIndex >= this.results.length) {
        this.resultIndex = 0;
      }
      this.displayResult_(this.resultIndex);
      return;
    } else {
      this.entryIndex = 0;
    }
  }

  if (this.options.transitionCallback) {
    this.options.transitionCallback(this.entries[this.entryIndex]);
  }
  this.displayCurrentEntry_();
  this.setDisplayTimer_();
}

/**
 * Display current entry.
 * @private
 */
GFdynamicFeedControl.prototype.displayCurrentEntry_ = function() {
  this.clearNode_(this.nodes.entry);
  this.current = this.entries[this.entryIndex].html;
  this.current.style.top = '0px';
  this.nodes.entry.appendChild(this.current);
  this.createOverlay_();
  
  // Expand the blog who's post is being displayed
  if(this.options.collapseable) {
    var feedTitle = null;
    
    for (var i=0; i < this.results.length; i++) {
      if(this.results[i].entries == this.entries) {
        feedTitle = this.results[i].feedTitle;
      }
    }

    var els = this.collapseElements;

    for (var i=0; i < els.length; i++) {
      var el = els[i];
      var divfeedTitle = el.collapse.previousSibling.innerHTML;
      var expandClicker = el.collapse;
      if(feedTitle == divfeedTitle) {
        if(this.ie) {
          expandClicker.click();
        } else {
          expandClicker.onclick();
        }

      }
    }
  }
  
  if (this.currentList) {
    
    //Custom code
    //------------------------------//
    /*
    var className = 'gfg-listentry ';
    className += (this.currentListIndex%2)?
    
    // 'gfg-listentry-even':'gfg-listentry-odd';
        this.options.evenRowStyle : this.options.alternatingRowStyle;
    this.currentList.className = className;
    */
    //------------------------------//
   
  }
  this.currentList = this.entries[this.entryIndex].listEntry;
  this.currentListIndex = this.entryIndex;
  //Custom code
  //------------------------------//
  /*
  var className = 'gfg-listentry ';
  className += (this.currentListIndex%2)?
        //'gfg-listentry-even':'gfg-listentry-odd';
       this.options.alternatingRowStyle : this.options.evenRowStyle;
  this.currentList.className = className;
  */
  //------------------------------//
}

/**
 * Simulated mouse hover events for list entries.
 * @private
 */
GFdynamicFeedControl.prototype.listMouseHover_ = function(resultIndex,
                                                          listIndex) {
  var result = this.results[resultIndex];
  var listEntry = result.entries[listIndex].listEntry;
  listEntry.selectTimer = null;
  this.clearTransitionTimer_();
  this.clearDisplayTimer_();
  this.resultIndex = resultIndex;
  this.entries = result.entries;
  this.entryIndex = listIndex;
  this.displayCurrentEntry_();
}

/**
 * Mouse over events for list entries.
 * @private
 */
GFdynamicFeedControl.prototype.listMouseOver_ = function(resultIndex,
                                                         listIndex) {
  var result = this.results[resultIndex];
  var listEntry = result.entries[listIndex].listEntry;
  var cb = this.bind_(this.listMouseHover_, resultIndex, listIndex);
  listEntry.selectTimer = setTimeout(cb, this.options.hoverTime);
}

/**
 * Mouse out events for list entries.
 * @private
 */
GFdynamicFeedControl.prototype.listMouseOut_ = function(resultIndex, listIndex) {
  var result = this.results[resultIndex];
  var listEntry = result.entries[listIndex].listEntry;
  if (listEntry.selectTimer) {
    clearTimeout(listEntry.selectTimer);
    listEntry.selectTimer = null;
  } else {
    this.setDisplayTimer_();
  }
}

/**
 * Mouse over events for main entry.
 * @private
 */
GFdynamicFeedControl.prototype.entryMouseOver_ = function(e) {
  this.clearDisplayTimer_();
  if (this.transitionTimer) {
    this.clearTransitionTimer_();
    this.displayCurrentEntry_();
  }
}

/**
 * Mouse out events for main entry.
 * @private
 */
GFdynamicFeedControl.prototype.entryMouseOut_ = function(e) {
  this.setDisplayTimer_();
}

/**
 * Create the overlay div. This hack is for IE and transparency effects.
 * @private
 */
GFdynamicFeedControl.prototype.createOverlay_ = function() {
  if (this.current == null) return;
  // Create div lazily and hold on to it..
  if (this.overlay == null) {
    var overlay = this.createDiv_('gfg-entry');
    overlay.style.position = 'absolute';
    overlay.style.top = '0px';
    overlay.style.left = '0px';
    this.overlay = overlay;
  }
  this.setOpacity_(this.overlay, 0);
  this.nodes.entry.appendChild(this.overlay);
}

/**
 * Sets the display timer.
 * @private
 */
GFdynamicFeedControl.prototype.setDisplayTimer_ = function() {
  if (this.displayTimer) {
    this.clearDisplayTimer_();
  }
  var cb = this.bind_(this.setFadeOutTimer_);
  //this.displayTimer = setTimeout(cb, this.options.displayTime);
};

/**
 * Class helper method for the time now in milliseconds
 * @private
 */
GFdynamicFeedControl.timeNow = function() {
  var d = new Date();
  return d.getTime();
};

/**
 * Transition animation for fadeout. Cleanup when finished.
 * @private
 */
GFdynamicFeedControl.prototype.fadeOutEntry_ = function() {
  if (this.overlay) {
    var delta = this.fadeOutDelta;
    var ts = this.options.transitionStep;
    var now = GFdynamicFeedControl.timeNow();
    var tick = now - this.lastTick;
    this.lastTick = now;
    delta *= (tick/ts);

    var op = this.overlay.opacity + delta;
    // Overlay opacity
    this.setOpacity_(this.overlay, op);
    // Scroll down
    if (this.options.scrollOnFadeOut && (op > .5)) {
      var r = (op-.5)*2;
      var newTop = Math.round(this.current.offsetHeight * r);
      this.current.style.top = newTop + 'px';
    }
    if (op < 1) return;
  }
  // Finished.
  this.clearTransitionTimer_();
  this.displayNextEntry_();
};

/**
 * Sets the transition timer for fadeout.
 * @private
 */
GFdynamicFeedControl.prototype.setFadeOutTimer_ = function() {
  this.clearTransitionTimer_();
  this.lastTick = GFdynamicFeedControl.timeNow();
  var cb = this.bind_(this.fadeOutEntry_);
  this.transitionTimer = setInterval(cb, this.options.transitionStep);
};

/**
 * Clear the transition timer. Used to prevent leaks.
 * @private
 */
GFdynamicFeedControl.prototype.clearTransitionTimer_ = function() {
  if (this.transitionTimer) {
    clearInterval(this.transitionTimer);
    this.transitionTimer = null;
  }
};

/**
 * Clear the display timer.
 * @private
 */
GFdynamicFeedControl.prototype.clearDisplayTimer_ = function() {
  if (this.displayTimer) {
    clearTimeout(this.displayTimer);
    this.displayTimer = null;
  }
};

/**
 * Setup our own subcontainer to the user supplied container.
 * @private
 */
GFdynamicFeedControl.prototype.createSubContainers_ = function() {
  var nodes = this.nodes;
  var container = this.nodes.container;
  
  //Custom code
  //---------------------------------//
  //creates the tile if not exists
  if(nodes.title == null)
  {
    nodes.title = this.createDiv_(this.options.headerStyle);
  }
  //removes previous entries
  if(nodes.list != null)
  {
    nodes.root.removeChild(nodes.list);
  }
  
  //nodes.entry = this.createDiv_('gfg-entry');
  nodes.entry = this.createDiv_();
  nodes.list = this.createDiv_('gfg-list');
  
  //nodes.root.appendChild(nodes.entry); 
  nodes.root.appendChild(nodes.title); 
  nodes.title.appendChild(nodes.entry);
  //---------------------------------//
   

  if (!this.options.horizontal && this.options.stacked) {
    var newTitle = this.createDiv_('gfg-subtitle');
    nodes.root.appendChild(newTitle);
    this.setTitle_(this.results[0].feed, newTitle);
    
    if(this.options.collapseable) {
      var collapseLink = document.createElement('div');
      collapseLink.className = 'gfg-collapse-open';
      newTitle.appendChild(collapseLink);
      collapseLink.onclick = this.toggleCollapse(this, nodes.list, collapseLink);
      this.collapseElements.push({
        list : nodes.list,
        collapse : collapseLink
      });
      nodes.list.style.display = 'block';
    }
    
    var clearFloat = document.createElement('div');
    clearFloat.className = 'clearFloat';
    newTitle.appendChild(clearFloat);
  }
  
  nodes.root.appendChild(nodes.list);
  
  if (this.options.pauseOnHover) {
    nodes.entry.onmouseover = this.bind_(this.entryMouseOver_);
    nodes.entry.onmouseout = this.bind_(this.entryMouseOut_);
  }

  // Add Branding.
  if (this.options.horizontal) {
    nodes.branding = this.createDiv_('gfg-branding');
    google.feeds.getBranding(nodes.branding, google.feeds.VERTICAL_BRANDING);
    container.appendChild(nodes.branding);
  }
};

/**
 * Helper method to properly clear a node and its children.
 * @param {Object} node Node to clear.
 * @private
 */
GFdynamicFeedControl.prototype.clearNode_ = function(node) {
  if (node == null) return;
  var child;
  while ((child = node.firstChild)) {
    node.removeChild(child);
  }
};

/**
 * Helper method to create a div with optional class and text.
 * @param {String} opt_className Optional className for the div.
 * @param {String} opt_text Optional text for the innerHTML.
 * @private
 */
GFdynamicFeedControl.prototype.createDiv_ = function(opt_className, opt_text) {
  var el = document.createElement("div");
  if (opt_text) {
    el.innerHTML = opt_text;
  }
  if (opt_className) { el.className = opt_className; }
  return el;
};

/**
 * Helper method to create a link with href and text.
 * @param {String} href Href URL
 * @param {String} text text for the link.
 * @param {String} opt_target Optional link target.
 * @private
 */
GFdynamicFeedControl.prototype.createLink_ = function(href, text, opt_target) {
  var link = document.createElement('a');
  link.href = href;
  link.innerHTML = text;
  if (opt_target) {
    link.target = opt_target;
  }
  return link;
};

/**
 * Cleanup results on being orphaned.
 * @private
 */
GFdynamicFeedControl.prototype.clearResults_ = function() {
  for (var i=0; i < this.results.length; i++) {
    var result = this.results[i];
    var entries = result.entries;
    for (var i = 0; i < entries.length; i++) {
      var entry = entries[i];
      entry.html = null;
      entry.listEntry.onmouseover = null;
      entry.listEntry.onmouseout = null;
      if (entry.listEntry.selectTimer) {
        clearTimeout(entry.listEntry.selectTimer);
        entry.listEntry.selectTimer = null;
      }
      entry.listEntry = null;
    }
  }
}

/**
 * Check for being orphaned.
 * @private
 */
GFdynamicFeedControl.prototype.isOrphaned_ = function() {
  var root = this.nodes.root;
  var orphaned = false;
  if (!root || !root.parentNode) {
    orphaned = true;
  } else if (this.options.horizontal && !root.parentNode.parentNode) {
    orphaned = true;
  }
  return orphaned;
}

/**
 * Cleanup on being orphaned.
 * @private
 */
GFdynamicFeedControl.prototype.cleanup_ = function() {
  this.started = false;
  // Timer Events.
  this.clearDisplayTimer_();
  this.clearTransitionTimer_();
  // Structures
  this.clearResults_();
  // Nodes
  this.clearNode_(this.nodes.root);
  this.nodes.container = null;
}

/**
 * Helper method to set opacity for nodes.. Also takes into account
 * visibility in general.
 * @param {Element} node element.
 * @param {Number} opacity alpha level.
 * @private
 */
GFdynamicFeedControl.prototype.setOpacity_ = function(node, opacity) {
  if (node == null) return;
  opacity = Math.max(0, Math.min(1, opacity));
  if (opacity == 0) {
    if (node.style.visibility != 'hidden') {
      node.style.visibility = 'hidden';
    }
  } else {
    if (node.style.visibility != 'visible') {
      node.style.visibility = 'visible';
    }
  }
  if (this.ie) {
    var normalized = Math.round(opacity*100);
    node.style.filter = 'alpha(opacity=' + normalized + ')';
  }
  node.style.opacity = node.opacity = opacity;
};

GFgadget = GFdynamicFeedControl;

//Custom code
//-----------------------------------------//
/**
 * Changes the style of the supplied element after
 * certain number of seconds have elapsed
 * @param {element} Element to modify.
 * @param {secondsToWait} Numers of seconds to wait until change the style.
 * @param {styleToSet} New syle to set on the element when the span of time have elapsed.
 * @private
 */
GFdynamicFeedControl.prototype.RegisterForStyleChange = function(element, secondsToWait, styleToSet)
{
    //generatas an unique key to register the object
    var windowKey = 'window._' + new Date().getTime().toString();
    var registered = false;
    do
    {
        windowKey += '_';
        eval('registered = (' + windowKey + ' != null);');
    }
    while(registered);
    
    //registers the element on windows object
    eval(windowKey + ' = element;');
    
    //creates the timer to get the object, change style, and remove from window
    var code = windowKey + '.className = "' + styleToSet + '";' + windowKey + '=null;';
    window.setTimeout(code, secondsToWait * 1000);
}

/**
 * Gets the description for certain item, trimming it if
 * is greater to the maximum allowed
 * @param {content} Content to show
 * @param {maximumChars} Maximum number of content's chars to show 
 * @private
 */
GFdynamicFeedControl.prototype.GetTrimmedContent = function(content, maximumChars, entryHref)
{

    if(content == null || content == '' || maximumChars == 0)
    {
        return '';
    }
    else if(content.length <= maximumChars) 
    {
        return content;
    }
    else
    {
        //determines whether the last word to
        //show ends, and cut from start to there
        
        var cutIndex = maximumChars;
        while(content.charAt(cutIndex) != ' ' && content.indexOf(' ', maximumChars) > -1 )
        {
            cutIndex += 1;
        }
        return content.substr(0, cutIndex);
    }
}

/**
 * Changes the visualization of the error messages displayed
 * to the user
 * @param {hasToShow} Indicates if the error's div with error messages has to shown
 * @param {message} Message to display when error's div is visible
 * @public
 */
GFdynamicFeedControl.prototype.ToggleMessageDivDisplay = function(hasToShow, message, style)
{
    this.messagesDiv.className = style;
    this.messagesDiv.style.display = (hasToShow ? 'block' : 'none');
    this.messagesDiv.innerHTML = (hasToShow ? message : '');
    
    if(hasToShow)
    {
        this.nodes.list.appendChild(this.messagesDiv);
    }
    else if(this.nodes.list != null && this.messagesDiv.parentNode == this.nodes.list)
    {
        this.nodes.list.removeChild(this.messagesDiv);
    }
}

/**
 * Creates an element to show the date associated to an entry
 * @param {date} Date to show
 * @param {style} CSS style name for the new element created
 * @private
 */
GFdynamicFeedControl.prototype.createDateLabel = function(date, style)
{
    var span = document.createElement("label");
    var newDate = Date.parse(this.fixDateString(date));
    //if date is on null, it could be due the value
    //was on ISO8601 and could not be parsed properly
    //by Date.parse. So, tries with an alternative parsing rule
    if(newDate == null)
    {
        newDate = new Date();
        newDate.setISO8601(date);
    }
    span.innerHTML = (newDate == null ? '&nbsp;' : '&nbsp;(' + newDate.toString('MM/dd/yyyy hh:mm:ss tt') + ')&nbsp;');
    span.className = style;
    return span;
}

/**
 * Analizes the supplied string representing a date trying
 * to fix any format problem it could have
 * @param {date} Date to be processed
 * @private
 */
GFdynamicFeedControl.prototype.fixDateString = function(date)
{
    var date = date.toString();
    var lastIndex = date.lastIndexOf(':');
    
    if(lastIndex > 0)
    {
        var lastDotIndex = date.indexOf('.', lastIndex);
        if(lastDotIndex > -1)
        {
            date = date.substring(0, lastDotIndex);
        }
    }
    return date;
}

//-----------------------------------------//