1// Module pattern 2(function( $ ) { 3 4 // Backwards compatibility 5 if ( $.fn.on === undefined ) { 6 $.fn.on = function( event, fn ) { 7 this.bind( event, fn ); 8 } 9 } 10 11 // Backwards compatibility 12 if ( $.fn.off === undefined ) { 13 $.fn.off = function( event, fn ) { 14 return this.unbind( event, fn ); 15 } 16 } 17 18 // The plugin wrapper 19 $.fn.newsticker = function( options ) { 20 var self = this; 21 22 self.methods = $.fn.newsticker.methods; 23 self.api = $.fn.newsticker.api; 24 self.delegate = $.fn.newsticker.delegate; 25 self.settings = $.extend( {}, $.fn.newsticker.defaults, options); 26 27 // Method call return 28 return this.each( function() { 29 self.api( 'init', $(this) ); 30 }); 31 } 32 33 // Default newsticker settings 34 $.fn.newsticker.defaults = { 35 style : 'scroll', 36 showControls : true, 37 tickerTitle : '#Juanes', 38 feedItems : 10, 39 feeds : 'https://search.twitter.com/search.atom?q=%23Juanes', 40 feedFormat : '%title% Posteado por %author%', 41 noCache : false, 42 twitter : '', 43 twitterFormat : '%excerpt% Posteado en <a href="%link%">Twitter</a> %timeago%', 44 twitterCount : '', 45 parseTwitterLinks : true, 46 openLinksInNewWindow: false, 47 facebook : '', 48 facebookFormat : '%excerpt%... Posteado en <a href="%link%">Facebook</a> %timeago%', 49 facebookCount : '', 50 lastfm : '', 51 lastfmFormat : '<a href="%link%">%title%</a> listened to %timeago%', 52 lastfmCount : '', 53 tumblr : '', 54 tumblrFormat : '<a href="%link%">%title%</a> posted by %author%, %timeago%', 55 tumblrCount : '', 56 pinterest : '', 57 pinterestFormat : '<a href="%item_link%">%excerpt%</a> pinned %timeago%', 58 pinterestCount : '', 59 excerptLength : 100, 60 sortBy : 'timestamp', 61 reverseOrder : false, 62 autoStart : true, 63 timeAgoFormat : function( date ) { 64 return $.fn.newsticker.methods.getTimeAgo( date ); 65 }, 66 dateFormat : function( date ) { 67 return $.fn.newsticker.methods.getFormattedDate( date ); 68 } 69 } 70 71 // An interface to call style methods 72 $.fn.newsticker.delegate = function( name ) { 73 var style_method = this.style[name], 74 args = Array.prototype.slice.call(arguments, 1); 75 76 if ( typeof( style_method ) === 'function' ) { 77 return style_method.apply( this, args ); 78 } 79 return false; 80 } 81 82 // An interface to call general newsticker methods 83 $.fn.newsticker.api = function( method ) { 84 var api_method = this.methods[method], 85 args = Array.prototype.slice.call(arguments, 1); 86 87 if ( typeof( api_method ) === 'function' ) { 88 return api_method.apply( this, args ); 89 } 90 return false; 91 } 92 93 // Global newsticker methods 94 $.fn.newsticker.methods = { 95 96 // Get unique newsticker ID 97 getId : function() { 98 var newDate = new Date; 99 return newDate.getTime(); 100 }, 101 102 // Once-off initialisation routine 103 init : function( element ) { 104 105 // Validate newsticker style and load if valid 106 if ( typeof( $.fn.newsticker.styles[ this.settings.style] ) === 'object' ) { 107 this.style = $.fn.newsticker.styles[ this.settings.style ]; 108 } 109 else { 110 console.log( 'ERROR: Invalid display style.' ); 111 return; 112 } 113 114 // Update settings with defaults from newsticker style object
115 this.settings = $.extend( this.delegate( 'settings' ), this.settings); 116 117 // Start style initialization 118 this.delegate( 'init' ); 119 120 // Create dom object 121 this.dom = {}; 122 this.dom.list = element; 123 124 // Compile feeds array. Check for 'sugar' feeds -- Twitter, Facebook page, Last.fm, Googe Plus 125 this.api( 'getFeeds' ); 126 127 if (this.feeds.length === 0 128 && this.settings.twitter.length === 0 129 && this.settings.facebook.length === 0 130 && this.settings.lastfm.length === 0 131 && this.settings.tumblr.length === 0 132 && this.settings.pinterest.length === 0) { 133 // List items 134 this.dom.items = this.dom.list.children('li'); 135 136 // Startup 137 this.api( 'startup' ); 138 } 139 }, 140 141 // Startup. This is a sort of second init, called only when all the list items are loaded 142 startup : function() { 143 var self = this, 144 dom = this.dom, 145 settings = this.settings, 146 list = dom.list; 147 148 // Make dom modifications -- this is applied to all newsticker instances, regardless of style 149 // Wrap the list inside a div, and insert a title before it 150 list.wrap('<div class="newsticker_wrapper newsticker_style_'+settings.style+'" id="newsticker_'+this.api( 'getId' )+'" style="position: relative; z-index: 150; overflow: hidden; "></div>'); 151 152 // Insert title if set 153 if (settings.tickerTitle.length > 0) { 154 $('<div class="newsticker_title"><h4>'+settings.tickerTitle+'</h4></div>').insertBefore(list); 155 } 156 157 // Save both of the new dom elements 158 dom.wrapper = list.parent(); 159 dom.title = dom.wrapper.children('.newsticker_title'); 160 161 // Add controls if set to true 162 if (settings.showControls === true) { 163 var pause_or_resume = settings.autoStart === false ? 'resume' : 'pause', 164 controls; 165 166 $('<ul class="newsticker_controls">\ 167 <li class="previous"></li>\ 168 <li class="'+pause_or_resume+'"></li>\ 169 <li class="next"></li>\ 170 </ul>').insertAfter(list); 171 172 controls = list.next('.newsticker_controls'); 173 174 controls.children('.previous').on('click', function() { 175 self.delegate( 'previous' ); 176 }); 177 controls.children('.pause').on('click', function() { 178 self.delegate( 'pause' ); 179 }); 180 controls.children('.resume').on('click', function() { 181 self.delegate( 'resume' ); 182 }); 183 controls.children('.next').on('click', function() { 184 self.delegate( 'next' ); 185 }); 186 } 187 188 // Save controls 189 dom.controls = controls === undefined ? dom.wrapper.children('.newsticker_controls') : controls; 190 191 // Pause animation on hover if set to true 192 if (settings.pauseOnHover === true) { 193 dom.items.on('mouseover', function(){ self.delegate( 'pause' ); }); 194 dom.items.on('mouseout', function(){ self.delegate( 'resume' ); }); 195 } 196 197 // Handle window resize 198 $(window).resize( function() { 199 self.api( 'resize' ); 200 }); 201 202 // Allow styles to make their own DOM manipulations 203 this.delegate( 'manipulateDom' ); 204 205 // Loop through individual items 206 this.delegate( 'itemLoop' ); 207 208 // Start animation
209 this.delegate( 'start' ); 210 }, 211 212 // Get collection of feeds to fetch 213 getFeeds : function() { 214 var self = this, 215 settings = self.settings, 216 feedItems = settings.feedItems, 217 count, 218 url; 219 220 self.feeds = []; 221 222 // Feeds 223 if (settings.feeds.length > 0) { 224 var _feeds = typeof (settings.feeds) === 'string' ? [settings.feeds] : settings.feeds; 225 226 for (var i = 0, max = _feeds.length; i < max; i += 1) { 227 var _feed = _feeds[i]; 228 229 url = typeof (_feed) === 'string' ? _feed : _feed.url; 230 count = _feed.hasOwnProperty('count') ? _feed.count : feedItems; 231 self.feeds.push({ 232 'type' : 'feed', 233 'url' : document.location.protocol + '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + parseInt(count) + '&callback=?&q=' + encodeURIComponent( url ), 234 'count' : count 235 }); 236 } 237 } 238 239 // Twitter feed 240 if (settings.twitter.length > 0) { 241 count = settings.twitterCount > 0 ? settings.twitterCount : feedItems; 242 self.feeds.push({ 243 'type' : 'twitter', 244 'url' : 'http://api.twitter.com/1/statuses/user_timeline.json?screen_name=' + encodeURIComponent( settings.twitter ) + '&callback=?&include_entities=1&count=' + parseInt(count), 245 'count' : count 246 }); 247 } 248 249 // Last.fm feed 250 if (settings.lastfm.length > 0) { 251 count = settings.lastfmCount > 0 ? settings.lastfmCount : feedItems; 252 url = 'http://ws.audioscrobbler.com/1.0/user/' + settings.lastfm + '/recenttracks.rss'; 253 self.feeds.push({ 254 'type' : 'lastfm', 255 'url' : document.location.protocol + '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + parseInt(count) + '&callback=?&q=' + encodeURIComponent( url ), 256 'count' : count 257 }); 258 } 259 260 // Tumblr feed 261 if (settings.tumblr.length > 0) { 262 count = settings.tumblrCount > 0 ? settings.tumblrCount : feedItems; 263 url = 'http://' + settings.tumblr + '.tumblr.com/rss'; 264 self.feeds.push({ 265 'type' : 'tumblr', 266 'url' : document.location.protocol + '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + parseInt(count) + '&callback=?&q=' + encodeURIComponent( url ), 267 'count' : count 268 }); 269 } 270 271 // Pinterest feed 272 if (settings.pinterest.length > 0) { 273 count = settings.pinterestCount > 0 ? settings.pinterestCount : feedItems; 274 url = 'http://pinterest.com/' + settings.pinterest + '/feed.rss'; 275 self.feeds.push({ 276 'type' : 'pinterest', 277 'url' : document.location.protocol + '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + parseInt(count) + '&callback=?&q=' + encodeURIComponent( url ), 278 'count' : count 279 }); 280 } 281 282 // Facebook page feed -- this should always be the last feed requested 283 if (settings.facebook.length > 0) { 284 var fb, 285 date = new Date(); 286 287 $.ajax({
288 url: document.location.protocol + '//graph.facebook.com/'+settings.facebook, // The timestamp is appended to prevent ID caching 289 dataType: 'jsonp', 290 success: function(json){ 291 fb = json; 292 }, 293 error: function(xhr, status, error) { 294 console.log( 'An error occurred while trying to fetch this profile from Facebook' ); 295 fb = 'error'; 296 }, 297 complete: function() { 298 if ( fb !== 'error' ) { 299 self.api( 'completeFacebookRequest', fb ); 300 } 301 } 302 }); 303 } 304 else { 305 self.api( 'getFeedItems' ); 306 } 307 }, 308 309 // Executed when request to Facebook for a Facebook page JSON object is completed 310 completeFacebookRequest : function( fb ) { 311 var settings = this.settings, 312 count = settings.facebookCount > 0 ? settings.facebookCount : settings.feedItems, 313 url = 'https://www.facebook.com/feeds/page.php?id=' + fb.id + '&format=rss20'; 314 315 this.feeds.push({ 316 'type' : 'facebook', 317 'url' : document.location.protocol + '//ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + parseInt(count) + '&callback=?&q=' + encodeURIComponent( url ), 318 'count' : count, 319 'data' : fb 320 }); 321 322 // Get list items from feed 323 this.api( 'getFeedItems' ); 324 }, 325 326 // Add items from fetched RSS feed 327 addItems : function( items, response, type ) { 328 var entries = this.api( 'getItemsFromResponse', response, type ), 329 max = entries.length, 330 i = 0; 331 332 for (i; i < max; i += 1) { 333 var entry = entries[i]; 334 entry.timestamp = this.api( 'getTimestamp', entry, type ); 335 items.push( entry ); 336 } 337 338 return items; 339 }, 340 341 // Get entries from ajax call response 342 getItemsFromResponse : function( response, type ) { 343 switch (type) { 344 case 'twitter': 345 return response; 346 break; 347 default: 348 return response.responseData.feed.entries; 349 break; 350 } 351 }, 352 353 // Get timestamp 354 getTimestamp : function( item, type ) { 355 switch (type) { 356 case 'twitter': 357 var v = item.created_at.split(' '); 358 return new Date(Date.parse( v[1] + " " + v[2] + ", " +v[5] + " " + v[3] + " UTC")); 359 break; 360 default: 361 return new Date( item.publishedDate ); 362 break; 363 } 364 }, 365 366 // Get feed items 367 getFeedItems : function() { 368 var self = this, 369 items = [], 370 feeds = this.feeds, 371 complete = 0, 372 feed, type, url; 373 374 for (var i = 0, max = feeds.length; i < max; i += 1) { 375 feed = feeds[i], 376 type = feed.type, 377 url = feed.url; 378 379 // Prevent feed caching 380 if ( self.settings.noCache === true ) { 381 var timestamp = new Date().getTime(); 382 url += '&' + timestamp; 383 } 384 385 $.ajax({
386 url: url, 387 dataType: 'json', 388 success: function(data) { 389 if ( type === 'feed' && data.responseStatus !== 200) { 390 console.log( 'Unable to load feed' ); 391 return; 392 } 393 394 // Add items to collection 395 self.api( 'addItems', items, data, type ); 396 }, 397 error: function(xhr, status, error) { 398 console.log( 'An error occurred while trying to fetch feed from ' + feed.url + '. Status: ' + status ); 399 }, 400 complete: function() { 401 // Make sure that every feed call has been completed before proceeding 402 complete += 1; 403 if (complete === max) { 404 self.api( 'completeFeedItemsRequest', items, type ); 405 } 406 } 407 }); 408 } 409 }, 410 411 // Function to execute when feed items retrieval is complete 412 completeFeedItemsRequest : function( items, type ) { 413 var i, 414 settings = this.settings, 415 max = items.length > settings.feedItems ? settings.feedItems : items.length, 416 html, 417 list = this.dom.list, 418 sort_by = settings.sortBy, 419 reverse = settings.reverseOrder; 420 421 // Sort items 422 if (sort_by === 'random') { 423 items = this.api( 'randomize', items ); 424 } 425 else { 426 items.sort( this.api('sortBy', sort_by, reverse ) ); 427 } 428 429 for (i = 0; i < max; i += 1) { 430 html = this.api( 'displayFeedItem', items[i], type ); 431 list.append( html ); 432 } 433 434 this.dom.items = list.children('li'); 435 this.api( 'startup' ); 436 }, 437 438 // Sort feed items 439 sortBy : function(field, reverse) { 440 441 var key = function(x) { 442 return x[field]; 443 }; 444 445 return function( a, b ) { 446 var A = key(a), 447 B = key(b); 448 449 return ( (A < B) ? -1 : 450 (A > B) ? +1 : 0) * [-1,1][+!!reverse]; 451 } 452 }, 453 454 // Randomly order feed items 455 randomize : function( array ) { 456 var tmp, 457 current, 458 top = array.length; 459 460 if (top) while( --top ) { 461 current = Math.floor( Math.random() * (top + 1) ); 462 tmp = array[current]; 463 array[current] = array[top]; 464 array[top] = tmp; 465 } 466 467 return array; 468 }, 469 470 // Get feed item format 471 getFormat : function( type ) { 472 var settings = this.settings; 473 474 switch (type) { 475 case 'facebook': 476 return settings.facebookFormat.length > 0 ? settings.facebookFormat : settings.feedFormat; 477 break; 478 case 'twitter': 479 return settings.twitterFormat.length > 0 ? settings.twitterFormat : settings.feedFormat; 480 break; 481 case 'lastfm': 482 return settings.lastfmFormat.length > 0 ? settings.lastfmFormat : settings.feedFormat; 483 break; 484 case 'tumblr': 485 return settings.tumblrFormat.length > 0 ? settings.tumblrFormat : settings.feedFormat; 486 break; 487 case 'pinterest': 488 return settings.pinterestFormat.length > 0 ? settings.pinterestFormat : settings.feedFormat; 489 break; 490 default: 491 return settings.feedFormat; 492 break; 493 } 494 }, 495 496 // Display feed item 497 displayFeedItem : function( item, type ) { 498 var text = this.api( 'getFormat', type ), 499 replace = ['author', 'excerpt', 'link', 'item_link', 'title', 'timeago', 'date'], 500 replacement, 501 key; 502 503 for (var i = 0; i < replace.length; i += 1) { 504 key = replace[i]; 505 506 // Get replacement 507 if (key === 'timeago') { 508 replacement = this.settings.timeAgoFormat( item.timestamp ); 509 } 510 else if (key === 'date') { 511 replacement = this.settings.dateFormat.apply( this, [ item.timestamp ] ); 512 } 513 else { 514 replacement = this.api( 'getReplacement', item, key, type ); 515 } 516 517 // Do replacement 518 switch (key) { 519 case 'author': 520 text = text.replace( /\%author\%/g, replacement ); 521 break; 522 523 case 'excerpt': 524 text = text.replace( /\%excerpt\%/g, replacement ); 525 break; 526 527 case 'link': 528 text = text.replace( /\%link\%/g, replacement ); 529 break; 530 531 case 'item_link': 532 text = text.replace( /\%item_link\%/g, replacement ); 533 break; 534 535 case 'title': 536 text = text.replace( /\%title\%/g, replacement ); 537 break; 538 539 case 'timeago': 540 text = text.replace( /\%timeago\%/g, replacement ); 541 break; 542 543 case 'date': 544 text = text.replace( /\%date\%/g, replacement); 545 break; 546 } 547 } 548 549 return '<li>
549' + text + '</li>'; 550 }, 551 552 // Get entry key 553 getReplacement : function( item, key, type ) { 554 switch (key) { 555 case 'author' : 556 switch (type) { 557 case 'twitter': 558 return item.user.name; 559 break; 560 561 default: 562 return item.author; 563 break; 564 } 565 break; 566 567 case 'excerpt' : 568 switch (type) { 569 case 'twitter': 570 var text = this.api( 'getTwitterText', item, this.settings.parseTwitterLinks ); 571 572 return text; 573 break; 574 575 case 'facebook': 576 var t = $(item.content).text(); 577 578 if (t === '') { 579 return item.contentSnippet.length > this.settings.excerptLength 580 ? item.contentSnippet.slice(0, this.settings.excerptLength) 581 : item.contentSnippet; 582 } 583 else { 584 return t; 585 } 586 break; 587 588 case 'lastfm': 589 return item.title; 590 break; 591 592 default: 593 return item.contentSnippet.length > this.settings.excerptLength 594 ? item.contentSnippet.slice(0, this.settings.excerptLength) 595 : item.contentSnippet; 596 break; 597 } 598 break; 599 600 case 'link' : 601 switch (type) { 602 case 'twitter': 603 return 'https://twitter.com/#!/'+ item.user.screen_name; 604 break; 605 606 case 'facebook': 607 return 'http://facebook.com/' + this.settings.facebook; 608 break; 609 610 case 'pinterest': 611 return 'http://pinterest.com/' + this.settings.pinterest + '/'; 612 break; 613 614 default: 615 return item.link; 616 break; 617 } 618 break; 619 620 case 'item_link' : 621 switch (type) { 622 case 'twitter': 623 return 'https://twitter.com/#!/'+ item.user.name + '/statuses/'+ item.id_str; 624 break; 625 626 case 'lastfm': 627 return item.content; 628 break; 629 630 default: 631 return item.link; 632 break; 633 } 634 break; 635 636 case 'title' : 637 switch (type) { 638 case 'twitter': 639 return ''; 640 break; 641 642 default: 643 return item.title; 644 break; 645 } 646 break; 647 648 } 649 }, 650 651 // Get Twitter text 652 getTwitterText: function( item, parseLinks ) { 653 var text = item.text, 654 self = this, 655 entities = [], 656 links, 657 entity; 658 659 text = text.length > this.settings.excerptLength 660 ? text.slice(0, this.settings.excerptLength) 661 : text; 662 663 if ( parseLinks === false ) { 664 return text; 665 } 666 667 // Parse URLs in tweets 668 jQuery.each( item.entities, function(name, links) { 669 for ( var i = 0, max = links.length; i < max; i += 1 ) { 670 entity = links[i]; 671 entity.type = name;
672 entity.start = entity.indices[0]; 673 entities.push( entity ); 674 } 675 }); 676 677 entities.sort( this.api('sortBy', 'start', false ) ); 678 679 for ( var i = 0, max = entities.length; i < max; i += 1 ) { 680 text = this.api( 'parseTwitterLink', text, entities[i] ); 681 } 682 683 return text; 684 }, 685 686 // Parse Twitter link 687 parseTwitterLink: function( text, entity ) { 688 var start = entity.start, 689 end = entity.indices[1], 690 url, 691 text; 692 693 if ( start < this.settings.excerptLength ) { 694 url = this.api( 'getTwitterLink', entity ); 695 text = text.slice(0, start) + url + text.slice(end); 696 } 697 698 return text; 699 }, 700 701 // Get Twitter link, depending on whether it's a URL, user mention or hashtag 702 getTwitterLink: function( entity ) { 703 var target = this.settings.openLinksInNewWindow === true ? 'target="_blank"' : ''; 704 switch ( entity.type ) { 705 case 'urls' : 706 return '<a href="' + entity.url + '" ' + target + '>' + entity.display_url + '</a>'; 707 break; 708 case 'user_mentions': 709 return '<a href="https://twitter.com/#!/' + entity.screen_name + '" ' + target + '>@' + entity.screen_name + '</a>'; 710 break; 711 case 'hashtags': 712 return '<a href="https://twitter.com/#!/search/' + entity.text + '" ' + target + '>#' + entity.text + '</a>'; 713 break; 714 } 715 }, 716 717 // Default way to display time ago 718 getTimeAgo: function( date ) { 719 var now = new Date(), 720 elapsed = now.getTime() - date.getTime(), // milliseconds elapsed 721 day = 86400000, 722 minute = 60000, 723 hour = 3600000, 724 units, 725 timeago; 726 727 if (elapsed < hour) { 728 units = Math.ceil( elapsed / minute); 729 timeago = units > 1 ? units + ' minutes ago' : units + ' minute ago'; 730 } else if (elapsed < day) { 731 units = Math.ceil( elapsed / hour); 732 timeago = units > 1 ? units + ' hours ago' : units + ' hour ago'; 733 } else { 734 units = Math.ceil( elapsed / day); 735 timeago = units > 1 ? units + ' days ago' : units + ' day ago'; 736 } 737 738 return timeago; 739 }, 740 741 // Default date format 742 getFormattedDate: function( date ) { 743 var date = new Date( date ), 744 months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 745 746 return date.getDate() + ' ' + months[ date.getMonth() ]; // eg. 16 Aug 747 }, 748 749 // Get newsticker list width 750 getListWidth : function() { 751 if (this.settings.showControls) { 752 return this.dom.controls.position().left - this.dom.title.outerWidth( true ); 753 } 754 else { 755 return this.dom.wrapper.width() - this.dom.title.outerWidth( true ) - 1; 756 } 757 }, 758 759 // Pause function to be executed whenever pause is initiated 760 pause : function() { 761 if (this.settings.showControls === true) { 762 var self = this; 763 764 this.dom.controls.children('.pause') 765 .removeClass('pause') 766 .addClass('resume') 767 .off('click', function() { 768 self.delegate( 'pause' ) 769 }) 770 .on('click', function() { 771 self.delegate( 'resume' ) 772 }); 773 } 774 }, 775 776 // Resume function to be executed whenever resume is initiated 777 resume : function() { 778 if (this.settings.showControls === true) { 779 var self = this; 780 781 this.dom.controls.children('.resume') 782 .removeClass('resume') 783 .addClass('pause') 784 .off('click', function() { 785 self.delegate( 'resume' ); 786 }) 787 .on('click', function() { 788 self.delegate( 'pause' ) 789 }); 790 } 791 }, 792 793 // Handle window resizing 794 resize : function() { 795 // First try delegating the resizing to the style's resize method. If none exists, handle it ourselves 796 if (this.delegate( 'resize' ) === false) { 797 this.dom.list.width( this.api( 'getListWidth' ) ); 798 } 799 } 800 } 801 802 // Newsticker styles collection 803 $.fn.newsticker.styles = { 804 805 // 806 // Fading news ticker 807 // 808 fade : { 809 810 // Loads style-specific settings 811 settings : function() { 812 return { 813 'fadeOutSpeed' : 'slow', 814 'fadeInSpeed' : 'slow', 815 'transitionSpeed' : 4000, 816 'pauseOnHover' : true 817 }; 818 }, 819 820 // Manipulate individual list item 821 manipulateItem : function(item, is_first) { 822 var self = this; 823 824 item.css({ 825 'position' : 'absolute', 826 'left' : 0, 827 'top' : 0 828 }); 829 830 if (is_first) { 831 item.addClass('display'); 832 } 833 else { 834 item.hide(); 835 } 836 }, 837 838 // Style-specific dom manipulations (see methods.mani
838pulateDom for other dom manipulations) 839 manipulateDom : function() { 840 this.dom.wrapper.css({'width' : '100%'}); 841 this.dom.title.css({'float' : 'left', 'margin' : 0}); 842 this.dom.list.css({'float' : 'left', 'width' : this.api('getListWidth') + 'px', 'position' : 'relative', 'list-style' : 'none'}); 843 }, 844 845 // Loop through each individual list item 846 itemLoop : function() { 847 var is_first = true, 848 self = this; 849 850 this.dom.items.each( function() { 851 self.delegate( 'manipulateItem', $(this), is_first); 852 is_first = false; 853 }); 854 }, 855 856 // Get adjacent item (either previous or next) 857 adjacentItem : function( movement ) { 858 var self = this; 859 860 this.dom.items.each(function() { 861 if ( $(this).hasClass('display') ) { 862 self.delegate( 'itemTransform', $(this), movement ); 863 } 864 }) 865 }, 866 867 // Item transformation (called by nextItem and previousItem) 868 // The item passed to this function is the one currently on display 869 itemTransform : function( item, movement ) { 870 // Fade out current item 871 this.delegate( 'itemFadeOut', item ); 872 873 if (movement === 'next') { 874 if ( item.is('li:last-child') ) { 875 this.delegate( 'itemFadeIn', this.dom.items.first() ); 876 } 877 else { 878 this.delegate( 'itemFadeIn', item.next() ); 879 } 880 } 881 else if (movement === 'previous') { 882 if ( item.is('li:first-child') ) { 883 this.delegate( 'itemFadeIn', this.dom.items.last() ); 884 } 885 else { 886 this.delegate( 'itemFadeIn', item.prev() ); 887 } 888 } 889 }, 890 891 // Fade item in 892 itemFadeIn : function( item ) { 893 item.fadeIn( this.settings.fadeInSpeed, function() { 894 item.addClass('display'); 895 }); 896 }, 897 898 // Fade item out 899 itemFadeOut : function( item ) { 900 item.fadeOut( this.settings.fadeOutSpeed, function() { 901 item.removeClass('display'); 902 }); 903 }, 904 905 // Start fade action 906 start : function() { 907 var self = this; 908 909 this.animationId = window.setInterval( function() { 910 self.delegate( 'adjacentItem', 'next' ); 911 }, this.settings.transitionSpeed) 912 }, 913 914 // Pause fade 915 pause : function() { 916 clearInterval( this.animationId ); 917 this.api( 'pause' ); 918 return this; 919 }, 920 921 // Resume fade 922 resume : function() { 923 this.delegate( 'start' ); 924 this.api( 'resume' ); 925 return this; 926 }, 927 928 // Next item 929 next : function() { 930 this.delegate( 'pause' ) 931 .delegate( 'adjacentItem', 'next' ); 932 return this; 933 }, 934 935 // Previous item 936 previous : function() { 937 this.delegate( 'pause' ) 938 .delegate( 'adjacentItem', 'previous' ); 939 return this; 940 } 941 942 }, 943 944 // 945 // Reveal news ticker 946 // 947 reveal : { 948 949 // Initialization script 950 init : function() { 951 this.progress = { 952 animate : true, 953 original : {}, 954 span : {}, 955 text : '', 956 html: '', 957 length : 0, 958 i: 0, 959 j: 0, 960 k: 0, 961 elements: {}, 962 nextHtml: {}, 963 nextHtmlIndex: [], 964 nextHtmlContent : '' 965 }; 966 }, 967 968 // Custom settings for this display style 969 settings : function() { 970 return { 971 'letterRevealSpeed' : 70, 972 'transitionSpeed' : 2000, 973 'pauseOnHover' : false 974 }; 975 }, 976 977 getLength: function() { 978 var progress = this.progress; 979 980 if (progress.length === 0) { 981 progress.length = progress.text.length; 982 } 983 return progress.text.length; 984 }, 985 986 resetTicker: function( vars ) { 987 var progress = this.progress; 988 989 progress.span = vars.span; 990 progress.elements = vars.elements; 991 progress.text = vars.text; 992 progress.html = vars.html; 993 progress.length = 0; 994 progress.j = 0; 995 this.delegate( 'getNextHtml' ); 996 progress.i = 0; 997 }, 998 999 getNextHtml: function() { 1000 var progress = this.progress; 1001 1002 if ( progress.elements.length <= progress.j ) { 1003 progress.nextHtml = {}; 1004 progress.nextHtmlIndex = [-1]; 1005 } 1006 else { 1007 progress.nextHtml = progress.elements[progress.j]; 1008 progress.nextHtmlIndex = progress.nextHtml.indeces; 1009 progress.nextHtmlContent = $(progress.nextHtml).text(); 1010 progress.k = 0; 1011 } 1012 }, 1013 1014 increment: function() { 1015 var self = this, 1016 progress = self.progress; 1017 1018 1019 if ( progress.nextHtmlIndex.start === progress.i ) { 1020 clearInterval( this.animationId ); 1021 1022 this.animationId = window.setInterval( function() { 1023 self.delegate( 'incrementHtml' ); 1024 }, self.settings.letterRevealSpeed ); 1025 1026 } 1027 else { 1028 progress.span.html( progress.text.slice(0, progress.i) ); 1029 progress.i += 1; 1030 } 1031 }, 1032 1033 incrementHtml: function() { 1034 var self = this, 1035 progress = self.progress, 1036 text = progress.span.text, 1037 element = $( self.delegate( 'getOuterHtml', $(progress.nextHtml) ) ), 1038 reveal; 1039 1040 progress.k += 1; 1041 1042 reveal = progress.nextHtmlContent.slice(0, progress.k); 1043 element = $( self.delegate( 'getOuterHtml', element.children().text( reveal ) ) ); 1044 1045 progress.span.html( progress.text.slice(0, progress.i) + element.html() ); 1046 1047 if (progress.k === progress.nextHtmlIndex.length ) { 1048 progress.i += progress.nextHtmlIndex.element.html().length; 1049 1050 clearInterval( this.animationId ); 1051 1052 progress.j += 1; 1053 self.delegate( 'getNextHtml' ); 1054 this.animationId = window.setInterval( function() { 1055 self.delegate( 'revealLetter' ); 1056 }, self.settings.letterRevealSpeed ); 1057 } 1058 }, 1059 1060 revealAll: function() { 1061 var progress = this.progress; 1062 1063 progress.span.html( progress.html ); 1064 progress.i = this.delegate( 'getLength' ); 1065 }, 1066 1067 manipulateDom : function() { 1068 var self = this, 1069 dom = self.dom, 1070 list = dom.list; 1071 1072 dom.wrapper.css({'width' : '100%'}); 1073 dom.title.css({'float' : 'left', 'overflow' : 'hidden'}); 1074 list.css({'float' : 'left', 'position' : 'relative', 'list-style' : 'none', 'margin' : 0, 'padding' : 0}); 1075 list.width( this.api( 'getListWidth' ) + 'px' ); 1076 }, 1077 1078 pause : function() {
1079 this.delegate( 'stopAnimation' ); 1080 this.delegate( 'revealAll' ); 1081 this.api( 'pause' ); 1082 return this; 1083 }, 1084 1085 resume : function() { 1086 var self = this, 1087 progress = self.progress; 1088 1089 progress.animate = true; 1090 this.animationId = window.setTimeout( function() { 1091 self.delegate( 'adjacentItem', 'next' ); 1092 }, 50 ); 1093 self.api( 'resume' ); 1094 return self; 1095 }, 1096 1097 // Reveal adjacent item 1098 adjacent : function( side ) { 1099 this.delegate( 'stopAnimation' ); 1100 this.api( 'pause' ); 1101 this.delegate( 'adjacentItem', side ); 1102 this.delegate( 'revealAll' ); 1103 return this; 1104 }, 1105 1106 // Reveal previous item 1107 previous : function() { 1108 return this.delegate( 'adjacent', 'previous' ); 1109 }, 1110 1111 // Reveal next item 1112 next : function() { 1113 return this.delegate( 'adjacent', 'next' ); 1114 }, 1115 1116 // Stop animation. Used by pause, previous and next methods. 1117 stopAnimation : function() { 1118 clearInterval(this.animationId); 1119 this.progress.animate = false; 1120 }, 1121 1122 // Manipulate individual list item 1123 manipulateItem : function(item, is_first) { 1124 // Give each list item position and wrap text in span 1125 item.css({'position' : 'absolute', 'left' : 0, 'top' : 0, 'z-index' : 1}) 1126 .wrapInner('<span class="original_text" />'); 1127 1128 // Hide each span and make it a block element -- IE8 fix 1129 item.children('span').css({'opacity' : 0, 'display' : 'block', 'position' : 'relative', 'z-index' : 4}); 1130 1131 if (is_first) { 1132 item.addClass('revealing'); 1133 } 1134 }, 1135 1136 // Loop through items before animation begins 1137 itemLoop : function() { 1138 var is_first = true, 1139 self = this, 1140 dom = self.dom; 1141 1142 dom.items.each( function() { 1143 self.delegate( 'manipulateItem', $(this), is_first ); 1144 is_first = false; 1145 }); 1146 }, 1147 1148 // Prepare a new item for reveal 1149 prepareItem : function() { 1150 var self = this, 1151 item = self.dom.list.find('.revealing'), 1152 progress = self.progress, 1153 html, text; 1154 1155 if (item.children('.revealing_text').length === 0) { 1156 item.append('<span class="revealing_text" />') 1157 .children('.revealing_text').css({'opacity' : 1, 'position' : 'absolute', 'left' : 0, 'top' : 0}); 1158 } 1159 1160 item.css({ 'z-index' : 2 }); 1161 1162 html = item.children('.original_text'); 1163 text = self.delegate( 'decodeHtml', html.html() ); 1164 1165 this.delegate( 'resetTicker', { 1166 span : item.children('.revealing_text'), 1167 text : text, 1168 html : html.html(), 1169 elements : this.delegate( 'getElements', html, text ) 1170 }); 1171 1172 if (progress.animate === true) { 1173 self.delegate( 'animate' ); 1174 } 1175 }, 1176 1177 // Return the links within a given list item 1178 getElements : function( html, text ) { 1179 var self = this, 1180 elements = html.children();
1181 1182 elements.each( function( i, element ) { 1183 elements[i].indeces = self.delegate( 'getElement', $(element), html.html() ); 1184 }); 1185 1186 return elements; 1187 }, 1188 1189 // Return start point and length of HTML element 1190 getElement : function( element, original_string ) { 1191 var el = this.delegate( 'getOuterHtml', element ), 1192 start = original_string.indexOf( el.html() ), 1193 length = element.text().length; 1194 1195 return { 1196 'start' : start, 1197 'length' : length, 1198 'element' : el 1199 }; 1200 }, 1201 1202 // Decode HTML 1203 decodeHtml: function(string) { 1204 string = string.replace( /<script[^>]*>([\S\s]*?)<\/script>/gmi, '' ); 1205 string = $("<div/>").html(string).html(); 1206 return string; 1207 }, 1208 1209 // Return outer HTML 1210 // @see http://stackoverflow.com/questions/2419749/get-selected-elements-outer-html 1211 getOuterHtml : function( element ) { 1212 return element.clone().wrap('<p>').parent(); 1213 }, 1214 1215 // Animate 1216 animate: function() { 1217 var self = this; 1218 this.animationId = window.setInterval( function() { 1219 self.delegate( 'revealLetter' ); 1220 }, self.settings.letterRevealSpeed ); 1221 }, 1222 1223 // Reveal the next letter 1224 revealLetter : function() { 1225 var self = this, 1226 progress = self.progress; 1227 1228 self.delegate( 'increment' ); 1229 1230 if ( progress.i > self.delegate( 'getLength' ) ) { 1231 clearInterval(this.animationId); 1232 this.animationId = window.setTimeout( function() { 1233 self.delegate( 'adjacentItem', 'next' ); 1234 }, self.settings.transitionSpeed ); 1235 } 1236 }, 1237 1238 // Adjacent item 1239 adjacentItem : function( side ) { 1240 var dom = this.dom, 1241 current = dom.list.find('.revealing').removeClass('revealing'), 1242 adjacent = this.delegate( side === 'previous' ? 'getPreviousItem' : 'getNextItem', dom, current ); 1243 1244 adjacent.addClass('revealing'); 1245 current 1246 .css({ 'z-index' : 1 }) 1247 .children('.revealing_text').text(''); 1248 this.delegate( 'prepareItem' ); 1249 }, 1250 1251 // Start revealing the next item 1252 getNextItem : function(dom, current) { 1253 return ( current.is('li:last-child') ) ? dom.list.find('li:first-child') : current.next(); 1254 }, 1255 1256 // Start revealing the previous item 1257 getPreviousItem : function(dom, current) { 1258 return ( current.is('li:first-child') ) ? dom.list.find('li:last-child') : current.prev(); 1259 }, 1260 1261 // Start the animation process 1262 start : function() { 1263 if (this.settings.autoStart === true) { 1264 this.delegate( 'prepareItem' ); 1265 } 1266 } 1267 }, 1268 1269 // 1270 // Scrolling news ticker 1271 // 1272 scroll : { 1273 1274 // Initialization script 1275 init : function() { 1276 this.progress = { 1277 autoScroll : this.settings.autoStart, 1278 left : 0, 1279 boundary : 0, 1280 last : {}, 1281 current : {}, 1282 animationId : 0 1283 }; 1284 }, 1285 1286 // Manages the scrolling action 1287 resetLeft : function() { 1288 this.progress.left = parseInt( this.dom.list.css('left') ); 1289 }, 1290 1291 // Loads style-specific settings 1292 settings : function() { 1293 return { 1294 'scrollSpeed' : 50, 1295 'slideSpeed' : 1000, 1296 'slideEasing' : 'swing', 1297 'pauseOnHover' : true 1298 }; 1299 }, 1300 1301 // Style-specific dom manipulations 1302 manipulateDom : function(is_secondary) { 1303 var dom = this.dom, 1304 is_secondary = typeof( is_secondary ) === 'undefined' ? false : is_secondary; 1305 1306 if (is_secondary === false) { 1307 dom.wrapper.css({'width' : '10000px', 'position' : 'relative'}); 1308 dom.title.css({'float' : 'left'}); 1309 dom.list.wrap('<div class="newsticker_scroller" />') 1310 .css({'position' : 'absolute', 'left' : 10}); 1311 dom.scroller = this.dom.wrapper.children('.newsticker_scroller'); 1312 } 1313 else if (is_secondary === true) { 1314 dom.wrapper.css({'width' : '100%'});
1315 dom.scroller.css({'position' : 'absolute', 'height' : '100%', 'overflow' : 'hidden', 'width' : this.api( 'getListWidth' )+'px', 'left' : dom.title.outerWidth(true)+'px'}); 1316 } 1317 }, 1318 1319 // Individual news item dom manipulations 1320 // @return integer. Next X coordinate. 1321 manipulateItem : function( item, x ) { 1322 var width = item.css({'float' : 'left'}).outerWidth(true); 1323 item.css({'position' : 'absolute', 'display' : 'inline', 'left' : x+'px', 'width' : width+'px'}); 1324 x += width; 1325 return x; 1326 }, 1327 1328 // Set up style-specific item modifications 1329 itemLoop : function() { 1330 var self = this, 1331 x = 0, 1332 progress = this.progress, 1333 dom = this.dom; 1334 1335 dom.items.each( function() { 1336 x = self.delegate( 'manipulateItem', $(this), x ); 1337 }); 1338 1339 progress.current = dom.items.first(); 1340 progress.last = dom.items.last(); 1341 progress.boundary = dom.items.length === 1 ? progress.current.width() : progress.current.next().position().left; 1342 1343 self.delegate( 'manipulateDom', true ); 1344 }, 1345 1346 // Start -- if autoScroll is off, this does nothing 1347 start : function() { 1348 if (this.progress.autoScroll === true) { 1349 this.delegate( 'animate' ); 1350 } 1351 }, 1352 1353 // Sets off the scrolling animation 1354 animate : function() { 1355 var self = this, 1356 progress = self.progress; 1357 1358 if (progress.autoScroll === false) { 1359 progress.autoScroll = true; 1360 } 1361 1362 progress.animationId = window.setInterval( function() { 1363 self.delegate( 'doAnimation' ); 1364 }, self.settings.scrollSpeed); 1365 }, 1366 1367 // Do animation 1368 doAnimation : function() { 1369 var progress = this.progress; 1370 1371 if (progress.left + progress.boundary < 0) { 1372 this.delegate( 'nextItem' ); 1373 } 1374 1375 progress.left -= 1; 1376 this.dom.list.css({ 'left' : progress.left+'px' }); 1377 }, 1378 1379 // Pause scroll 1380 pause : function() { 1381 var progress = this.progress; 1382 1383 progress.autoScroll = false; 1384 clearInterval(progress.animationId); 1385 this.api( 'pause' ); 1386 return this; 1387 }, 1388 1389 // Resume scrolling 1390 resume : function() { 1391 this.delegate( 'animate' ); 1392 this.api( 'resume' ); 1393 return this; 1394 }, 1395 1396 // Next item -- fired by next control button 1397 next : function() { 1398 var self = this, 1399 progress = self.progress, 1400 settings = self.settings, 1401 left = progress.boundary < 0 ? progress.left - progress.current.width() : '-' + progress.boundary; 1402 1403 self.delegate( 'pause' ); 1404 1405 self.dom.list.animate({'left' : left + 'px'}, settings.slideSpeed, settings.slideEasing, function() { 1406 self.delegate( 'resetLeft' ); 1407 self.delegate( 'nextItem' ); 1408 }); 1409 }, 1410
1411 // Window resize 1412 resize : function() { 1413 this.dom.scroller.width( this.api( 'getListWidth' )); 1414 }, 1415 1416 // Previous item -- fired by previous control button 1417 previous : function() { 1418 var self = this, 1419 progress = self.progress, 1420 settings = self.settings, 1421 previous, 1422 left; 1423 1424 self.delegate( 'pause' ); 1425 1426 previous = self.delegate( 'getPrevious' ); 1427 previous.css({'left' : progress.current.position().left - parseInt( previous.width() )}); 1428 left = previous.position().left < 0 ? (0 - previous.position().left) : '-' + previous.position().left; 1429 1430 self.dom.list.animate({'left' : left + 'px'}, settings.slideSpeed, settings.slideEasing, function() { 1431 self.delegate( 'resetLeft' ); 1432 self.delegate( 'previousItem', previous ); 1433 }); 1434 }, 1435 1436 // Switch to next item 1437 nextItem : function() { 1438 var progress = this.progress, 1439 dom = this.dom, 1440 last = dom.items.last(), 1441 first = dom.items.first(), 1442 new_x = progress.last.outerWidth() + progress.last.position().left, 1443 current = progress.current; 1444 1445 current.css({'left' : new_x+'px'}); 1446 1447 if ( current.next().length === 0 ) { 1448 progress.current = first; 1449 progress.last = last; 1450 } 1451 else { 1452 progress.current = current.next(); 1453 progress.last = progress.current.prev(); 1454 } 1455 1456 // Set next boundary 1457 if ( progress.current.next().length === 0 ) { 1458 progress.boundary = first.position().left; 1459 } 1460 else { 1461 progress.boundary = progress.current.next().position().left; 1462 } 1463 }, 1464 1465 // Get previous item 1466 getPrevious : function() { 1467 var progress = this.progress; 1468 1469 if (progress.current.prev().length === 0) { 1470 return this.dom.items.last(); 1471 } 1472 1473 return progress.current.prev(); 1474 }, 1475 1476 // Switch to previous item 1477 previousItem : function( previous ) { 1478 var progress = this.progress, 1479 dom = this.dom; 1480 1481 progress.current = previous; 1482 if ( progress.current.prev().length === 0 ) { 1483 progress.last = dom.items.last(); 1484 } 1485 else { 1486 progress.last = progress.current.prev(); 1487 } 1488 1489 // Set next boundary 1490 if ( progress.current.next().length === 0) { 1491 progress.boundary = dom.items.first().position().left; 1492 } 1493 else { 1494 progress.boundary = progress.current.next().position().left; 1495 } 1496 } 1497 } 1498 } 1499})( jQuery );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.