/*
Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
Available via Academic Free License >= 2.1 OR the modified BSD license.
see: http://dojotoolkit.org/license for details
*/
if(!dojo._hasResource["dojox.atom.widget.FeedViewer"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
dojo._hasResource["dojox.atom.widget.FeedViewer"] = true;
dojo.provide("dojox.atom.widget.FeedViewer");
dojo.require("dijit._Widget");
dojo.require("dijit._Templated");
dojo.require("dijit._Container");
dojo.require("dojox.atom.io.Connection");
dojo.requireLocalization("dojox.atom.widget", "FeedViewerEntry", null, "ROOT,ar,ca,cs,da,de,el,es,fi,fr,he,hu,it,ja,kk,ko,nb,nl,pl,pt,pt-pt,ro,ru,sk,sl,sv,th,tr,zh,zh-tw");
dojo.experimental("dojox.atom.widget.FeedViewer");
dojo.declare("dojox.atom.widget.FeedViewer",[dijit._Widget, dijit._Templated, dijit._Container],{
// summary:
// An ATOM feed viewer that allows for viewing a feed, deleting entries, and editing entries.
// description:
// An ATOM feed viewer that allows for viewing a feed, deleting entries, and editing entries.
feedViewerTableBody: null, //The body of the feed viewer table so we can access it and populate it. Will be assigned via template.
feedViewerTable: null, //The overal table container which contains the feed viewer table. Will be assigned via template.
entrySelectionTopic: "", //The topic to broadcast when any entry is clicked so that a listener can pick up it and display it.
url: "", //The URL to which to connect to initially on creation.
xmethod: false,
localSaveOnly: false,
//Templates for the HTML rendering. Need to figure these out better, admittedly.
templateString: dojo.cache("dojox.atom", "widget/templates/FeedViewer.html", "<div class=\"feedViewerContainer\" dojoAttachPoint=\"feedViewerContainerNode\">\n\t<table cellspacing=\"0\" cellpadding=\"0\" class=\"feedViewerTable\">\n\t\t<tbody dojoAttachPoint=\"feedViewerTableBody\" class=\"feedViewerTableBody\">\n\t\t</tbody>\n\t</table>\n</div>\n"),
_feed: null,
_currentSelection: null, // Currently selected entry
_includeFilters: null,
alertsEnabled: false,
postCreate: function(){
// summary:
// The postCreate function.
// description:
// The postCreate function. Creates our AtomIO object for future interactions and subscribes to the
// event given in markup/creation.
this._includeFilters = [];
if(this.entrySelectionTopic !== ""){
this._subscriptions = [dojo.subscribe(this.entrySelectionTopic, this, "_handleEvent")];
}
this.atomIO = new dojox.atom.io.Connection();
this.childWidgets = [];
},
startup: function(){
// summary:
// The startup function.
// description:
// The startup function. Parses the filters and sets the feed based on the given url.
this.containerNode = this.feedViewerTableBody;
var children = this.getDescendants();
for(var i in children){
var child = children[i];
if(child && child.isFilter){
this._includeFilters.push(new dojox.atom.widget.FeedViewer.CategoryIncludeFilter(child.scheme, child.term, child.label));
child.destroy();
}
}
if(this.url !== ""){
this.setFeedFromUrl(this.url);
}
},
clear: function(){
// summary:
// Function clearing all current entries in the feed view.
// description:
// Function clearing all current entries in the feed view.
//
// returns:
// Nothing.
this.destroyDescendants();
},
setFeedFromUrl: function(/*string*/url){
// summary:
// Function setting the feed from a URL which to get the feed.
// description:
// Function setting the dojox.atom.io.model.Feed data into the view.
//
// url:
// The URL to the feed to load.
//
// returns:
// Nothing.
if(url !== ""){
if(this._isRelativeURL(url)){
var baseUrl = "";
if(url.charAt(0) !== '/'){
baseUrl = this._calculateBaseURL(window.location.href, true);
}else{
baseUrl = this._calculateBaseURL(window.location.href, false);
}
this.url = baseUrl + url;
}
this.atomIO.getFeed(url,dojo.hitch(this,this.setFeed));
}
},
setFeed: function(/*object*/feed){
// summary:
// Function setting the dojox.atom.io.model.Feed data into the view.
// description:
// Function setting the dojox.atom.io.model.Feed data into the view.
//
// entry:
// The dojox.atom.io.model.Feed object to process
//
// returns:
// Nothing.
this._feed = feed;
this.clear();
var entrySorter=function(a,b){
var dispA = this._displayDateForEntry(a);
var dispB = this._displayDateForEntry(b);
if(dispA > dispB){return -1;}
if(dispA < dispB){return 1;}
return 0;
};
// This function may not be safe in different locales.
var groupingStr = function(dateStr){
var dpts = dateStr.split(',');
dpts.pop(); // remove year and time
return dpts.join(",");
};
var sortedEntries = feed.entries.sort(dojo.hitch(this,entrySorter));
if(feed){
var lastSectionTitle = null;
for(var i=0;i<sortedEntries.length;i++){
var entry = sortedEntries[i];
if(this._isFilterAccepted(entry)){
var time = this._displayDateForEntry(entry);
var sectionTitle = "";
if(time !== null){
sectionTitle = groupingStr(time.toLocaleString());
if(sectionTitle === "" ){
//Generally an issue on Opera with how its toLocaleString() works, so do a quick and dirty date construction M/D/Y
sectionTitle = "" + (time.getMonth() + 1) + "/" + time.getDate() + "/" + time.getFullYear();
}
}
if((lastSectionTitle === null) || (lastSectionTitle != sectionTitle)){
this.appendGrouping(sectionTitle);
lastSectionTitle = sectionTitle;
}
this.appendEntry(entry);
}
}
}
},
_displayDateForEntry: function(/*object*/entry){
// summary:
// Internal function for determining the appropriate date to display.
// description: Internal function for determining of a particular entry is editable.
//
// entry:
// The dojox.atom.io.model.Entry object to examine.
//
// returns:
// An appropriate date for the feed viewer display.
if(entry.updated){return entry.updated;}
if(entry.modified){return entry.modified;}
if(entry.issued){return entry.issued;}
return new Date();
},
appendGrouping: function(/*string*/titleText){
// summary:
// Function for appending a new grouping of entries to the feed view.
// description:
// Function for appending a grouping of entries to the feed view.
//
// entry:
// The title of the new grouping to create on the view.
//
// returns:
// Nothing.
var entryWidget = new dojox.atom.widget.FeedViewerGrouping({});
entryWidget.setText(titleText);
this.addChild(entryWidget);
this.childWidgets.push(entryWidget);
},
appendEntry: function(/*object*/entry){
// summary:
// Function for appending an entry to the feed view.
// description:
// Function for appending an entry to the feed view.
//
// entry:
// The dojox.atom.io.model.Entry object to append
//
// returns:
// Nothing.
var entryWidget = new dojox.atom.widget.FeedViewerEntry({"xmethod": this.xmethod});
entryWidget.setTitle(entry.title.value);
entryWidget.setTime(this._displayDateForEntry(entry).toLocaleTimeString());
entryWidget.entrySelectionTopic = this.entrySelectionTopic;
entryWidget.feed = this;
this.addChild(entryWidget);
this.childWidgets.push(entryWidget);
this.connect(entryWidget, "onClick", "_rowSelected");
entry.domNode = entryWidget.entryNode;
//Need to set up a bi-directional reference here to control events between the two.
entry._entryWidget = entryWidget;
entryWidget.entry = entry;
},
deleteEntry: function(/*object*/entryRow){
// summary:
// Function for deleting a row from the view
// description:
// Function for deleting a row from the view
if(!this.localSaveOnly){
this.atomIO.deleteEntry(entryRow.entry, dojo.hitch(this, this._removeEntry, entryRow), null, this.xmethod);
}else{
this._removeEntry(entryRow, true);
}
dojo.publish(this.entrySelectionTopic, [{ action: "delete", source: this, entry: entryRow.entry }]);
},
_removeEntry: function(/*FeedViewerEntry*/ entry, /* boolean */success){
// summary:
// callback for when an entry is deleted from a feed.
// description:
// callback for when an entry is deleted from a feed.
if(success){
/* Check if this is the last Entry beneath the given date */
var idx = dojo.indexOf(this.childWidgets, entry);
var before = this.childWidgets[idx-1];
var after = this.childWidgets[idx+1];
if( before.declaredClass === 'dojox.atom.widget.FeedViewerGrouping' &&
(after === undefined || after.declaredClass === 'dojox.atom.widget.FeedViewerGrouping')){
before.destroy();
}
/* Destroy the FeedViewerEntry to remove it from the view */
entry.destroy();
}else{}
},
_rowSelected: function(/*object*/evt){
// summary:
// Internal function for handling the selection of feed entries.
// description:
// Internal function for handling the selection of feed entries.
//
// evt:
// The click event that triggered a selection.
//
// returns:
// Nothing.
var selectedNode = evt.target;
while(selectedNode){
if(selectedNode.attributes){
var widgetid = selectedNode.attributes.getNamedItem("widgetid");
if(widgetid && widgetid.value.indexOf("FeedViewerEntry") != -1){
break;
}
}
selectedNode = selectedNode.parentNode;
}
for(var i=0;i<this._feed.entries.length;i++){
var entry = this._feed.entries[i];
if( (selectedNode === entry.domNode) && (this._currentSelection !== entry) ){
//Found it and it isn't already selected.
dojo.addClass(entry.domNode, "feedViewerEntrySelected");
dojo.removeClass(entry._entryWidget.timeNode, "feedViewerEntryUpdated");
dojo.addClass(entry._entryWidget.timeNode, "feedViewerEntryUpdatedSelected");
this.onEntrySelected(entry);
if(this.entrySelectionTopic !== ""){
dojo.publish(this.entrySelectionTopic, [{ action: "set", source: this, feed: this._feed, entry: entry }]);
}
if(this._isEditable(entry)){
entry._entryWidget.enableDelete();
}
this._deselectCurrentSelection();
this._currentSelection = entry;
break;
}else if( (selectedNode === entry.domNode) && (this._currentSelection === entry) ){
//Found it and it is the current selection, we just want to de-select it so users can 'unselect rows' if they want.
dojo.publish(this.entrySelectionTopic, [{ action: "delete", source: this, entry: entry }]);
this._deselectCurrentSelection();
break;
}
}
},
_deselectCurrentSelection: function(){
// summary:
// Internal function for unselecting the current selection.
// description:
// Internal function for unselecting the current selection.
//
// returns:
// Nothing.
if(this._currentSelection){
dojo.addClass(this._currentSelection._entryWidget.timeNode, "feedViewerEntryUpdated");
dojo.removeClass(this._currentSelection.domNode, "feedViewerEntrySelected");
dojo.removeClass(this._currentSelection._entryWidget.timeNode, "feedViewerEntryUpdatedSelected");
this._currentSelection._entryWidget.disableDelete();
this._currentSelection = null;
}
},
_isEditable: function(/*object*/entry){
// summary:
// Internal function for determining of a particular entry is editable.
// description:
// Internal function for determining of a particular entry is editable.
// This is used for determining if the delete action should be displayed or not.
//
// entry:
// The dojox.atom.io.model.Entry object to examine
//
// returns:
// Boolean denoting if the entry seems editable or not..
var retVal = false;
if(entry && entry !== null && entry.links && entry.links !== null){
for(var x in entry.links){
if(entry.links[x].rel && entry.links[x].rel == "edit"){
retVal = true;
break;
}
}
}
return retVal;
},
onEntrySelected: function(/*object*/entry){
// summary:
// Function intended for over-riding/replacement as an attachpoint to for other items to recieve
// selection notification.
// description: Function intended for over0-riding/replacement as an attachpoint to for other items to recieve
// selection notification.
//
// entry:
// The dojox.atom.io.model.Entry object selected.
//
// returns:
// Nothing.
},
_isRelativeURL: function(/*string*/url){
// summary:
// Method to determine if the URL is relative or absolute.
// description:
// Method to determine if the URL is relative or absolute. Basic assumption is if it doesn't start
// with http:// or file://, it's relative to the current document.
//
// url:
// The URL to inspect.
//
// returns:
// boolean indicating whether it's a relative url or not.
var isFileURL = function(url){
var retVal = false;
if(url.indexOf("file://") === 0){
retVal = true;
}
return retVal;
}
var isHttpURL = function(url){
var retVal = false;
if(url.indexOf("http://") === 0){
retVal = true;
}
return retVal;
}
var retVal = false;
if(url !== null){
if(!isFileURL(url) && !isHttpURL(url)){
retVal = true;
}
}
return retVal;
},
_calculateBaseURL: function(/*string*/fullURL, /*boolean*/currentPageRelative){
// summary:
// Internal function to calculate a baseline URL from the provided full URL.
// description:
// Internal function to calculate a baseline URL from the provided full URL.
//
// fullURL:
// The full URL as a string.
// currentPageRelative:
// Flag to denote of the base URL should be calculated as just the server base, or relative to the current page/location in the URL.
//
// returns:
// String of the baseline URL
var baseURL = null;
if(fullURL !== null){
//Check to see if we need to strip off any query parameters from the URL.
var index = fullURL.indexOf("?");
if(index != -1){
fullURL = fullURL.substring(0,index);
//console.debug("Removed query parameters. URL now: " + fullURL);
}
if(currentPageRelative){
//Relative to the 'current page' in the URL, so we need to trim that off.
//Now we need to trim if necessary. If it ends in /, then we don't have a filename to trim off
//so we can return.
index = fullURL.lastIndexOf("/");
if((index > 0) && (index < fullURL.length) && (index !== (fullURL.length -1))){
//We want to include the terminating /
baseURL = fullURL.substring(0,(index + 1));
}else{
baseURL = fullURL;
}
}else{
//We want to find the first occurance of / after the <protocol>://
index = fullURL.indexOf("://");
if(index > 0){
index = index + 3;
var protocol = fullURL.substring(0,index);
var fragmentURL = fullURL.substring(index, fullURL.length);
index = fragmentURL.indexOf("/");
if((index < fragmentURL.length) && (index > 0) ){
baseURL = protocol + fragmentURL.substring(0,index);
}else{
baseURL = protocol + fragmentURL;
}
}
}
}
return baseURL;
},
_isFilterAccepted: function(/*object*/entry) {
// summary:
// Internal function to do matching of category filters to widgets.
// description:
// Internal function to do matching of category filters to widgets.
//
// returns:
// boolean denoting if this entry matched one of the accept filters.
var accepted = false;
if (this._includeFilters && (this._includeFilters.length > 0)) {
for (var i = 0; i < this._includeFilters.length; i++) {
var filter = this._includeFilters[i];
if (filter.match(entry)) {
accepted = true;
break;
}
}
}
else {
accepted = true;
}
return accepted;
},
addCategoryIncludeFilter: function(/*object*/filter) {
// summary:
// Function to add a filter for entry inclusion in the feed view.
// description:
// Function to add a filter for entry inclusion in the feed view.
//
// filter:
// The basic items to filter on and the values.
// Should be of format: {scheme: <some text or null>, term: <some text or null>, label: <some text or null>}
//
// returns:
// Nothing.
if (filter) {
var scheme = filter.scheme;
var term = filter.term;
var label = filter.label;
var addIt = true;
if (!scheme) {
scheme = null;
}
if (!term) {
scheme = null;
}
if (!label) {
scheme = null;
}
if (this._includeFilters && this._includeFilters.length > 0) {
for (var i = 0; i < this._includeFilters.length; i++) {
var eFilter = this._includeFilters[i];
if ((eFilter.term === term) && (eFilter.scheme === scheme) && (eFilter.label === label)) {
//Verify we don't have this filter already.
addIt = false;
break;
}
}
}
if (addIt) {
this._includeFilters.push(dojox.atom.widget.FeedViewer.CategoryIncludeFilter(scheme, term, label));
}
}
},
removeCategoryIncludeFilter: function(/*object*/filter) {
// summary:
// Function to remove a filter for entry inclusion in the feed view.
// description:
// Function to remove a filter for entry inclusion in the feed view.
//
// filter:
// The basic items to identify the filter that is present.
// Should be of format: {scheme: <some text or null>, term: <some text or null>, label: <some text or null>}
//
// returns:
// Nothing.
if (filter) {
var scheme = filter.scheme;
var term = filter.term;
var label = filter.label;
if (!scheme) {
scheme = null;
}
if (!term) {
scheme = null;
}
if (!label) {
scheme = null;
}
var newFilters = [];
if (this._includeFilters && this._includeFilters.length > 0) {
for (var i = 0; i < this._includeFilters.length; i++) {
var eFilter = this._includeFilters[i];
if (!((eFilter.term === term) && (eFilter.scheme === scheme) && (eFilter.label === label))) {
//Keep only filters that do not match
newFilters.push(eFilter);
}
}
this._includeFilters = newFilters;
}
}
},
_handleEvent: function(/*object*/entrySelectionEvent) {
// summary:
// Internal function for listening to a topic that will handle entry notification.
// description:
// Internal function for listening to a topic that will handle entry notification.
//
// entrySelectionEvent:
// The topic message containing the entry that was selected for view.
//
// returns:
// Nothing.
if(entrySelectionEvent.source != this) {
if(entrySelectionEvent.action == "update" && entrySelectionEvent.entry) {
var evt = entrySelectionEvent;
if(!this.localSaveOnly){
this.atomIO.updateEntry(evt.entry, dojo.hitch(evt.source,evt.callback), null, true);
}
this._currentSelection._entryWidget.setTime(this._displayDateForEntry(evt.entry).toLocaleTimeString());
this._currentSelection._entryWidget.setTitle(evt.entry.title.value);
} else if(entrySelectionEvent.action == "post" && entrySelectionEvent.entry) {
if(!this.localSaveOnly){
this.atomIO.addEntry(entrySelectionEvent.entry, this.url, dojo.hitch(this,this._addEntry));
}else{
this._addEntry(entrySelectionEvent.entry);
}
}
}
},
_addEntry: function(/*object*/entry) {
// summary:
// callback function used when adding an entry to the feed.
// description:
// callback function used when adding an entry to the feed. After the entry has been posted to the feed,
// we add it to our feed representation (to show it on the page) and publish an event to update any entry viewers.
this._feed.addEntry(entry);
this.setFeed(this._feed);
dojo.publish(this.entrySelectionTopic, [{ action: "set", source: this, feed: this._feed, entry: entry }]);
},
destroy: function(){
// summary:
// Destroys this widget, including all descendants and subscriptions.
// description:
// Destroys this widget, including all descendants and subscriptions.
this.clear();
dojo.forEach(this._subscriptions, dojo.unsubscribe);
}
});
dojo.declare("dojox.atom.widget.FeedViewerEntry",[dijit._Widget, dijit._Templated],{
// summary:
// Widget for handling the display of an entry and specific events associated with it.
// description: Widget for handling the display of an entry and specific events associated with it.
templateString: dojo.cache("dojox.atom", "widget/templates/FeedViewerEntry.html", "<tr class=\"feedViewerEntry\" dojoAttachPoint=\"entryNode\" dojoAttachEvent=\"onclick:onClick\">\n <td class=\"feedViewerEntryUpdated\" dojoAttachPoint=\"timeNode\">\n </td>\n <td>\n <table border=\"0\" width=\"100%\" dojoAttachPoint=\"titleRow\">\n <tr padding=\"0\" border=\"0\">\n <td class=\"feedViewerEntryTitle\" dojoAttachPoint=\"titleNode\">\n </td>\n <td class=\"feedViewerEntryDelete\" align=\"right\">\n <span dojoAttachPoint=\"deleteButton\" dojoAttachEvent=\"onclick:deleteEntry\" class=\"feedViewerDeleteButton\" style=\"display:none;\">[delete]</span>\n </td>\n <tr>\n </table>\n </td>\n</tr>\n"),
entryNode: null,
timeNode: null,
deleteButton: null,
entry: null,
feed: null,
postCreate: function(){
var _nlsResources = dojo.i18n.getLocalization("dojox.atom.widget", "FeedViewerEntry");
this.deleteButton.innerHTML = _nlsResources.deleteButton;
},
setTitle: function(/*string*/text){
// summary:
// Function to set the title of the entry.
// description:
// Function to set the title of the entry.
//
// text:
// The title.
//
// returns:
// Nothing.
if (this.titleNode.lastChild){this.titleNode.removeChild(this.titleNode.lastChild);}
var titleTextNode = document.createElement("div");
titleTextNode.innerHTML = text;
this.titleNode.appendChild(titleTextNode);
},
setTime: function(/*string*/timeText){
// summary:
// Function to set the time of the entry.
// description:
// Function to set the time of the entry.
//
// timeText:
// The string form of the date.
//
// returns:
// Nothing.
if (this.timeNode.lastChild){this.timeNode.removeChild(this.timeNode.lastChild);}
var timeTextNode = document.createTextNode(timeText);
this.timeNode.appendChild(timeTextNode);
},
enableDelete: function(){
// summary:
// Function to enable the delete action on this entry.
// description:
// Function to enable the delete action on this entry.
//
// returns:
// Nothing.
if (this.deleteButton !== null) {
//TODO Fix this
this.deleteButton.style.display = 'inline';
}
},
disableDelete: function(){
// summary:
// Function to disable the delete action on this entry.
// description:
// Function to disable the delete action on this entry.
//
// returns:
// Nothing.
if (this.deleteButton !== null) {
this.deleteButton.style.display = 'none';
}
},
deleteEntry: function(/*object*/event) {
// summary:
// Function to handle the delete event and delete the entry.
// description:
// Function to handle the delete event and delete the entry.
//
// returns:
// Nothing.
event.preventDefault();
event.stopPropagation();
this.feed.deleteEntry(this);
},
onClick: function(/*object*/e){
// summary:
// Attach point for when a row is clicked on.
// description:
// Attach point for when a row is clicked on.
//
// e:
// The event generated by the click.
}
});
dojo.declare("dojox.atom.widget.FeedViewerGrouping",[dijit._Widget, dijit._Templated],{
// summary:
// Grouping of feed entries.
// description:
// Grouping of feed entries.
templateString: dojo.cache("dojox.atom", "widget/templates/FeedViewerGrouping.html", "<tr dojoAttachPoint=\"groupingNode\" class=\"feedViewerGrouping\">\n\t<td colspan=\"2\" dojoAttachPoint=\"titleNode\" class=\"feedViewerGroupingTitle\">\n\t</td>\n</tr>\n"),
groupingNode: null,
titleNode: null,
setText: function(text){
// summary:
// Sets the text to be shown above this grouping.
// description:
// Sets the text to be shown above this grouping.
//
// text:
// The text to show.
if (this.titleNode.lastChild){this.titleNode.removeChild(this.titleNode.lastChild);}
var textNode = document.createTextNode(text);
this.titleNode.appendChild(textNode);
}
});
dojo.declare("dojox.atom.widget.AtomEntryCategoryFilter",[dijit._Widget, dijit._Templated],{
// summary:
// A filter to be applied to the list of entries.
// description:
// A filter to be applied to the list of entries.
scheme: "",
term: "",
label: "",
isFilter: true
});
dojo.declare("dojox.atom.widget.FeedViewer.CategoryIncludeFilter",null,{
constructor: function(scheme, term, label){
// summary:
// The initializer function.
// description:
// The initializer function.
this.scheme = scheme;
this.term = term;
this.label = label;
},
match: function(entry) {
// summary:
// Function to determine if this category filter matches against a category on an atom entry
// description:
// Function to determine if this category filter matches against a category on an atom entry
//
// returns:
// boolean denoting if this category filter matched to this entry.
var matched = false;
if (entry !== null) {
var categories = entry.categories;
if (categories !== null) {
for (var i = 0; i < categories.length; i++) {
var category = categories[i];
if (this.scheme !== "") {
if (this.scheme !== category.scheme) {
break;
}
}
if (this.term !== "") {
if (this.term !== category.term) {
break;
}
}
if (this.label !== "") {
if (this.label !== category.label) {
break;
}
}
//Made it this far, everything matched.
matched = true;
}
}
}
return matched;
}
});
}