jquery.ticker.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. /*
  2. jQuery News Ticker is free software: you can redistribute it and/or modify
  3. it under the terms of the GNU General Public License as published by
  4. the Free Software Foundation, version 2 of the License.
  5. jQuery News Ticker is distributed in the hope that it will be useful,
  6. but WITHOUT ANY WARRANTY; without even the implied warranty of
  7. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  8. GNU General Public License for more details.
  9. You should have received a copy of the GNU General Public License
  10. along with jQuery News Ticker. If not, see <http://www.gnu.org/licenses/>.
  11. */
  12. (function($){
  13. $.fn.ticker = function(options) {
  14. // Extend our default options with those provided.
  15. // Note that the first arg to extend is an empty object -
  16. // this is to keep from overriding our "defaults" object.
  17. var opts = $.extend({}, $.fn.ticker.defaults, options);
  18. // check that the passed element is actually in the DOM
  19. if ($(this).length == 0) {
  20. if (window.console && window.console.log) {
  21. window.console.log('Element does not exist in DOM!');
  22. }
  23. else {
  24. alert('Element does not exist in DOM!');
  25. }
  26. return false;
  27. }
  28. /* Get the id of the UL to get our news content from */
  29. var newsID = '#' + $(this).attr('id');
  30. /* Get the tag type - we will check this later to makde sure it is a UL tag */
  31. var tagType = $(this).get(0).tagName;
  32. return this.each(function() {
  33. // get a unique id for this ticker
  34. var uniqID = getUniqID();
  35. /* Internal vars */
  36. var settings = {
  37. position: 0,
  38. time: 0,
  39. distance: 0,
  40. newsArr: {},
  41. play: true,
  42. paused: false,
  43. contentLoaded: false,
  44. dom: {
  45. contentID: '#ticker-content-' + uniqID,
  46. titleID: '#ticker-title-' + uniqID,
  47. titleElem: '#ticker-title-' + uniqID + ' SPAN',
  48. tickerID : '#ticker-' + uniqID,
  49. wrapperID: '#ticker-wrapper-' + uniqID,
  50. revealID: '#ticker-swipe-' + uniqID,
  51. revealElem: '#ticker-swipe-' + uniqID + ' SPAN',
  52. controlsID: '#ticker-controls-' + uniqID,
  53. prevID: '#prev-' + uniqID,
  54. nextID: '#next-' + uniqID,
  55. playPauseID: '#play-pause-' + uniqID
  56. }
  57. };
  58. // if we are not using a UL, display an error message and stop any further execution
  59. if (tagType != 'UL' && tagType != 'OL' && opts.htmlFeed === true) {
  60. debugError('Cannot use <' + tagType.toLowerCase() + '> type of element for this plugin - must of type <ul> or <ol>');
  61. return false;
  62. }
  63. // set the ticker direction
  64. opts.direction == 'rtl' ? opts.direction = 'right' : opts.direction = 'left';
  65. // lets go...
  66. initialisePage();
  67. /* Function to get the size of an Object*/
  68. function countSize(obj) {
  69. var size = 0, key;
  70. for (key in obj) {
  71. if (obj.hasOwnProperty(key)) size++;
  72. }
  73. return size;
  74. };
  75. function getUniqID() {
  76. var newDate = new Date;
  77. return newDate.getTime();
  78. }
  79. /* Function for handling debug and error messages */
  80. function debugError(obj) {
  81. if (opts.debugMode) {
  82. if (window.console && window.console.log) {
  83. window.console.log(obj);
  84. }
  85. else {
  86. alert(obj);
  87. }
  88. }
  89. }
  90. /* Function to setup the page */
  91. function initialisePage() {
  92. // process the content for this ticker
  93. processContent();
  94. // add our HTML structure for the ticker to the DOM
  95. $(newsID).wrap('<div id="' + settings.dom.wrapperID.replace('#', '') + '"></div>');
  96. // remove any current content inside this ticker
  97. $(settings.dom.wrapperID).children().remove();
  98. $(settings.dom.wrapperID).append('<div id="' + settings.dom.tickerID.replace('#', '') + '" class="ticker"><div id="' + settings.dom.titleID.replace('#', '') + '" class="ticker-title"><span><!-- --></span></div><p id="' + settings.dom.contentID.replace('#', '') + '" class="ticker-content"></p><div id="' + settings.dom.revealID.replace('#', '') + '" class="ticker-swipe"><span><!-- --></span></div></div>');
  99. $(settings.dom.wrapperID).removeClass('no-js').addClass('ticker-wrapper has-js ' + opts.direction);
  100. // hide the ticker
  101. $(settings.dom.tickerElem + ',' + settings.dom.contentID).hide();
  102. // add the controls to the DOM if required
  103. if (opts.controls) {
  104. // add related events - set functions to run on given event
  105. $(settings.dom.controlsID).live('click mouseover mousedown mouseout mouseup', function (e) {
  106. var button = e.target.id;
  107. if (e.type == 'click') {
  108. switch (button) {
  109. case settings.dom.prevID.replace('#', ''):
  110. // show previous item
  111. settings.paused = true;
  112. $(settings.dom.playPauseID).addClass('paused');
  113. manualChangeContent('prev');
  114. break;
  115. case settings.dom.nextID.replace('#', ''):
  116. // show next item
  117. settings.paused = true;
  118. $(settings.dom.playPauseID).addClass('paused');
  119. manualChangeContent('next');
  120. break;
  121. case settings.dom.playPauseID.replace('#', ''):
  122. // play or pause the ticker
  123. if (settings.play == true) {
  124. settings.paused = true;
  125. $(settings.dom.playPauseID).addClass('paused');
  126. pauseTicker();
  127. }
  128. else {
  129. settings.paused = false;
  130. $(settings.dom.playPauseID).removeClass('paused');
  131. restartTicker();
  132. }
  133. break;
  134. }
  135. }
  136. else if (e.type == 'mouseover' && $('#' + button).hasClass('controls')) {
  137. $('#' + button).addClass('over');
  138. }
  139. else if (e.type == 'mousedown' && $('#' + button).hasClass('controls')) {
  140. $('#' + button).addClass('down');
  141. }
  142. else if (e.type == 'mouseup' && $('#' + button).hasClass('controls')) {
  143. $('#' + button).removeClass('down');
  144. }
  145. else if (e.type == 'mouseout' && $('#' + button).hasClass('controls')) {
  146. $('#' + button).removeClass('over');
  147. }
  148. });
  149. // add controls HTML to DOM
  150. $(settings.dom.wrapperID).append('<ul id="' + settings.dom.controlsID.replace('#', '') + '" class="ticker-controls"><li id="' + settings.dom.playPauseID.replace('#', '') + '" class="jnt-play-pause controls"><a href=""><!-- --></a></li><li id="' + settings.dom.prevID.replace('#', '') + '" class="jnt-prev controls"><a href=""><!-- --></a></li><li id="' + settings.dom.nextID.replace('#', '') + '" class="jnt-next controls"><a href=""><!-- --></a></li></ul>');
  151. }
  152. if (opts.displayType != 'fade') {
  153. // add mouse over on the content
  154. $(settings.dom.contentID).mouseover(function () {
  155. if (settings.paused == false) {
  156. pauseTicker();
  157. }
  158. }).mouseout(function () {
  159. if (settings.paused == false) {
  160. restartTicker();
  161. }
  162. });
  163. }
  164. // we may have to wait for the ajax call to finish here
  165. if (!opts.ajaxFeed) {
  166. setupContentAndTriggerDisplay();
  167. }
  168. }
  169. /* Start to process the content for this ticker */
  170. function processContent() {
  171. // check to see if we need to load content
  172. if (settings.contentLoaded == false) {
  173. // construct content
  174. if (opts.ajaxFeed) {
  175. if (opts.feedType == 'xml') {
  176. $.ajax({
  177. url: opts.feedUrl,
  178. cache: false,
  179. dataType: opts.feedType,
  180. async: true,
  181. success: function(data){
  182. count = 0;
  183. // get the 'root' node
  184. for (var a = 0; a < data.childNodes.length; a++) {
  185. if (data.childNodes[a].nodeName == 'rss') {
  186. xmlContent = data.childNodes[a];
  187. }
  188. }
  189. // find the channel node
  190. for (var i = 0; i < xmlContent.childNodes.length; i++) {
  191. if (xmlContent.childNodes[i].nodeName == 'channel') {
  192. xmlChannel = xmlContent.childNodes[i];
  193. }
  194. }
  195. // for each item create a link and add the article title as the link text
  196. for (var x = 0; x < xmlChannel.childNodes.length; x++) {
  197. if (xmlChannel.childNodes[x].nodeName == 'item') {
  198. xmlItems = xmlChannel.childNodes[x];
  199. var title, link = false;
  200. for (var y = 0; y < xmlItems.childNodes.length; y++) {
  201. if (xmlItems.childNodes[y].nodeName == 'title') {
  202. title = xmlItems.childNodes[y].lastChild.nodeValue;
  203. }
  204. else if (xmlItems.childNodes[y].nodeName == 'link') {
  205. link = xmlItems.childNodes[y].lastChild.nodeValue;
  206. }
  207. if ((title !== false && title != '') && link !== false) {
  208. settings.newsArr['item-' + count] = { type: opts.titleText, content: '<a href="' + link + '">' + title + '</a>' }; count++; title = false; link = false;
  209. }
  210. }
  211. }
  212. }
  213. // quick check here to see if we actually have any content - log error if not
  214. if (countSize(settings.newsArr < 1)) {
  215. debugError('Couldn\'t find any content from the XML feed for the ticker to use!');
  216. return false;
  217. }
  218. settings.contentLoaded = true;
  219. setupContentAndTriggerDisplay();
  220. }
  221. });
  222. }
  223. else {
  224. debugError('Code Me!');
  225. }
  226. }
  227. else if (opts.htmlFeed) {
  228. if($(newsID + ' LI').length > 0) {
  229. $(newsID + ' LI').each(function (i) {
  230. // maybe this could be one whole object and not an array of objects?
  231. settings.newsArr['item-' + i] = { type: opts.titleText, content: $(this).html()};
  232. });
  233. }
  234. else {
  235. debugError('Couldn\'t find HTML any content for the ticker to use!');
  236. return false;
  237. }
  238. }
  239. else {
  240. debugError('The ticker is set to not use any types of content! Check the settings for the ticker.');
  241. return false;
  242. }
  243. }
  244. }
  245. function setupContentAndTriggerDisplay() {
  246. settings.contentLoaded = true;
  247. // update the ticker content with the correct item
  248. // insert news content into DOM
  249. $(settings.dom.titleElem).html(settings.newsArr['item-' + settings.position].type);
  250. $(settings.dom.contentID).html(settings.newsArr['item-' + settings.position].content);
  251. // set the next content item to be used - loop round if we are at the end of the content
  252. if (settings.position == (countSize(settings.newsArr) -1)) {
  253. settings.position = 0;
  254. }
  255. else {
  256. settings.position++;
  257. }
  258. // get the values of content and set the time of the reveal (so all reveals have the same speed regardless of content size)
  259. distance = $(settings.dom.contentID).width();
  260. time = distance / opts.speed;
  261. // start the ticker animation
  262. revealContent();
  263. }
  264. // slide back cover or fade in content
  265. function revealContent() {
  266. $(settings.dom.contentID).css('opacity', '1');
  267. if(settings.play) {
  268. // get the width of the title element to offset the content and reveal
  269. var offset = $(settings.dom.titleID).width() + 20;
  270. $(settings.dom.revealID).css(opts.direction, offset + 'px');
  271. // show the reveal element and start the animation
  272. if (opts.displayType == 'fade') {
  273. // fade in effect ticker
  274. $(settings.dom.revealID).hide(0, function () {
  275. $(settings.dom.contentID).css(opts.direction, offset + 'px').fadeIn(opts.fadeInSpeed, postReveal);
  276. });
  277. }
  278. else if (opts.displayType == 'scroll') {
  279. // to code
  280. }
  281. else {
  282. // default bbc scroll effect
  283. $(settings.dom.revealElem).show(0, function () {
  284. $(settings.dom.contentID).css(opts.direction, offset + 'px').show();
  285. // set our animation direction
  286. animationAction = opts.direction == 'right' ? { marginRight: distance + 'px'} : { marginLeft: distance + 'px' };
  287. $(settings.dom.revealID).css('margin-' + opts.direction, '0px').delay(20).animate(animationAction, time, 'linear', postReveal);
  288. });
  289. }
  290. }
  291. else {
  292. return false;
  293. }
  294. };
  295. // here we hide the current content and reset the ticker elements to a default state ready for the next ticker item
  296. function postReveal() {
  297. if(settings.play) {
  298. // we have to separately fade the content out here to get around an IE bug - needs further investigation
  299. $(settings.dom.contentID).delay(opts.pauseOnItems).fadeOut(opts.fadeOutSpeed);
  300. // deal with the rest of the content, prepare the DOM and trigger the next ticker
  301. if (opts.displayType == 'fade') {
  302. $(settings.dom.contentID).fadeOut(opts.fadeOutSpeed, function () {
  303. $(settings.dom.wrapperID)
  304. .find(settings.dom.revealElem + ',' + settings.dom.contentID)
  305. .hide()
  306. .end().find(settings.dom.tickerID + ',' + settings.dom.revealID)
  307. .show()
  308. .end().find(settings.dom.tickerID + ',' + settings.dom.revealID)
  309. .removeAttr('style');
  310. setupContentAndTriggerDisplay();
  311. });
  312. }
  313. else {
  314. $(settings.dom.revealID).hide(0, function () {
  315. $(settings.dom.contentID).fadeOut(opts.fadeOutSpeed, function () {
  316. $(settings.dom.wrapperID)
  317. .find(settings.dom.revealElem + ',' + settings.dom.contentID)
  318. .hide()
  319. .end().find(settings.dom.tickerID + ',' + settings.dom.revealID)
  320. .show()
  321. .end().find(settings.dom.tickerID + ',' + settings.dom.revealID)
  322. .removeAttr('style');
  323. setupContentAndTriggerDisplay();
  324. });
  325. });
  326. }
  327. }
  328. else {
  329. $(settings.dom.revealElem).hide();
  330. }
  331. }
  332. // pause ticker
  333. function pauseTicker() {
  334. settings.play = false;
  335. // stop animation and show content - must pass "true, true" to the stop function, or we can get some funky behaviour
  336. $(settings.dom.tickerID + ',' + settings.dom.revealID + ',' + settings.dom.titleID + ',' + settings.dom.titleElem + ',' + settings.dom.revealElem + ',' + settings.dom.contentID).stop(true, true);
  337. $(settings.dom.revealID + ',' + settings.dom.revealElem).hide();
  338. $(settings.dom.wrapperID)
  339. .find(settings.dom.titleID + ',' + settings.dom.titleElem).show()
  340. .end().find(settings.dom.contentID).show();
  341. }
  342. // play ticker
  343. function restartTicker() {
  344. settings.play = true;
  345. settings.paused = false;
  346. // start the ticker again
  347. postReveal();
  348. }
  349. // change the content on user input
  350. function manualChangeContent(direction) {
  351. pauseTicker();
  352. switch (direction) {
  353. case 'prev':
  354. if (settings.position == 0) {
  355. settings.position = countSize(settings.newsArr) -2;
  356. }
  357. else if (settings.position == 1) {
  358. settings.position = countSize(settings.newsArr) -1;
  359. }
  360. else {
  361. settings.position = settings.position - 2;
  362. }
  363. $(settings.dom.titleElem).html(settings.newsArr['item-' + settings.position].type);
  364. $(settings.dom.contentID).html(settings.newsArr['item-' + settings.position].content);
  365. break;
  366. case 'next':
  367. $(settings.dom.titleElem).html(settings.newsArr['item-' + settings.position].type);
  368. $(settings.dom.contentID).html(settings.newsArr['item-' + settings.position].content);
  369. break;
  370. }
  371. // set the next content item to be used - loop round if we are at the end of the content
  372. if (settings.position == (countSize(settings.newsArr) -1)) {
  373. settings.position = 0;
  374. }
  375. else {
  376. settings.position++;
  377. }
  378. }
  379. });
  380. };
  381. // plugin defaults - added as a property on our plugin function
  382. $.fn.ticker.defaults = {
  383. speed: 0.10,
  384. ajaxFeed: false,
  385. feedUrl: '',
  386. feedType: 'xml',
  387. displayType: 'reveal',
  388. htmlFeed: true,
  389. debugMode: true,
  390. controls: true,
  391. titleText: 'Latest',
  392. direction: 'ltr',
  393. pauseOnItems: 3000,
  394. fadeInSpeed: 600,
  395. fadeOutSpeed: 300
  396. };
  397. })(jQuery);