open-insight/SYSPROG/STPROCINS/RTI_IDE_HTML_GOOGLE_DYNAMICFEED.txt
2024-03-25 15:17:34 -07:00

867 lines
25 KiB
Plaintext

compile insert RTI_IDE_HTML_GOOGLE_DYNAMICFEED
/**
* 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
};
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;
};
/**
* 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;
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));
}
};
/**
* 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 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.options.feedLoadCallback) {
this.options.feedLoadCallback(result);
}
if (result.error) {
// 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) {
this.nodes.container.innerHTML = 'Feed' + ((this.expected > 1)?'s ':' ') +
'could not be loaded.';
}
return;
}
// Override of title option.
if (this.feeds[index].title) {
result.feed.title = this.feeds[index].title;
}
this.results.push(result);
if (!this.started) {
this.createSubContainers_();
this.displayResult_(0);
} else if (!this.options.horizontal && this.options.stacked) {
this.addResult_(this.results.length-1);
}
};
/**
* 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].feed.entries[0].publishedDate;
var newEntryDateMS = Date.parse(newEntryDate);
var insertIndex = null;
for (var i = 0; i < this.results.length; i++) {
var mostRecentPost = this.results[i].feed.entries[0].publishedDate;
var mostRecentPostMS = Date.parse(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);
}
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.feed.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 link = document.createElement('a');
link.target = google.feeds.LINK_TARGET_BLANK;
link.href = resultFeed.link;
link.className = 'gfg-collapse-href';
link.innerHTML = resultFeed.title;
el.appendChild(link);
}
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].feed.entries;
this.clearNode_(node);
for (var i = 0; i < entries.length; i++) {
this.feedControl.createHtml(entries[i]);
var className = 'gfg-listentry ';
className += (i%2)?'gfg-listentry-even':'gfg-listentry-odd';
var listEntry = this.createDiv_(className);
var link = this.createLink_(entries[i].link,
entries[i].title,
this.options.linkTarget);
listEntry.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);
}
if (node == this.nodes.list) {
this.entries = entries;
}
}
/**
* Begin to display the entries.
* @private
*/
GFdynamicFeedControl.prototype.displayEntries_ = function() {
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].feed.entries == this.entries) {
feedTitle = this.results[i].feed.title;
}
}
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) {
var className = 'gfg-listentry ';
className += (this.currentListIndex%2)?
'gfg-listentry-even':'gfg-listentry-odd';
this.currentList.className = className;
}
this.currentList = this.entries[this.entryIndex].listEntry;
this.currentListIndex = this.entryIndex;
var className = 'gfg-listentry gfg-listentry-highlight ';
className += (this.currentListIndex%2)?
'gfg-listentry-even':'gfg-listentry-odd';
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.feed.entries[listIndex].listEntry;
listEntry.selectTimer = null;
this.clearTransitionTimer_();
this.clearDisplayTimer_();
this.resultIndex = resultIndex;
this.entries = result.feed.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.feed.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.feed.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;
this.clearNode_(container);
if (this.options.horizontal) {
container = this.createDiv_('gfg-horizontal-container');
nodes.root = this.createDiv_('gfg-horizontal-root');
this.nodes.container.appendChild(container);
} else {
nodes.root = this.createDiv_('gfg-root');
}
nodes.title = this.createDiv_('gfg-title');
nodes.entry = this.createDiv_('gfg-entry');
nodes.list = this.createDiv_('gfg-list');
nodes.root.appendChild(nodes.title);
nodes.root.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);
container.appendChild(nodes.root);
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.feed.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;