FeedPortlet.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /*
  2. Copyright (c) 2004-2012, The Dojo Foundation All Rights Reserved.
  3. Available via Academic Free License >= 2.1 OR the modified BSD license.
  4. see: http://dojotoolkit.org/license for details
  5. */
  6. if(!dojo._hasResource["dojox.widget.FeedPortlet"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
  7. dojo._hasResource["dojox.widget.FeedPortlet"] = true;
  8. dojo.provide("dojox.widget.FeedPortlet");
  9. dojo.require("dojox.widget.Portlet");
  10. dojo.require("dijit.Tooltip");
  11. dojo.require("dijit.form.TextBox");
  12. dojo.require("dijit.form.Button");
  13. dojo.require("dojox.data.GoogleFeedStore");
  14. dojo.declare("dojox.widget.FeedPortlet", dojox.widget.Portlet, {
  15. // summary:
  16. // A Portlet that loads a XML feed.
  17. // description: The feed is displayed as
  18. // an unordered list of links. When a link is hovered over
  19. // by the mouse, it displays a summary in a tooltip.
  20. // local: Boolean
  21. // Specifies whether the feed is to be loaded from the same domain as the
  22. // page, or a remote domain. If local is true, then the feed must be an
  23. // Atom feed. If it is false, it can be an Atom or RSS feed.
  24. local: false,
  25. // maxResults: Number
  26. // The number of results to display from the feed.
  27. maxResults: 5,
  28. // url: String
  29. // The URL of the feed to load. If this is different to the domain
  30. // of the HTML page, local should be set to false.
  31. url: "",
  32. // openNew: Boolean
  33. // If true, when a link is clicked it will open in a new window.
  34. // If false, it will not.
  35. openNew: true,
  36. // useFeedTitle: Boolean
  37. // If true, the title of the loaded feed is displayed in the title bar of the portlet.
  38. // If false, the title remains unchanged.
  39. showFeedTitle: true,
  40. postCreate: function(){
  41. this.inherited(arguments);
  42. if(this.local && !dojox.data.AtomReadStore){
  43. throw Error(this.declaredClass + ": To use local feeds, you must include dojox.data.AtomReadStore on the page.");
  44. }
  45. },
  46. onFeedError: function(){
  47. // summary:
  48. // Called when a feed fails to load successfully.
  49. this.containerNode.innerHTML = "Error accessing the feed."
  50. },
  51. addChild: function(child){
  52. this.inherited(arguments);
  53. var url = child.attr("feedPortletUrl");
  54. if(url){
  55. this.set("url", url);
  56. }
  57. },
  58. _getTitle: function(item){
  59. // summary:
  60. // Gets the title of a feed item.
  61. var t = this.store.getValue(item, "title");
  62. return this.local ? t.text : t;
  63. },
  64. _getLink: function(item){
  65. // summary:
  66. // Gets the href link of a feed item.
  67. var l = this.store.getValue(item, "link");
  68. return this.local ? l.href : l;
  69. },
  70. _getContent: function(item){
  71. // summary:
  72. // Gets the summary of a feed item.
  73. var c = this.store.getValue(item, "summary");
  74. if(!c){
  75. return null;
  76. }
  77. if(this.local){
  78. c = c.text;
  79. }
  80. // Filter out any sneaky scripts in the code
  81. c = c.split("<script").join("<!--").split("</script>").join("-->");
  82. c = c.split("<iframe").join("<!--").split("</iframe>").join("-->");
  83. return c;
  84. },
  85. _setUrlAttr: function(url){
  86. // summary:
  87. // Sets the URL to load.
  88. this.url = url;
  89. if(this._started){
  90. this.load();
  91. }
  92. },
  93. startup: function(){
  94. // summary:
  95. // Loads the widget.
  96. if(this.started || this._started){return;}
  97. this.inherited(arguments);
  98. if(!this.url || this.url == ""){
  99. throw new Error(this.id + ": A URL must be specified for the feed portlet");
  100. }
  101. if(this.url && this.url != ""){
  102. this.load();
  103. }
  104. },
  105. load: function(){
  106. // summary:
  107. // Loads the feed.
  108. if(this._resultList){
  109. dojo.destroy(this._resultList);
  110. }
  111. var store, query;
  112. // If the feed is on the same domain, use the AtomReadStore,
  113. // as we cannot be guaranteed that it will be available to
  114. // Google services.
  115. if(this.local){
  116. store = new dojox.data.AtomReadStore({
  117. url: this.url
  118. });
  119. query = {};
  120. }else{
  121. store = new dojox.data.GoogleFeedStore();
  122. query = {url: this.url};
  123. }
  124. var request = {
  125. query: query,
  126. count: this.maxResults,
  127. onComplete: dojo.hitch(this, function(items){
  128. if (this.showFeedTitle && store.getFeedValue) {
  129. var title = this.store.getFeedValue("title");
  130. if(title){
  131. this.set("title", title.text ? title.text : title);
  132. }
  133. }
  134. this.generateResults(items);
  135. }),
  136. onError: dojo.hitch(this, "onFeedError")
  137. };
  138. this.store = store;
  139. store.fetch(request);
  140. },
  141. generateResults: function (items){
  142. // summary:
  143. // Generates a list of hyperlinks and displays a tooltip
  144. // containing a summary when the mouse hovers over them.
  145. var store = this.store;
  146. var timer;
  147. var ul = (this._resultList =
  148. dojo.create("ul", {"class" : "dojoxFeedPortletList"}, this.containerNode));
  149. dojo.forEach(items, dojo.hitch(this, function(item){
  150. var li = dojo.create("li", {
  151. innerHTML: '<a href="'
  152. + this._getLink(item)
  153. + '"'
  154. + (this.openNew ? ' target="_blank"' : '')
  155. +'>'
  156. + this._getTitle(item) + '</a>'
  157. },ul);
  158. dojo.connect(li, "onmouseover", dojo.hitch(this, function(evt){
  159. if(timer){
  160. clearTimeout(timer);
  161. }
  162. // Show the tooltip after the mouse has been hovering
  163. // for a short time.
  164. timer = setTimeout(dojo.hitch(this, function(){
  165. timer = null;
  166. var summary = this._getContent(item);
  167. if(!summary){return;}
  168. var content = '<div class="dojoxFeedPortletPreview">'
  169. + summary + '</div>'
  170. dojo.query("li", ul).forEach(function(item){
  171. if(item != evt.target){
  172. dijit.hideTooltip(item);
  173. }
  174. });
  175. // Hover the tooltip over the anchor tag
  176. dijit.showTooltip(content, li.firstChild, !this.isLeftToRight());
  177. }), 500);
  178. }));
  179. // Hide the tooltip when the mouse leaves a list item.
  180. dojo.connect(li, "onmouseout", function(){
  181. if(timer){
  182. clearTimeout(timer);
  183. timer = null;
  184. }
  185. dijit.hideTooltip(li.firstChild);
  186. });
  187. }));
  188. this.resize();
  189. }
  190. });
  191. dojo.declare("dojox.widget.ExpandableFeedPortlet", dojox.widget.FeedPortlet, {
  192. // summary:
  193. // A FeedPortlet that uses an list of expandable links to display
  194. // a feed. An icon is placed to the left of each item
  195. // which, when clicked, toggles the visible state
  196. // of the item summary.
  197. // onlyOpenOne: Boolean
  198. // If true, only a single item can be expanded at any given time.
  199. onlyOpenOne: false,
  200. generateResults: function(items){
  201. // summary:
  202. // Generates a list of items, and places an icon beside them that
  203. // can be used to show or hide a summary of that item.
  204. var store = this.store;
  205. var iconCls = "dojoxPortletToggleIcon";
  206. var collapsedCls = "dojoxPortletItemCollapsed";
  207. var expandedCls = "dojoxPortletItemOpen";
  208. var timer;
  209. var ul = (this._resultList = dojo.create("ul", {
  210. "class": "dojoxFeedPortletExpandableList"
  211. }, this.containerNode));
  212. // Create the LI elements. Each LI has two DIV elements, the
  213. // top DIV contains the toggle icon and title, and the bottom
  214. // div contains the extended summary.
  215. dojo.forEach(items, dojo.hitch(this, dojo.hitch(this, function(item){
  216. var li = dojo.create("li", {"class": collapsedCls}, ul);
  217. var upper = dojo.create("div", {style: "width: 100%;"}, li);
  218. var lower = dojo.create("div", {"class": "dojoxPortletItemSummary", innerHTML: this._getContent(item)}, li);
  219. dojo.create("span", {
  220. "class": iconCls,
  221. innerHTML: "<img src='" + dojo.config.baseUrl + "/resources/blank.gif'>"}, upper);
  222. var a = dojo.create("a", {href: this._getLink(item), innerHTML: this._getTitle(item) }, upper);
  223. if(this.openNew){
  224. dojo.attr(a, "target", "_blank");
  225. }
  226. })));
  227. // Catch all clicks on the list. If a toggle icon is clicked,
  228. // toggle the visible state of the summary DIV.
  229. dojo.connect(ul, "onclick", dojo.hitch(this, function(evt){
  230. if(dojo.hasClass(evt.target, iconCls) || dojo.hasClass(evt.target.parentNode, iconCls)){
  231. dojo.stopEvent(evt);
  232. var li = evt.target.parentNode;
  233. while(li.tagName != "LI"){
  234. li = li.parentNode;
  235. }
  236. if(this.onlyOpenOne){
  237. dojo.query("li", ul).filter(function(item){
  238. return item != li;
  239. }).removeClass(expandedCls).addClass(collapsedCls);
  240. }
  241. var isExpanded = dojo.hasClass(li, expandedCls);
  242. dojo.toggleClass(li, expandedCls, !isExpanded);
  243. dojo.toggleClass(li, collapsedCls, isExpanded);
  244. }
  245. }));
  246. }
  247. });
  248. dojo.declare("dojox.widget.PortletFeedSettings",
  249. dojox.widget.PortletSettings, {
  250. // summary:
  251. // A Settings widget designed to be used with a dojox.widget.FeedPortlet
  252. // description:
  253. // It provides form items that the user can use to change the URL
  254. // for a feed to load into the FeedPortlet.
  255. // There are two forms that it can take. <br>
  256. // The first is to display a text field, with Load and Cancel buttons,
  257. // which is prepopulated with the enclosing FeedPortlet's URL.
  258. // If a <select> DOM node is used as the source node for this widget,
  259. // it displays a list of predefined URLs that the user can select from
  260. // to load into the enclosing FeedPortlet.
  261. //
  262. // example:
  263. // <div dojoType="dojox.widget.PortletFeedSettings"></div>
  264. //
  265. // example:
  266. // <select dojoType="dojox.widget.PortletFeedSettings">
  267. // <option>http://www.dojotoolkit.org/aggregator/rss</option>
  268. // <option>http://dojocampus.org/content/category/podcast/feed/</option>
  269. // </select>
  270. "class" : "dojoxPortletFeedSettings",
  271. // urls: Array
  272. // An array of JSON object specifying URLs to display in the
  273. // PortletFeedSettings object. Each object contains a 'url' and 'label'
  274. // attribute, e.g.
  275. // [{url:'http:google.com', label:'Google'}, {url:'http://dojotoolkit.org', label: 'Dojo'}]
  276. urls: null,
  277. // selectedIndex: Number
  278. // The selected URL. Defaults to zero.
  279. selectedIndex: 0,
  280. buildRendering: function(){
  281. // If JSON URLs have been specified, create a SELECT DOM node,
  282. // and insert the required OPTION elements.
  283. var s;
  284. if(this.urls && this.urls.length > 0){
  285. console.log(this.id + " -> creating select with urls ", this.urls)
  286. s = dojo.create("select");
  287. if(this.srcNodeRef){
  288. dojo.place(s, this.srcNodeRef, "before");
  289. dojo.destroy(this.srcNodeRef);
  290. }
  291. this.srcNodeRef = s;
  292. dojo.forEach(this.urls, function(url){
  293. dojo.create("option", {value: url.url || url, innerHTML: url.label || url}, s);
  294. });
  295. }
  296. // If the srcNodeRef is a SELECT node, then replace it with a DIV, and insert
  297. // the SELECT node into that div.
  298. if(this.srcNodeRef.tagName == "SELECT"){
  299. this.text = this.srcNodeRef;
  300. var div = dojo.create("div", {}, this.srcNodeRef, "before");
  301. div.appendChild(this.text);
  302. this.srcNodeRef = div;
  303. dojo.query("option", this.text).filter("return !item.value;").forEach("item.value = item.innerHTML");
  304. if(!this.text.value){
  305. if(this.content && this.text.options.length == 0){
  306. this.text.appendChild(this.content);
  307. }
  308. dojo.attr(s || this.text, "value", this.text.options[this.selectedIndex].value);
  309. }
  310. }
  311. this.inherited(arguments);
  312. },
  313. _setContentAttr: function(){
  314. },
  315. postCreate: function(){
  316. console.log(this.id + " -> postCreate");
  317. if(!this.text){
  318. // If a select node is not being used, create a new TextBox to
  319. // edit the URL.
  320. var text = this.text = new dijit.form.TextBox({});
  321. dojo.create("span", {
  322. innerHTML: "Choose Url: "
  323. }, this.domNode);
  324. this.addChild(text);
  325. }
  326. // Add a LOAD button
  327. this.addChild(new dijit.form.Button({
  328. label: "Load",
  329. onClick: dojo.hitch(this, function(){
  330. // Set the URL of the containing Portlet with the selected URL.
  331. this.portlet.attr("url",
  332. (this.text.tagName == "SELECT") ? this.text.value : this.text.attr('value'));
  333. if(this.text.tagName == "SELECT"){
  334. // Set the selected index on the Select node.
  335. dojo.some(this.text.options, dojo.hitch(this, function(opt, idx){
  336. if(opt.selected){
  337. this.set("selectedIndex", idx);
  338. return true;
  339. }
  340. return false;
  341. }));
  342. }
  343. // Hide the widget.
  344. this.toggle();
  345. })
  346. }));
  347. // Add a CANCEL button, which hides this widget
  348. this.addChild(new dijit.form.Button({
  349. label: "Cancel",
  350. onClick: dojo.hitch(this, "toggle")
  351. }));
  352. this.inherited(arguments);
  353. },
  354. startup: function(){
  355. // summary:
  356. // Sets the portlet associated with this PortletSettings object.
  357. if(this._started){return;}
  358. console.log(this.id + " -> startup");
  359. this.inherited(arguments);
  360. if(!this.portlet){
  361. throw Error(this.declaredClass + ": A PortletFeedSettings widget cannot exist without a Portlet.");
  362. }
  363. if(this.text.tagName == "SELECT"){
  364. // Set the initial selected option.
  365. dojo.forEach(this.text.options, dojo.hitch(this, function(opt, index){
  366. dojo.attr(opt, "selected", index == this.selectedIndex);
  367. }));
  368. }
  369. var url = this.portlet.attr("url");
  370. if(url){
  371. // If a SELECT node is used to choose a URL, ensure that the Portlet's URL
  372. // is one of the options.
  373. if(this.text.tagName == "SELECT"){
  374. if(!this.urls && dojo.query("option[value='" + url + "']", this.text).length < 1){
  375. dojo.place(dojo.create("option", {
  376. value: url,
  377. innerHTML: url,
  378. selected: "true"
  379. }), this.text, "first");
  380. }
  381. }else{
  382. this.text.attr("value", url);
  383. }
  384. }else{
  385. this.portlet.attr("url", this.get("feedPortletUrl"));
  386. }
  387. },
  388. _getFeedPortletUrlAttr: function(){
  389. return this.text.value;
  390. }
  391. });
  392. }