1/* 2 * jTweetsAnywhere V1.2.1 3 * http://thomasbillenstein.com/jTweetsAnywhere/ 4 * 5 * Copyright 2010, Thomas Billenstein 6 * Licensed under the MIT license. 7 * http://thomasbillenstein.com/jTweetsAnywhere/license.txt 8 */ 9(function($) 10{ 11 $.fn.jTweetsAnywhere = function(options) 12 { 13 // setup the default options 14 var options = $.extend( 15 { 16 /** 17 * The user's name who's tweet feed or list feed is displayed. This 18 * param is also used when a Twitter "Follow Button" is displayed. Usually 19 * this param is a string, but can also be an array of strings. If an array 20 * is supplied (and the params 'list' and 'searchParams' are null), a 21 * combined feed of all users is displayed. 22 * 23 * Sample: 'tbillenstein' or ['twitterapi', '...', '...'] 24 */ 25 username: 'tbillenstein', 26 27 /** 28 * The name of a user's list where the tweet feed is generated from 29 */ 30 list: null, 31 32 /** 33 * A single search param string or an array of search params, to be used in 34 * a Twitter search call. All Twitter Search Params are supported 35 * See here for the details: 36 * http://apiwiki.twitter.com/Twitter-Search-API-Method%3A-search 37 * 38 * Sample: 'q=twitter' or ['q=twitter', 'geocode=48.856667,2.350833,30km'] 39 */ 40 searchParams: null, 41 42 /** 43 * The number of tweets shown in the tweet feed. If this param is 0, no feed 44 * is displayed. For user or list feeds the maximum count is 20, for search 45 * results the maximum count is 100. 46 * 47 * Unlike in previous releases, since 1.2.0 jTweetsAnywhere is based on a 48 * tweets caching algorithm that will always deliver the requested count of 49 * tweets accepting that this request can only be fullfilled by calling Twitter 50 * more than once. 51 * 52 * IMPORTANT: Please always keep in mind, that the use of the Twitter API is 53 * rate limited. Non-authenticated users are rated IP-based and you have only 54 * 150 calls per hour available. Every retrieval of tweets counts and so does 55 * for example hovering over a profile image to show the hovercard. 56 * jTweetsAnywhere will always check the remaining count of free API calls before 57 * actually calling Twitter to avoid black listing your visitor's IP. 58 */ 59 count: 0, 60 61 /** 62 * A flag (true/false) that specifies whether to display a profile image in 63 * tweets. If the param is set to null (the default value), a profile image 64 * is displayed only if the feed represents a user's list or the result of a 65 * Twitter search. 66 * 67 * THIS OPTION IS DEPRECATED. You should use showTweetFeed.showProfileImages 68 * instead. 69 */ 70 tweetProfileImagePresent: null, 71 72 /** 73 * Each tweet that is loaded from Twitter will pass the tweetFilter. if 74 * the filter returns true, the tweet will be added to the tweets cache 75 * otherwise it is ignored. The defaultTweetFilter alsways retruns true 76 * but you can supply your own tweet filter to customize the tweet feed. 77 */ 78 tweetFilter: defaultTweetFilter, 79 80 /** 81 * A flag (true/false) that specifies whether to display a Tweet Feed 82 * or an object literal representing the configuration options for the 83 * Tweet Feed. This flag works in conjunction with the count parameter: 84 * - if count equals 0, no feed is displayed, ignoring showTweetFeed 85 * - if count not equals 0 and showTweetFeed equals false, no feed 86 * is displayed 87 * { 88 * expandHovercards: false, // Boolean - Show Hovercards in expanded mode. 89 * 90 * showTimestamp: true, // A flag (true/false) that specifies whether to display a tweet's timestamp 91 * // or an object literal representing the configuration options for the 92 * // timestamp. 93 * // { 94 * // refreshInterval: 0, // Time in seconds to be waited until the 95 * // // timestamps of the displayed tweets get refreshed 96 * // // 0 means no refreshing. 97 * // } 98 * 99 * showSource: false, // Boolean - Show info about the source of the tweet. 100 * 101 * showGeoLocation: true, // Boolean - Show geolocation info and link to Google maps. 102 * 103 * showInReplyTo: true, // Boolean - Show link to the "replied to" tweet (if available). 104 * 105 * showProfileImages: null, // A flag (true/false) that specifies whether to display a profile image in 106 * // tweets. If the param is set to null (the default value), a profile image 107 * // is displayed only if the feed represents a user's list or the result of a 108 * // Twitter search. 109 * 110 * showUserScreenNames: null, // A flag (true/false/null) that specifies whether to display a username in 111 * // tweets. If the param is set to null (the default value), a username 112 * // is displayed only if the feed represents a user's list or the result of a 113 * // Twitter search. 114 * 115 * showUserFullNames: false, // A flag (true/false/null) that specifies whether to display a user's full name 116 * // in tweets. If the param is set to null, a user's full name 117 * // is displayed only if the feed represents a user's list or the result of a 118 * // Twitter search. 119 * 120 * includeRetweets: true, // Boolean - Include native retweets in a user's tweet feed 121 * 122 * paging: // An object literal representing the configuration options for the 123 * { // paging support, that specifies how more/earlier tweets can be loaded 124 * mode: "none" // by using the supplied UI controls (more/next buttons, scrollbar). 125 * }, // Accepted values for mode are: "none" | "more" | "prev-next" | "endless-scroll" 126 * // if mode equals "endless-scroll" you have to set the height of the tweet feed 127 * // element (.jta-tweet-list) in your CSS to get a scrollbar! You should also set 128 * // the "overflow" attribute to "auto". 129 * 130 * autorefresh: // An object literal representing the configuration options for the 131 * { // autorefresh behaviour. 132 * 133 * // IMPORTANT: Please always keep in mind, that using the Twitter API is rate 134 * // limited. Non-authenticated users are rated IP-based and you have only 150 135 * // calls per hour available. Every retrieval of tweets counts and s
135o does for 136 * // example hovering over a profile image to show the hovercard. jTweetsAnywhere will 137 * // always check the remaining count of free API calls before actually calling 138 * // Twitter to avoid black listing your visitor's IP. 139 * 140 * // However - choose your settings wisely to keep your visitors happy. An update 141 * // interval of 30 seconds on a feed that is updated averaged once per hour 142 * // does not make sense and is a total waste of remaining API calls! 143 * 144 * mode: "none", // Accepted values for mode are: "none" | "auto-insert" | "trigger-insert" 145 * // "none" (the default value) - disables the autorefresh feature 146 * // "auto-insert" - automatically insert the new tweets on top of the tweet feed 147 * // "trigger-insert" - if new tweets arrived, show or update a button that displays 148 * // the number of new tweets. These new tweets are inserted on top of the tweet 149 * // feed, if the user clicks on the button. 150 * 151 * interval: 60, // Time in seconds to be waited until the next request for new tweets. Minimum 152 * // value is 30. 153 * 154 * duration: 3600 // Time in seconds for how long the autorefresh will be active. After 155 * // this period of time, autorefreshing will stop. A value of -1 means 156 * // autorefresh for ever. 157 * } 158 * } 159 */ 160 showTweetFeed: true, 161 162 /** 163 * A flag (true/false) that specifies whether to display a Twitter "Follow 164 * Button". 165 */ 166 showFollowButton: false, 167 168 /** 169 * A flag (true/false) that specifies whether to display a Twitter "Connect 170 * Button" or an object literal representing the configuration options for 171 * the "Tweet Box". 172 * { 173 * size: 'medium' // String - The size of the Connect Button. Valid values are: small, medium, large, xlarge 174 * } 175 */
176 showConnectButton: false, 177 178 /** 179 * A flag (true/false) that specifies whether to display Login Infos. 180 */ 181 showLoginInfo: false, 182 183 /** 184 * A flag (true/false) that specifies whether to display a Twitter "Tweet 185 * Box" or an object literal representing the configuration options for 186 * the "Tweet Box". 187 * { 188 * counter: true, // Boolean - Display a counter in the Tweet Box for counting characters 189 * width: 515, // Number - The width of the Tweet Box in pixels 190 * height: 65, // Number - The height of the Tweet Box in pixels 191 * label: "What's happening", // String - The text above the Tweet Box, a call to action 192 * defaultContent: <none>, // String - Pre-populated text in the Tweet Box. Useful for an @mention, a #hashtag, a link, etc. 193 * onTweet: <none> // Function - Specify a listener for when a tweet is sent from the Tweet Box. The listener receives two arguments: a plaintext tweet and an HTML tweet 194 * } 195 */ 196 showTweetBox: false, 197 198 /** 199 * A decorator is a function that is responsible for constructing a certain 200 * element of the widget. Most of the decorators return a HTML string. 201 * Exceptions are the mainDecorator, which defines the basic sequence of 202 * the widget's components, plus the linkDecorator, the usernameDecorator 203 * and the hashtagDecorator which return the string that is supplied as a 204 * function param, enriched with the HTML tags. 205 * 206 * For details, see the implementations of the default decorators. Each 207 * default decorator can be overwritten by your own implementation. 208 */ 209 mainDecorator: defaultMainDecorator, 210 211 tweetFeedDecorator: defaultTweetFeedDecorator, 212 213 tweetDecorator: defaultTweetDecorator, 214 tweetProfileImageDecorator: defaultTweetProfileImageDecorator, 215 tweetBodyDecorator: defaultTweetBodyDecorator, 216 tweetUsernameDecorator: defaultTweetUsernameDecorator, 217 tweetTextDecorator: defaultTweetTextDecorator, 218 tweetAttributesDecorator: defaultTweetAttributesDecorator, 219 tweetTimestampDecorator: defaultTweetTimestampDecorator, 220 tweetSourceDecorator: defaultTweetSourceDecorator, 221 tweetGeoLocationDecorator: defaultTweetGeoLocationDecorator, 222 tweetInReplyToDecorator: defaultTweetInReplyToDecorator, 223 tweetRetweeterDecorator: defaultTweetRetweeterDecorator, 224 225 tweetFeedControlsDecorator: defaultTweetFeedControlsDecorator, 226 tweetFeedControlsMoreBtnDecorator: defaultTweetFeedControlsMoreBtnDecorator, 227 tweetFeedControlsPrevBtnDecorator: defaultTweetFeedControlsPrevBtnDecorator, 228 tweetFeedControlsNextBtnDecorator: defaultTweetFeedControlsNextBtnDecorator, 229 230 tweetFeedAutorefreshTriggerDecorator: defaultTweetFeedAutorefreshTriggerDecorator, 231 tweetFeedAutorefreshTriggerContentDecorator: defaultTweetFeedAutorefreshTriggerContentDecorator, 232 233 connectButtonDecorator: defaultConnectButtonDecorator, 234 235 loginInfoDecorator: defaultLoginInfoDecorator, 236 loginInfoContentDecorator: defaultLoginInfoContentDecorator, 237 238 followButtonDecorator: defaultFollowButtonDecorator, 239 240 tweetBoxDecorator: defaultTweetBoxDecorator, 241 242 linkDecorator: defaultLinkDecorator, 243 usernameDecorator: defaultUsernameDecorator, 244 hashtagDecorator: defaultHashtagDecorator, 245 246 loadingDecorator: defaultLoadingDecorator, 247 errorDecorator: defaultErrorDecorator, 248 noDataDecorator: defaultNoDataDecorator, 249 250 /** 251 * Formatters are currently used for date format processing only. 252 * 253 * The tweetTimestampFormatter formats the tweet's timestamp to be shown 254 * in the tweet attributes section 255 * 256 * For details, see the implementation of the defaultTweetTimestampFormatter. 257 */ 258 tweetTimestampFormatter : defaultTweetTimestampFormatter, 259 260 /** 261 * The tweetTimestampTooltipFormatter formats the tweet's timestamp to be shown 262 * in the tooltip when hovering over the timestamp link. 263 */ 264 tweetTimestampTooltipFormatter : defaultTweetTimestampTooltipFormatter, 265 266 /** 267 * A visualizer is a function that is responsible for adding one or more 268 * elements to the DOM and thereby making them visible to the user. 269 * A visualizer might also be responsible to do the opposite effect: 270 * To remove one or more elements from the DOM. 271 * 272 * The tweetVisualizer gets called each time a tweet element should be
273 * appended or prepended to the tweet feed element. 274 * 275 * For details, see the implementation of the defaultTweetVisualizer. 276 * 277 * Each default visualizer can be overwritten by your own implementation. 278 */ 279 tweetVisualizer: defaultTweetVisualizer, 280 281 /** 282 * The loadingIndicatorVisualizer gets called each time data is retrieved 283 * from Twitter to visualize the loading indicator. This visualizer is also 284 * used to hide the loading indicator. 285 * 286 * For details, see the implementation of the defaultLoadingIndicatorVisualizer. 287 */ 288 loadingIndicatorVisualizer: defaultLoadingIndicatorVisualizer, 289 290 /** 291 * The autorefreshTriggerVisualizer will be called if the autorefresh 292 * trigger should be visualized or hidden. 293 * 294 * For details, see the implementation of the autorefreshTriggerVisualizer. 295 */ 296 autorefreshTriggerVisualizer: defaultAutorefreshTriggerVisualizer, 297 298 /** 299 * An event handler is a function that gets called whenever the event you 300 * are interested in, occurs. 301 * 302 * The onDataRequest event handler will be called immediatly before calling 303 * Twitter to retrieve new data and gives you the opportunity to deny 304 * the call by returning false from the function. 305 * 306 * This feature might be used in conjunction with the paging feature, 307 * especially when using the "endless-scroll" paging mode, to avoid the 308 * exhaustion of remaining Twitter API calls, before the rate limit is 309 * reached. The stats parameter contains statistical infos and counters 310 * that you can examine to base your decision whether to return true or 311 * false. 312 */ 313 onDataRequestHandler: defaultOnDataRequestHandler, 314 315 /** 316 * The onRateLimitData event handler is called each time 317 * jTweetsAnywhere retrieved the rate limit data from Twitter. The actual 318 * rate limit data is contained in the stats object. 319 */ 320 onRateLimitDataHandler: defaultOnRateLimitDataHandler, 321 322 _tweetFeedConfig: 323 { 324 expandHovercards: false, 325 showTimestamp: 326 { 327 refreshInterval: 0 328 }, 329 showSource: false, 330 showGeoLocation: true, 331 showInReplyTo: true, 332 showProfileImages: null, 333 showUserScreenNames: null, 334 showUserFullNames: false, 335 includeRetweets: true, 336 paging: 337 { 338 mode: "none", 339 _limit: 0, 340 _offset: 0 341 }, 342 autorefresh: 343 { 344 mode: "none", 345 interval: 60, 346 duration: 3600, 347 _startTime: null, 348 _triggerElement: null 349 }, 350 _pageParam: 0, 351 _maxId: null, 352 _recLevel: 0, 353 _noData: false, 354 _clearBeforePopulate: false 355 }, 356 _tweetBoxConfig: 357 { 358 counter: true, 359 width: 515, 360 height: 65, 361 label: "What's happening?", 362 defaultContent: '', 363 onTweet: function(textTweet, htmlTweet) {} 364 }, 365 _connectButtonConfig: 366 { 367 size: "medium" 368 }, 369 _baseSelector: null, 370 _baseElement: null, 371 _tweetFeedElement: null, 372 _tweetFeedControlsElement: null, 373 _followButtonElement: null, 374 _loginInfoElement: null, 375 _connectButtonElement: null, 376 _tweetBoxElement: null, 377 _loadingIndicatorElement: null, 378 _noDataElement: null, 379 _tweetsCache: [], 380 _autorefreshTweetsCache: [], 381 _stats: 382 { 383 dataRequestCount: 0, 384 rateLimitPreventionCount: 0, 385 rateLimit: 386 { 387 remaining_hits: 150, 388 hourly_limit: 150 389 } 390 } 391 }, options); 392 393 // no main decorator? nothing to do! 394 if (!options.mainDecorator) 395 { 396 return; 397 } 398 399 options._baseSelector = this.selector; 400 401 // if username is an array, create the search query and flatten username 402 if (typeof(options.username) != 'string') 403 { 404 if (!options.searchParams) 405 { 406 options.searchParams = ['q=from:' + options.username.join(" OR from:")]; 407 } 408 409 options.username = options.username[0]; 410 } 411 412 // if showTweetFeed is not set to a boolean value, we expect the configuration of 413 // the tweet feed 414 if (typeof(options.showTweetFeed) == 'object') 415 { 416 $.extend(true, options._tweetFeedConfig, options.showTweetFeed); 417 } 418 419 // if showTweetBox is not set to a boolean value, we expect the configuration of 420 // the TweetBox 421 if (typeof(options.showTweetBox) == 'object') 422 { 423 options._tweetBoxConfig = options.showTweetBox; 424 options.showTweetBox = true; 425 } 426 427 // if showConnectButton is not set to a boolean value, we expect the 428 // configuration of the Connect Button 429 if (typeof(options.showConnectButton) == 'object') 430 { 431 options._connectButtonConfig = options.showConnectButton; 432 options.showConnectButton = true; 433 } 434 435 // to be compatible, check the deprecated option 'tweetProfileImagePresent' 436 if (options._tweetFeedConfig.showProfileImages == null) 437 { 438 options._tweetFeedConfig.showProfileImages = options.tweetProfileImagePresent; 439 } 440 441 // if _tweetFeedConfig.showProfileImages is not set to a boolean value, 442 // we decide to show a profile image if the feed represents a user's 443 // list or the results of a Twitter search 444 if (options._tweetFeedConfig.showProfileImages == null) 445 { 446 options._tweetFeedConfig.showProfileImages = (options.list || options.searchParams) && options.tweetProfileImageDecorator; 447 } 448 449 // if _tweetFeedConfig.showUserScreenNames is not set to a boolean value, 450 // we decide to show a username if the feed represents a user's
451 // list or the results of a Twitter search or a tweet is a native retweet 452 if (options._tweetFeedConfig.showUserScreenNames == null) 453 { 454 if (options.list || options.searchParams) 455 { 456 options._tweetFeedConfig.showUserScreenNames = true; 457 } 458 459 if (!options.tweetUsernameDecorator) 460 { 461 options._tweetFeedConfig.showUserScreenNames = false; 462 } 463 } 464 465 // if _tweetFeedConfig.showUserFullNames is not set to a boolean value, 466 // we decide to show a user's full name if the feed represents a user's 467 // list or the results of a Twitter search or a tweet is a native retweet 468 if (options._tweetFeedConfig.showUserFullNames == null) 469 { 470 if (options.list || options.searchParams) 471 { 472 options._tweetFeedConfig.showUserFullNames = true; 473 } 474 475 if (!options.tweetUsernameDecorator) 476 { 477 options._tweetFeedConfig.showUserFullNames = false; 478 } 479 } 480 481 options.count = validateRange(options.count, 0, options.searchParams ? 100 : 20); 482 483 options._tweetFeedConfig.autorefresh.interval = Math.max(30, options._tweetFeedConfig.autorefresh.interval); 484 485 options._tweetFeedConfig.paging._offset = 0; 486 options._tweetFeedConfig.paging._limit = options.count; 487 488 // internally, the decision of what parts of a widget are to be 489 // displayed is based on the existence of the decorators 490 if (options.count == 0 || !options.showTweetFeed) 491 { 492 options.tweetFeedDecorator = null; 493 options.tweetFeedControlsDecorator = null; 494 } 495 496 if (options._tweetFeedConfig.paging.mode == 'none') 497 { 498 options.tweetFeedControlsDecorator = null; 499 } 500 501 if (!options.showFollowButton) 502 { 503 options.followButtonDecorator = null; 504 } 505 506 if (!options.showTweetBox) 507 { 508 options.tweetBoxDecorator = null; 509 } 510 511 if (!options.showConnectButton) 512 { 513 options.connectButtonDecorator = null; 514 } 515 516 if (!options.showLoginInfo) 517 { 518 options.loginInfoDecorator = null; 519 } 520 521 if (!options._tweetFeedConfig.showTimestamp) 522 { 523 options.tweetTimestampDecorator = null; 524 } 525 526 if (!options._tweetFeedConfig.showSource) 527 { 528 options.tweetSourceDecorator = null; 529 } 530 531 if (!options._tweetFeedConfig.showGeoLocation) 532 { 533 options.tweetGeoLocationDecorator = null; 534 } 535 536 if (!options._tweetFeedConfig.showInReplyTo) 537 { 538 options.tweetInReplyToDecorator = null; 539 } 540 541 // setup ajax 542 $.ajaxSetup({ cache: true }); 543 544 return this.each(function() 545 { 546 // the DOM element, where to display the widget 547 options._baseElement = $(this); 548 549 // create the widget's necessary sub DOM elements 550 options._tweetFeedElement = options.tweetFeedDecorator ? $(options.tweetFeedDecorator(options)) : null; 551 options._tweetFeedControlsElement = options.tweetFeedControlsDecorator ? $(options.tweetFeedControlsDecorator(options)) : null; 552 options._followButtonElement = options.followButtonDecorator ? $(options.followButtonDecorator(options)) : null; 553 options._tweetBoxElement = options.tweetBoxDecorator ? $(options.tweetBoxDecorator(options)) : null; 554 options._connectButtonElement = options.connectButtonDecorator ? $(options.connectButtonDecorator(options)): null; 555 options._loginInfoElement = options.loginInfoDecorator ? $(options.loginInfoDecorator(options)) : null; 556 557 // add the widget to the DOM 558 options.mainDecorator(options); 559 560 populateTweetFeed(options); 561 populateAnywhereControls(options); 562 563 // bind event handlers to support paging 564 bindEventHandlers(options); 565 566 // install autorefresh support 567 options._tweetFeedConfig.autorefresh._startTime = new Date().getTime(); 568 startAutorefresh(options); 569 startTimestampRefresh(options); 570 }); 571 }; 572 defaultMainDecorator = function(options) 573 { 574 // defines the default sequence of the widget's elements 575 if (options._tweetFeedElement) 576 { 577 options._baseElement.append(options._tweetFeedElement); 578 } 579 580 if (options._tweetFeedControlsElement) 581 { 582 options._baseElement.append(options._tweetFeedControlsElement); 583 } 584 585 if (options._connectButtonElement) 586 { 587 options._baseElement.append(options._connectButtonElement); 588 } 589 590 if (options._loginInfoElement) 591 { 592 options._baseElement.append(options._loginInfoElement); 593 } 594 595 if (options._followButtonElement) 596 { 597 options._baseElement.append(options._followButtonElement); 598 } 599 600 if (options._tweetBoxElement) 601 { 602 options._baseElement.append(options._tweetBoxElement); 603 } 604 }; 605 defaultTweetFeedControlsDecorator = function(options) 606 { 607 // the default tweet feed's paging controls 608 var html = ''; 609 610 if (options._tweetFeedConfig.paging.mode == 'prev-next') 611 { 612 if (options.tweetFeedControlsPrevBtnDecorator) 613 { 614 html += options.tweetFeedControlsPrevBtnDecorator(options); 615 } 616 617 if (options.tweetFeedControlsNextBtnDecorator) 618 { 619 html += options.tweetFeedControlsNextBtnDecorator(options); 620 } 621 } 622 else if (options._tweetFeedConfig.paging.mode == 'endless-scroll') 623 { 624 // nothing to do here atm 625 } 626 else 627 { 628 if (options.tweetFeedControlsMoreBtnDecorator) 629 { 630 html += options.tweetFeedControlsMoreBtnDecorator(options); 631 } 632 } 633
634 return '<div class="jta-tweet-list-controls">' + html + '</div>'; 635 }; 636 defaultTweetFeedControlsMoreBtnDecorator = function(options) 637 { 638 return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-more">' + 'More' + '</span>'; 639 }; 640 defaultTweetFeedControlsPrevBtnDecorator = function(options) 641 { 642 return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-prev">' + 'Prev' + '</span>'; 643 }; 644 defaultTweetFeedControlsNextBtnDecorator = function(options) 645 { 646 return '<span class="jta-tweet-list-controls-button jta-tweet-list-controls-button-next">' + 'Next' + '</span>'; 647 }; 648 defaultTweetFeedAutorefreshTriggerDecorator = function(count, options) 649 { 650 var html = ''; 651 652 if (options.tweetFeedAutorefreshTriggerContentDecorator) 653 { 654 html = options.tweetFeedAutorefreshTriggerContentDecorator(count, options); 655 } 656 657 return '<li class="jta-tweet-list-autorefresh-trigger">' + html + '</li>'; 658 }; 659 defaultTweetFeedAutorefreshTriggerContentDecorator = function(count, options) 660 { 661 var content = '' + count + ' new ' + (count > 1 ? ' tweets' : ' tweet'); 662 663 return '<span class="jta-tweet-list-autorefresh-trigger-content">' + content + '</span>'; 664 }; 665 defaultTweetFeedDecorator = function(options) 666 { 667 // the default placeholder for the tweet feed is an unordered list 668 return '<ul class="jta-tweet-list"></ul>'; 669 }; 670 defaultTweetDecorator = function(tweet, options) 671 { 672 // the default tweet is made of the optional user's profile image and the 673 // tweet body inside a list item element 674 var html = ''; 675 676 if (options._tweetFeedConfig.showProfileImages) 677 { 678 html += options.tweetProfileImageDecorator(tweet, options); 679 } 680 681 if (options.tweetBodyDecorator) 682 { 683 html += options.tweetBodyDecorator(tweet, options); 684 } 685 686 html += '<div class="jta-clear"> </div>'; 687 688 return '<li class="jta-tweet-list-item">' + html + '</li>'; 689 }; 690 defaultTweetProfileImageDecorator = function(tweet, options) 691 { 692 // if tweet is a native retweet, use the retweet's profile 693 var t = tweet.retweetedStatus || tweet; 694 695 // the default profile image decorator simply adds a link to the user's Twitter profile 696 var screenName = t.user ? t.user.screenName : false || t.from_user; 697 var imageUrl = t.user ? t.user.profileImageURL : false || t.profile_image_url; 698 699 var html = 700 '<a class="jta-tweet-profile-image-link" href="http://twitter.com/' + screenName + '" target="_blank">' + 701 '<img src="' + imageUrl + '" alt="' + screenName + '"' + 702 (isAnywherePresent() ? '' : (' title="' + screenName + '"')) + 703 '/>' + 704 '</a>'; 705 706 return '<div class="jta-tweet-profile-image">' + html + '</div>'; 707 }; 708 defaultTweetBodyDecorator = function(tweet, options) 709 { 710 // the default tweet body contains the tweet text and the tweet's creation date 711 var html = ''; 712 713 if (options.tweetTextDecorator) 714 { 715 html += options.tweetTextDecorator(tweet, options); 716 } 717 718 if (options.tweetAttributesDecorator) 719 { 720 html += options.tweetAttributesDecorator(tweet, options); 721 } 722 723 return '<div class="jta-tweet-body ' + (options._tweetFeedConfig.showProfileImages ? 'jta-tweet-body-list-profile-image-present' : '') + '">' + html + '</div>'; 724 }; 725 defaultTweetTextDecorator = function(tweet, options) 726 { 727 var tweetText = tweet.text; 728 729 // if usernames should be visible and tweet is a native retweet, use 730 // the original tweet text 731 if (tweet.retweeted && 732 ( 733 options._tweetFeedConfig.showUserScreenNames || 734 options._tweetFeedConfig.showUserScreenNames == null || 735 options._tweetFeedConfig.showUserFullNames || 736 options._tweetFeedConfig.showUserFullNames == null 737 ) 738 ) 739 { 740 tweetText = tweet.retweetedStatus.text; 741 } 742 743 // the default tweet text decorator optionally marks links, @usernames, 744 // and #hashtags 745 if (options.linkDecorator) 746 { 747 tweetText = options.linkDecorator(tweetText, options); 748 } 749 750 if (options.usernameDecorator) 751 { 752 tweetText = options.usernameDecorator(tweetText, options); 753 } 754 755 if (options.hashtagDecorator) 756 { 757 tweetText = options.hashtagDecorator(tweetText, options); 758 } 759 760 if (options._tweetFeedConfig.showUserScreenNames || 761 options._tweetFeedConfig.showUserFullNames || 762 tweet.retweeted && 763 ( 764 options._tweetFeedConfig.showUserScreenNames == null || 765 options._tweetFeedConfig.showUserFullNames == null 766 ) 767 ) 768 { 769 tweetText = options.tweetUsernameDecorator(tweet, options) + ' ' + tweetText; 770 } 771 772 return '<span class="jta-tweet-text">' + tweetText + '</span>'; 773 }; 774 defaultTweetUsernameDecorator = function(tweet, options) 775 {
776 // if tweet is a native retweet, use the retweet's profile 777 var t = tweet.retweetedStatus || tweet; 778 var screenName = t.user ? t.user.screenName : false || t.from_user; 779 var fullName = t.user ? t.user.name : null; 780 781 var htmlScreenName; 782 if (screenName && (options._tweetFeedConfig.showUserScreenNames || (options._tweetFeedConfig.showUserScreenNames == null && tweet.retweetedStatus))) 783 { 784 htmlScreenName = 785 '<span class="jta-tweet-user-screen-name">' + 786 '<a class="jta-tweet-user-screen-name-link" href="http://twitter.com/' + screenName + '" target="_blank">' + 787 screenName + 788 '</a>' + 789 '</span>'; 790 } 791 792 var htmlFullName; 793 if (fullName && (options._tweetFeedConfig.showUserFullNames || (options._tweetFeedConfig.showUserFullNames == null && tweet.retweetedStatus))) 794 { 795 htmlFullName = 796 '<span class="jta-tweet-user-full-name">' + 797 (htmlScreenName ? ' (' : '') + 798 '<a class="jta-tweet-user-full-name-link" href="http://twitter.com/' + screenName + '" name="' + screenName + '" target="_blank">' + 799 fullName + 800 '</a>' + 801 (htmlScreenName ? ')' : '') + 802 '</span>'; 803 } 804 805 var html = ""; 806 807 if (htmlScreenName) 808 { 809 html += htmlScreenName; 810 } 811 812 if (htmlFullName) 813 { 814 if (htmlScreenName) 815 { 816 html += ' '; 817 } 818 819 html += htmlFullName; 820 } 821 822 if (htmlScreenName || htmlFullName) 823 { 824 html = 825 '<span class="jta-tweet-user-name">' + 826 (tweet.retweeted ? 'RT ' : '') + 827 html + 828 '</span>'; 829 } 830 831 return html; 832 }; 833 defaultTweetAttributesDecorator = function(tweet, options) 834 { 835 var html = ''; 836 837 if (options.tweetTimestampDecorator || 838 options.tweetSourceDecorator || 839 options.tweetGeoLocationDecorator || 840 options.tweetInReplyToDecorator || 841 (tweet.retweeted && options.tweetRetweeterDecorator) 842 ) 843 { 844 html += '<span class="jta-tweet-attributes">'; 845 846 if (options.tweetTimestampDecorator) 847 { 848 html += options.tweetTimestampDecorator(tweet, options); 849 } 850 851 if (options.tweetSourceDecorator) 852 { 853 html += options.tweetSourceDecorator(tweet, options); 854 } 855 856 if (options.tweetGeoLocationDecorator) 857 { 858 html += options.tweetGeoLocationDecorator(tweet, options); 859 } 860 861 if (options.tweetInReplyToDecorator) 862 { 863 html += options.tweetInReplyToDecorator(tweet, options); 864 } 865 866 if (tweet.retweetedStatus && options.tweetRetweeterDecorator) 867 { 868 html += options.tweetRetweeterDecorator(tweet, options); 869 } 870 871 html += '</span>'; 872 } 873 874 return html; 875 }; 876 defaultTweetTimestampDecorator = function(tweet, options) 877 { 878 // the default tweet timestamp decorator does a little bit of Twitter like formatting. 879 880 // if tweet is a native retweet, use the retweet's timestamp 881 var tw = tweet.retweetedStatus || tweet; 882 883 // reformat timestamp from Twitter, so IE is happy 884 var createdAt = formatDate(tw.createdAt); 885 886 // format the timestamp by the tweetTimestampFormatter 887 var tweetTimestamp = options.tweetTimestampFormatter(createdAt); 888 var tweetTimestampTooltip = options.tweetTimestampTooltipFormatter(createdAt); 889 890 var screenName = tw.user ? tw.user.screenName : false || tw.from_user; 891 var html = 892 '<span class="jta-tweet-timestamp">' + 893 '<a class="jta-tweet-timestamp-link" data-timestamp="' + createdAt + 894 '" href="http://twitter.com/' + screenName + '/status/' + tw.id + '" target="_blank" title="' + 895 tweetTimestampTooltip + '">' + 896 tweetTimestamp + 897 '</a>' + 898 '</span>'; 899 900 return html; 901 }; 902 defaultTweetTimestampTooltipFormatter = function(timeStamp) 903 { 904 var d = new Date(timeStamp); 905 906 return d.toLocaleString(); 907 }; 908 defaultTweetTimestampFormatter = function(timeStamp) 909 { 910 var now = new Date(); 911 912 var diff = parseInt((now.getTime() - Date.parse(timeStamp)) / 1000); 913 914 var tweetTimestamp = ''; 915 if (diff < 60) 916 { 917 tweetTimestamp += diff + ' second' + (diff == 1 ? '' : 's') + ' ago'; 918 } 919 else if (diff < 3600) 920 { 921 var t = parseInt((diff + 30) / 60); 922 tweetTimestamp += t + ' minute' + (t == 1 ? '' : 's') + ' ago'; 923 } 924 else if (diff < 86400) 925 { 926 var t = parseInt((diff + 1800) / 3600); 927 tweetTimestamp += t + ' hour' + (t == 1 ? '' : 's') + ' ago'; 928 } 929 else 930 { 931 var d = new Date(timeStamp); 932 var period = 'AM'; 933 934 var hours = d.getHours(); 935 if (hours > 12) 936 { 937 hours -= 12; 938 period = 'PM'; 939 } 940 941 var mins = d.getMinutes(); 942 var minutes = (mins < 10 ? '0' : '') + mins; 943 944 var monthName = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]; 945 946 tweetTimestamp += monthName[d.getMonth()] + ' ' + d.getDate(); 947 948 if (d.getFullYear() < now.getFullYear()) 949 { 950 tweetTimestamp += ', ' + d.getFullYear(); 951 } 952 953 var t = parseInt((diff + 43200) / 86400); 954 tweetTimestamp += ' (' + t + ' day' + (t == 1 ? '' : 's') + ' ago)'; 955 } 956 957 return tweetTimestamp; 958 }; 959 exTimestampFormatter = function(timeStamp) 960 { 961 var diff = parseInt((new Date().getTime() - Date.parse(timeStamp)) / 1000); 962 963 var tweetTimestamp = ''; 964 if (diff < 60) 965 { 966 tweetTimestamp += 'less than a minute ago'; 967 } 968 else if (diff < 3600) 969 { 970 var t = parseInt((diff + 30) / 60); 971 tweetTimestamp += t + ' minute' + (t == 1 ? '' : 's') + ' ago'; 972 } 973 else if (diff < 86400) 974 { 975 var t = parseInt((diff + 1800) / 3600); 976 tweetTimestamp += 'about ' + t + ' hour' + (t == 1 ? '' : 's') + ' ago'; 977 } 978 else 979 { 980 var t = parseInt((diff + 43200) / 86400); 981 tweetTimestamp += 'about ' + t + ' day' + (t == 1 ? '' : 's') + ' ago'; 982 983 var d = new Date(timeStamp); 984 var period = 'AM'; 985 986 var hours = d.getHours(); 987 if (hours > 12) 988 { 989 hours -= 12; 990 period = 'PM'; 991 } 992 993 var mins = d.getMinutes(); 994 var minutes = (mins < 10 ? '0' : '') + mins; 995 996 tweetTimestamp += ' (' + hours + ':' + minutes + ' ' + period + ' ' + (d.getMonth() + 1) + '/' + d.getDate() + '/' + d.getFullYear() + ')'; 997 } 998 999 return tweetTimestamp; 1000 }; 1001 defaultTweetSourceDecorator = function(tweet, options) 1002 {
1003 // if tweet is a native retweet, use the retweet's source 1004 var tw = tweet.retweetedStatus || tweet; 1005 1006 var source = tw.source.replace(/\<\;/gi,'<').replace(/\>\;/gi,'>').replace(/\"\;/gi,'"'); 1007 var html = 1008 '<span class="jta-tweet-source">' + 1009 ' via ' + 1010 '<span class="jta-tweet-source-link">' + 1011 source + 1012 '</span>' + 1013 '</span>'; 1014 1015 return html; 1016 }; 1017 defaultTweetGeoLocationDecorator = function(tweet, options) 1018 { 1019 var html = ''; 1020 1021 // if tweet is a native retweet, use the retweet's source 1022 var tw = tweet.retweetedStatus || tweet; 1023 1024 var q; 1025 if (tw.geoLocation && tw.geoLocation.latitude && tw.geoLocation.longitude) 1026 { 1027 q = tw.geoLocation.latitude + "," + tw.geoLocation.longitude; 1028 } 1029 else if (tw.place && tw.place.fullName) 1030 { 1031 q = tw.place.fullName; 1032 } 1033 1034 if (q) 1035 { 1036 var location = 'here'; 1037 if (tw.place && tw.place.fullName) 1038 { 1039 location = tw.place.fullName; 1040 } 1041 1042 var link = 'http://maps.google.com/maps?q=' + q; 1043 1044 html = 1045 '<span class="jta-tweet-location">' + 1046 ' from ' + 1047 '<a class="jta-tweet-location-link" href="' + link + '" target="_blank">' + 1048 location + 1049 '</a>' + 1050 '</span>'; 1051 } 1052 1053 return html; 1054 }; 1055 defaultTweetInReplyToDecorator = function(tweet, options) 1056 { 1057 // if tweet is a native retweet, use the retweet's source 1058 var tw = tweet.retweetedStatus || tweet; 1059 1060 var html = ''; 1061 if (tw.inReplyToStatusId && tw.inReployToScreenName) 1062 { 1063 html = 1064 '<span class="jta-tweet-inreplyto">' + 1065 ' ' + 1066 '<a class="jta-tweet-inreplyto-link" href="http://twitter.com/' + tw.inReployToScreenName + '/status/' + tw.inReplyToStatusId + '" target="_blank">' + 1067 'in reply to ' + tw.inReployToScreenName + 1068 '</a>' + 1069 '</span>'; 1070 } 1071 1072 return html; 1073 }; 1074 defaultTweetRetweeterDecorator = function(tweet, options) 1075 { 1076 var html = ''; 1077 1078 if (tweet.retweetedStatus) 1079 { 1080 var screenName = tweet.user ? tweet.user.screenName : false || tweet.from_user; 1081 var rtc = (tweet.retweetedStatus.retweetCount || 0) - 1; 1082 1083 var link = 1084 '<a class="jta-tweet-retweeter-link" href="http://twitter.com/' + screenName + '" target="_blank">' + 1085 screenName + 1086 '</a>'; 1087 var rtcount = ' and ' + rtc + (rtc > 1 ? ' others' : ' other'); 1088 html = 1089 '<br/>' + 1090 '<span class="jta-tweet-retweeter">' + 1091 'Retweeted by ' + link + 1092 (rtc > 0 ? rtcount : '') + 1093 '</span>'; 1094 } 1095 1096 return html; 1097 }; 1098 defaultConnectButtonDecorator = function(options) 1099 { 1100 // the default placeholder for the @Anywhere ConnectButton 1101 return '<div class="jta-connect-button"></div>'; 1102 }; 1103 defaultLoginInfoDecorator = function(options) 1104 { 1105 // the default placeholder for the LoginInfo 1106 return '<div class="jta-login-info"></div>'; 1107 }; 1108 defaultLoginInfoContentDecorator = function(options, T) 1109 { 1110 // the default markup of the LoginInfo content: the user's profile image, the 1111 // user's screen_name and a "button" to sign out 1112 var html = ''; 1113 1114 if (T.isConnected()) 1115 { 1116 var screenName = T.currentUser.data('screen_name'); 1117 var imageUrl = T.currentUser.data('profile_image_url'); 1118 1119 html = 1120 '<div class="jta-login-info-profile-image">' + 1121 '<a href="http://twitter.com/' + screenName + '" target="_blank">' + 1122 '<img src="' + imageUrl + '" alt="' + screenName + '" title="' + screenName + '"/>' + 1123 '</a>' + 1124 '</div>' + 1125 '<div class="jta-login-info-block">' + 1126 '<div class="jta-login-info-screen-name">' + 1127 '<a href="http://twitter.com/' + screenName + '" target="_blank">' + screenName + '</a>' + 1128 '</div>' + 1129 '<div class="jta-login-info-sign-out">' + 1130 'Sign out' + 1131 '</div>' + 1132 '</div>' + 1133 '<div class="jta-clear"> </div>' 1134 ; 1135 } 1136 1137 return html; 1138 }; 1139 defaultFollowButtonDecorator = function(options) 1140 { 1141 // the default placeholder for the @Anywhere FollowButton 1142 return '<div class="jta-follow-button"></div>'; 1143 }; 1144 defaultTweetBoxDecorator = function(options) 1145 { 1146 // the default placeholder for the @Anywhere TweetBox 1147 return '<div class="jta-tweet-box"></div>'; 1148 }; 1149 defaultLinkDecorator = function(text, options) 1150 { 1151 // the regex to markup links 1152 return text.replace(/((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi,'<a href="$1" class="jta-tweet-a jta-tweet-link" target="_blank" rel="nofollow">$1<\/a>'); 1153 }; 1154 defaultUsernameDecorator = function(text, options) 1155 { 1156 // the regex to markup @usernames. if @Anywhere is present the task is left to 1157 // them 1158 return isAnywherePresent() ? text : text.replace(/@([a-zA-Z0-9_]+)/gi,'@<a href="http://twitter.com/$1" class="jta-tweet-a twitter-anywhere-user" target="_blank" rel="nofollow">$1<\/a>'); 1159 }; 1160 defaultHashtagDecorator = function(text, options) 1161 { 1162 // the regex to markup #hashtags 1163 return text.replace(/#([a-zA-Z0-9_]+)/gi,'<a href="http://twitter.com/search?q=%23$1" class="jta-tweet-a jta-tweet-hashtag" title="#$1" target="_blank" rel="nofollow">#$1<\/a>'); 1164 }; 1165 defaultLoadingDecorator = function(options) 1166 { 1167 // the default loading decorator simply says: loading ... 1168 return '<li class="jta-loading">loading ...</li>'; 1169 }; 1170 defaultErrorDecorator = function(errorText, options) 1171 { 1172 // the default error decorator shows the error message 1173 return '<li class="jta-error">ERROR: ' + errorText + '</li>'; 1174 }; 1175 defaultNoDataDecorator = function(options) 1176 { 1177 // the default no-data decorator simply says: No more data 1178 return '<li class="jta-nodata">No more data</li>'; 1179 }; 1180 1181 defaultTweetFilter = function(tweet, options) 1182 { 1183 return true; 1184 }; 1185 1186 defaultTweetVisualizer = function(tweetFeedElement, tweetElement, inserter, options) 1187 { 1188 // insert (append/prepend) the tweetElement to the tweetFeedElement 1189 tweetFeedElement[inserter](tweetElement); 1190 }; 1191 defaultLoadingIndicatorVisualizer = function(tweetFeedElement, loadingIndicatorElement, options, callback) 1192 { 1193 defaultVisualizer(tweetFeedElement, loadingIndicatorElement, 'append', 'fadeIn', 600, 'fadeOut', 200, callback); 1194 }; 1195 defaultAutorefreshTriggerVisualizer = function(tweetFeedElement, triggerElement, options, callback) 1196 { 1197 defaultVisualizer(tweetFeedElement, triggerElement, 'prepend', 'slideDown', 600, 'fadeOut', 200, callback); 1198 }; 1199 defaultVisualizer = function(container, element, inserter, effectIn, durationIn, effectOut, durationOut, callback) 1200 { 1201 // if param container is null element has to be removed from 1202 // the DOM, else element has to be inserted in container 1203 1204 // if param callback is not null, the callback function must be called 1205 // in any case, if the visualizer is done 1206 1207 var cb = function() 1208 { 1209 if (callback) 1210 { 1211 callback(); 1212 } 1213 }; 1214 1215 if (container) 1216 { 1217 element.hide(); 1218 container[inserter](element); 1219 element[effectIn](durationIn, cb); 1220 } 1221 else 1222 { 1223 element[effectOut](durationOut, function() 1224 { 1225 element.remove(); 1226 cb(); 1227 }); 1228 } 1229 }; 1230 1231 defaultOnDataRequestHandler = function(stats, options) 1232 { 1233 return true; 1234 }; 1235 defaultOnRateLimitDataHandler = function(stats, options) 1236 { 1237 }; 1238 1239 updateLoginInfoElement = function(options, T) 1240 { 1241 // update the content of the LoginInfo element 1242 if (options._loginInfoElement && options.loginInfoContentDecorator) 1243 { 1244 options._loginInfoElement.children().remove(); 1245 options._loginInfoElement.append(options.loginInfoContentDecorator(options, T)); 1246 $(options._baseSelector + ' .jta-login-info-sign-out').bind('click', function() 1247 { 1248 twttr.anywhere.signOut(); 1249 }); 1250 } 1251 }; 1252 getFeedUrl = function(options, flPaging) 1253 { 1254 var url = '/apis/twitter'; 1255 1256 if (options.searchParams) 1257 { 1258 url += '/search?' + 1259 ((options.searchParams instanceof Array) ? options.searchParams.join('&') : options.searchParams) + 1260 '&rpp=100'; 1261 } 1262 else if (options.list) 1263 { 1264 url += options.username + '/lists/' + options.list; 1265 } 1266 else 1267 { 1268 url += '/tweets/' + options.username + '?count=20'; 1269 1270 if (options._tweetFeedConfig.includeRetweets) 1271 { 1272 url += '&include_rts=true';
1273 } 1274 1275 if (flPaging) 1276 { 1277 url += 1278 (options._tweetFeedConfig._maxId ? '&max_id=' + options._tweetFeedConfig._maxId : '') + 1279 '&page=' + options._tweetFeedConfig._pageParam; 1280 } 1281 } 1282 1283 url += '&callback=?'; 1284 return url; 1285 }; 1286 isAnywherePresent = function() 1287 { 1288 // check, if @Anywhere is present 1289 return typeof(twttr) != 'undefined'; 1290 }; 1291 clearTweetFeed = function(options) 1292 { 1293 if (options._tweetFeedElement) 1294 { 1295 options._tweetFeedElement.empty(); 1296 } 1297 }; 1298 populateTweetFeed = function(options) 1299 { 1300 // if a tweet feed is to be displayed, get the tweets and show them 1301 if (options.tweetDecorator && options._tweetFeedElement) 1302 { 1303 getPagedTweets(options, function(tweets, options) 1304 { 1305 if (options._tweetFeedConfig._clearBeforePopulate) 1306 { 1307 clearTweetFeed(options); 1308 } 1309 1310 hideLoadingIndicator(options, function() 1311 { 1312 // process the tweets 1313 $.each(tweets, function(idx, tweet) 1314 { 1315 // decorate the tweet and give it to the tweet visualizer 1316 options.tweetVisualizer( 1317 options._tweetFeedElement, 1318 $(options.tweetDecorator(tweet, options)), 1319 'append', 1320 options 1321 ); 1322 }); 1323 1324 if (options._tweetFeedConfig._noData && options.noDataDecorator && !options._tweetFeedConfig._noDataElement) 1325 { 1326 options._tweetFeedConfig._noDataElement = $(options.noDataDecorator(options)); 1327 options._tweetFeedElement.append(options._tweetFeedConfig._noDataElement); 1328 } 1329 1330 if (options._tweetFeedConfig._clearBeforePopulate) 1331 { 1332 options._tweetFeedElement.scrollTop(0); 1333 } 1334 1335 addHovercards(options); 1336 }); 1337 }); 1338 } 1339 }; 1340 populateTweetFeed2 = function(options) 1341 { 1342 if (options._tweetFeedElement && options._autorefreshTweetsCache.length > 0) 1343 { 1344 if (options._tweetFeedConfig.autorefresh.mode == 'trigger-insert') 1345 { 1346 if (options._tweetFeedConfig.autorefresh._triggerElement) 1347 { 1348 if (options.tweetFeedAutorefreshTriggerContentDecorator) 1349 { 1350 options._tweetFeedConfig.autorefresh._triggerElement.html( 1351 options.tweetFeedAutorefreshTriggerContentDecorator(options._autorefreshTweetsCache.length, options) 1352 ); 1353 } 1354 } 1355 else 1356 { 1357 if (options.tweetFeedAutorefreshTriggerDecorator) 1358 { 1359 options._tweetFeedConfig.autorefresh._triggerElement = 1360 $(options.tweetFeedAutorefreshTriggerDecorator(options._autorefreshTweetsCache.length, options)); 1361 options._tweetFeedConfig.autorefresh._triggerElement.bind('click', function() 1362 { 1363 options.autorefreshTriggerVisualizer( 1364 null, 1365 options._tweetFeedConfig.autorefresh._triggerElement, 1366 options, 1367 function() 1368 { 1369 insertTriggerTweets(options); 1370 } 1371 ); 1372 options._tweetFeedConfig.autorefresh._triggerElement = null; 1373 }); 1374 1375 options.autorefreshTriggerVisualizer(options._tweetFeedElement, options._tweetFeedConfig.autorefresh._triggerElement, options); 1376 } 1377 } 1378 } 1379 else 1380 { 1381 insertTriggerTweets(options); 1382 } 1383 } 1384 }; 1385 insertTriggerTweets = function(options) 1386 { 1387 // populate the tweet feed with tweets from the autorefresh cache 1388 if (options.tweetDecorator && options._autorefreshTweetsCache.length > 0) 1389 { 1390 // process the autorefresh cache 1391 while (options._autorefreshTweetsCache.length > 0) 1392 { 1393 // get the last tweet and remove it from the autorefresh cache 1394 var tweet = options._autorefreshTweetsCache.pop(); 1395 1396 // put that tweet on top of the tweets cache 1397 options._tweetsCache.unshift(tweet); 1398 1399 // adjust paging offset 1400 options._tweetFeedConfig.paging._offset++; 1401 1402 // decorate the tweet and give it to the tweet visualizer 1403 options.tweetVisualizer( 1404 options._tweetFeedElement, 1405 $(options.tweetDecorator(tweet, options)), 1406 'prepend', 1407 options 1408 ); 1409 } 1410 1411 addHovercards(options); 1412 } 1413 }; 1414 addHovercards = function(options) 1415 { 1416 if (isAnywherePresent()) 1417 { 1418 // if @Anywhere is present, append Hovercards to @username and 1419 // profile images 1420 twttr.anywhere(function(T) 1421 { 1422 T(options._baseSelector + ' .jta-tweet-list').hovercards({expanded: options._tweetFeedConfig.expandHovercards}); 1423 T(options._baseSelector + ' .jta-tweet-profile-image img').hovercards( 1424 { 1425 expanded: options._tweetFeedConfig.expandHovercards, 1426 username: function(node) { return node.alt; } 1427 }); 1428 T(options._baseSelector + ' .jta-tweet-retweeter-link').hovercards( 1429 { 1430 expanded: options._tweetFeedConfig.expandHovercards, 1431 username: function(node) { return node.text; } 1432 }); 1433 T(options._baseSelector + ' .jta-tweet-user-screen-name-link').hovercards( 1434 { 1435 expanded: options._tweetFeedConfig.expandHovercards, 1436 username: function(node) { return node.text; } 1437 }); 1438 T(options._baseSelector + ' .jta-tweet-user-full-name-link').hovercards( 1439 { 1440 expanded: options._tweetFeedConfig.expandHovercards, 1441 username: function(node) { return node.name; } 1442 }); 1443 }); 1444 } 1445 }; 1446 populateAnywhereControls = function(options) 1447 { 1448 if (isAnywherePresent()) 1449 { 1450 twttr.anywhere(function(T) 1451 { 1452 // optionally add an @Anywhere TweetBox 1453 if (options.tweetBoxDecorator) 1454 { 1455 T(options._baseSelector + ' .jta-tweet-box').tweetBox(options._tweetBoxConfig); 1456 } 1457 1458 // optionally add an @Anywhere FollowButton 1459 if (options.followButtonDecorator) 1460 { 1461 T(options._baseSelector + ' .jta-follow-button').followButton(options.username); 1462 } 1463 1464 // optionally add an @Anywhere ConnectButton 1465 if (options.connectButtonDecorator) 1466 { 1467 var o = $.extend( 1468 { 1469 authComplete: function(user) 1470 { 1471 // display/update login infos on connect/signin event 1472 updateLoginInfoElement(options, T); 1473 }, 1474 signOut: function() 1475 { 1476 // display/update login infos on signout event 1477 updateLoginInfoElement(options, T); 1478 } 1479 }, options._connectButtonConfig); 1480 1481 T(options._baseSelector + ' .jta-connect-button').connectButton(o); 1482 1483 // display/update login infos 1484 updateLoginInfoElement(options, T); 1485 } 1486 }); 1487 } 1488 }; 1489 bindEventHandlers = function(options) 1490 { 1491 if (options.tweetFeedControlsDecorator) 1492 { 1493 if (options._tweetFeedConfig.paging.mode == 'prev-next') 1494 {
1495 $(options._baseSelector + ' .jta-tweet-list-controls-button-prev').bind('click', function() 1496 { 1497 if (!isLoading(options) && options._tweetFeedConfig.paging._offset > 0) 1498 { 1499 prevPage(options, true); 1500 } 1501 }); 1502 $(options._baseSelector + ' .jta-tweet-list-controls-button-next').bind('click', function() 1503 { 1504 if (!isLoading(options)) 1505 { 1506 nextPage(options, true); 1507 } 1508 }); 1509 } 1510 else if (options._tweetFeedConfig.paging.mode == 'endless-scroll') 1511 { 1512 options._tweetFeedElement.bind("scroll", function() 1513 { 1514 if (!isLoading(options) && ($(this)[0].scrollHeight - $(this).scrollTop() == $(this).outerHeight())) 1515 { 1516 nextPage(options, false); 1517 } 1518 }); 1519 } 1520 else 1521 { 1522 $(options._baseSelector + ' .jta-tweet-list-controls-button-more').bind('click', function() 1523 { 1524 if (!isLoading(options)) 1525 { 1526 nextPage(options, false); 1527 } 1528 }); 1529 } 1530 } 1531 }; 1532 nextPage = function(options, flClear) 1533 { 1534 doPage(options, flClear, Math.min(options._tweetFeedConfig.paging._offset + options._tweetFeedConfig.paging._limit, options._tweetsCache.length)); 1535 }; 1536 prevPage = function(options, flClear) 1537 { 1538 doPage(options, flClear, Math.max(0, options._tweetFeedConfig.paging._offset - options._tweetFeedConfig.paging._limit)); 1539 }; 1540 doPage = function(options, flClear, newOffset) 1541 { 1542 options._tweetFeedConfig.paging._offset = newOffset; 1543 options._tweetFeedConfig._clearBeforePopulate = flClear; 1544 1545 populateTweetFeed(options); 1546 }; 1547 startAutorefresh = function(options) 1548 { 1549 if (options._tweetFeedConfig.autorefresh.mode != 'none' && 1550 options._tweetFeedConfig.paging.mode != 'prev-next' && 1551 options._tweetFeedConfig.autorefresh.duration != 0 && 1552 ( 1553 options._tweetFeedConfig.autorefresh.duration < 0 || 1554 (new Date().getTime() - options._tweetFeedConfig.autorefresh._startTime) <= options._tweetFeedConfig.autorefresh.duration * 1000 1555 ) 1556 ) 1557 { 1558 window.setTimeout(function() { processAutorefresh(options); }, options._tweetFeedConfig.autorefresh.interval * 1000); 1559 } 1560 }; 1561 stopAutorefresh = function(options) 1562 { 1563 options._tweetFeedConfig.autorefresh.duration = 0; 1564 }; 1565 processAutorefresh = function(options) 1566 { 1567 if (options._tweetFeedConfig.autorefresh.duration != 0) 1568 { 1569 // load the data ... 1570 getRateLimitedData(options, true, getFeedUrl(options, false), function(data, options) 1571 { 1572 // reverse the sequence of the autorefresh tweets ... 1573 var tweets = (data.results || data).slice(0); 1574 tweets.reverse(); 1575 1576 // ...then process them 1577 $.each(tweets, function(idx, tweet) 1578 { 1579 // if this tweet is already in the standard tweets cache, ignore 1580 if (!isTweetInCache(tweet, options)) 1581 { 1582 // optionally filter tweet ... 1583 if (options.tweetFilter(tweet, options)) 1584 { 1585 // ... then put it to the top of the autorefresh cache 1586 options._autorefreshTweetsCache.unshift(tweet); 1587 } 1588 } 1589 }); 1590 1591 populateTweetFeed2(options); 1592 }); 1593 1594 // restart autorefresh 1595 startAutorefresh(options); 1596 } 1597 }; 1598 startTimestampRefresh = function(options) 1599 { 1600 if ( 1601 options.tweetTimestampDecorator && 1602 typeof(options._tweetFeedConfig.showTimestamp) == 'object' && 1603 options._tweetFeedConfig.showTimestamp.refreshInterval > 0 1604 ) 1605 { 1606 window.setTimeout(function() { processTimestampRefresh(options); }, options._tweetFeedConfig.showTimestamp.refreshInterval * 1000); 1607 } 1608 }; 1609 processTimestampRefresh = function(options) 1610 { 1611 $.each(options._tweetFeedElement.find('.jta-tweet-timestamp-link'), function(idx, element) 1612 { 1613 var dataTimestamp = $(element).attr('data-timestamp'); 1614 1615 $(element).html(options.tweetTimestampFormatter(dataTimestamp)); 1616 }); 1617 1618 startTimestampRefresh(options); 1619 }; 1620 isTweetInCache = function(tweet, options) 1621 { 1622 var l = options._tweetsCache.length; 1623 1624 for (var i = 0; i < l; i++) 1625 { 1626 if (tweet.id == options._tweetsCache[i].id) 1627 { 1628 return true; 1629 } 1630 } 1631 1632 return false; 1633 }; 1634 showLoadingIndicator = function(options) 1635 { 1636 if (options._tweetFeedElement && options.loadingDecorator && !options._loadingIndicatorElement) 1637 { 1638 options._loadingIndicatorElement = $(options.loadingDecorator(options)); 1639 options.loadingIndicatorVisualizer(options._tweetFeedElement, options._loadingIndicatorElement, options, null); 1640 options._tweetFeedElement.scrollTop(1000000); 1641 } 1642 }; 1643 hideLoadingIndicator = function(options, callback) 1644 { 1645 if (options._loadingIndicatorElement) 1646 { 1647 options.loadingIndicatorVisualizer(null, options._loadingIndicatorElement, options, callback); 1648 options._loadingIndicatorElement = null; 1649 } 1650 else 1651 { 1652 if (callback) 1653 { 1654 callback(); 1655 } 1656 } 1657 }; 1658 isLoading = function(options) 1659 { 1660 return options._loadingIndicatorElement != null; 1661 }; 1662 formatDate = function(dateStr) 1663 { 1664 return dateStr.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'); 1665 }; 1666 validateRange = function(num, lo, hi) 1667 { 1668 if (num < lo) 1669 num = lo; 1670 1671 if (num > hi) 1672 num = hi; 1673 1674 return num; 1675 }; 1676 showError = function(options, errorText) 1677 { 1678 if (options.errorDecorator && options._tweetFeedElement) 1679 { 1680 options._tweetFeedElement.append(options.errorDecorator(errorText, options)); 1681 } 1682 }; 1683 getPagedTweets = function(options, callback) 1684 { 1685 options._tweetFeedConfig._recLevel = 0; 1686 1687 getRecPagedTweets(options, options._tweetFeedConfig.paging._offset, options._tweetFeedConfig.paging._limit, callback); 1688 }; 1689 getRecPagedTweets = function(options, offset, limit, callback) 1690 { 1691 ++options._tweetFeedConfig._recLevel; 1692 1693 if (offset + limit <= options._tweetsCache.length || 1694 options._tweetFeedConfig._recLevel > 3 || 1695 options._tweetFeedConfig._noData 1696 ) 1697 { 1698 // if the requested data is already cached or the max. no. of 1699 // consecutive API calls is reached, use the records 1700 1701 if (offset + limit > options._tweetsCache.length) 1702 { 1703 limit = Math.max(0, options._tweetsCache.length - offset); 1704 } 1705 1706 var tweets = []; 1707 1708 for (var i = 0; i < limit; i++) 1709 { 1710 tweets[i] = options._tweetsCache[offset + i]; 1711 } 1712 1713 callback(tweets, options); 1714 } 1715 else 1716 { 1717 // ... if not, load the data, fill the cache and try again 1718 ++options._tweetFeedConfig._pageParam; 1719 1720 getRateLimitedData(options, false, getFeedUrl(options, true), function(data, options) 1721 { 1722 var tweets = data.results || data; 1723 1724 if (tweets.length == 0) 1725 { 1726 options._tweetFeedConfig._noData = true; 1727 } 1728 else 1729 { 1730 $.each(tweets, function(idx, tweet) 1731 { 1732 // save the first tweet id for subsequent paging requests 1733 if (!options._tweetFeedConfig._maxId) 1734 { 1735 options._tweetFeedConfig._maxId = tweet.id; 1736 } 1737 1738 // optionally filter tweet ... 1739 if (options.tweetFilter(tweet, options)) 1740 { 1741 // then put it into the cache 1742 options._tweetsCache.push(tweet); 1743 } 1744 }); 1745 } 1746 1747 getRecPagedTweets(options, offset, limit, callback); 1748 }); 1749 } 1750 }; 1751 getRateLimitedData = function(options, flAutorefresh, url, callback) 1752 { 1753 getRateLimit(options, function(rateLimit) 1754 { 1755 if (rateLimit && rateLimit.remaining <= 0) 1756 { 1757 options._stats.rateLimitPreventionCount++; 1758 hideLoadingIndicator(options, null); 1759 return; 1760 } 1761 1762 getData(options, flAutorefresh, url, callback); 1763 }); 1764 }; 1765 getData = function(options, flAutorefresh, url, callback) 1766 { 1767 options._stats.dataRequestCount++; 1768 1769 if (!options.onDataRequestHandler(options._stats, options)) 1770 { 1771 hideLoadingIndicator(options, null); 1772 return; 1773 } 1774 1775 if (!flAutorefresh) 1776 { 1777 showLoadingIndicator(options); 1778 } 1779 1780 $.getJSON(url, function(data) 1781 { 1782 if (data.error) 1783 { 1784 // in case of an error, display the error message 1785 showError(options, data.error); 1786 } 1787 else 1788 {
1789 callback(data, options); 1790 } 1791 }); 1792 }; 1793 getRateLimit = function(options, callback) 1794 { 1795 var url = '/apis/twitter/rateLimit'; 1796 1797 if (options.searchParams) 1798 { 1799 url += '/search/tweets'; 1800 } 1801 else if (options.list) 1802 { 1803 url += '/lists/statuses'; 1804 } 1805 else 1806 { 1807 url += '/statuses/user_timeline'; 1808 } 1809 1810 url += '?callback=?'; 1811 1812 $.getJSON(url, function(rateLimit) 1813 { 1814 options._stats.rateLimit = rateLimit; 1815 1816 options.onRateLimitDataHandler(options._stats, options); 1817 1818 callback(rateLimit); 1819 }); 1820 }; 1821})(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.