Quest For A Simple Twitter Feed

I’ve always wanted to be able to just slap a quick and simple twitter feed on to a site and could never find the code to really do it (I mean really simple, just show some tweets and be done with it).

Here is the basic solution that I came up with (end result image on the right).

Basically this consists of 3 parts, jQuery, a jQuery extension that I wrote (jquery-twitterfeed) and some CSS. Other than that, you don’t need much to make this work; and that was the point.

With the jQuery extension installed, you can put a Twitter feed anywhere on your page using some simple code. Basically a container for the tweets and a call to the extension.

<div id="twitter-feed">
	<span class="twitter-loading">Loading...</span>
	<img src="/images/ajax-loader-arrows.gif" alt="" />
</div>
$('#twitter-feed').twitterfeed({
	'user': 'santsys', /* Your User Name Here */
	'num': 10
});

The look and feel is almost exactly the same as the Twitter embedded tweet samples, just simplified down. This layout supports a minimum width of 300px, and that could be lowered with some minor tweaks to the layout. If you want more information on the Twitter APIs, check out the documentation here, https://dev.twitter.com/docs.

Below is the full set of CSS and the jQuery extension used to generate the tweets in the image on the right. Now available on GitHub, https://github.com/santsys/jquery-twitterfeed.

The css used to style the tweets. This uses a background image set from twitter directly.

.clear { clear: both; }
.tweet, .tweet a, .tweet span, .tweet div { font: normal normal normal 12px/16px "Helvetica Neue",Arial,sans-serif; color: #777; }
.tweet { padding: .25em; border: 1px solid #EEE; border-radius: 5px; max-width:500px; margin-bottom: .25em; min-width: 300px; }
.tweet .t-head a, .tweet .t-head a span { text-decoration: none; color: #333; line-height: 18px; }
.tweet .t-head .t-avatar { position: absolute; }
.tweet .t-head .t-avatar img { border-radius: 4px; }
.tweet .t-head .t-name { margin-left: 53px; margin-top: 8px; float: left; font-size: 16px; font-weight: bold; }
.tweet .t-head a:hover span.t-name { text-decoration: underline; }
.tweet .t-head .t-nickname { margin-left: 53px; float: left; clear: both; color: #999; font-size: 11px; }
.tweet .t-body { clear: both; margin-top: 55px; }
.tweet .t-content { color: #333; line-height: 18px; font-size: 14px; }
.tweet .t-content a { line-height: 18px; font-size: 14px; color: #2FC2EF; text-decoration: none; }
.tweet .t-content a:hover { text-decoration: underline; }
.tweet .t-foot { margin-top: 6px; }
.tweet .t-foot a.t-details { color: #777; text-decoration: none; }
.tweet .t-foot a:hover.t-details { color: #999; text-decoration: underline; }
.tweet .t-foot .t-actions { clear: both; float: right; margin: 0; padding: 0; }
.tweet .t-foot .t-actions li { float: left; list-style-type: none; list-style-position: outside; margin-left: .2em;}
.tweet .t-foot .t-actions li * { float: left; font-weight: normal; }
.tweet .t-foot .t-actions i { background: transparent url(https://platform.twitter.com/embed/sprite.png) no-repeat 0px 0px; } 
.tweet .t-foot .t-actions a, .tweet .t-foot .t-actions a b { color: #999; text-decoration: none; }
.tweet .t-foot .t-actions a:hover, .tweet .t-foot .t-actions a:hover b { color: #777; text-decoration: underline; }
.tweet .t-foot .t-actions a.t-reply i { background-position: 0 -30px; height: 13px; width: 18px; margin: 1px 5px 0 8px; } 
.tweet .t-foot .t-actions a.t-retweet i { background-position: 0 -48px; height: 13px; width: 22px; margin: 1px 5px 0 8px; } 
.tweet .t-foot .t-actions a.t-favorite i { background-position: 0 -66px; height: 15px; width: 16px; margin: 0 5px 0 8px; }
.tweet .t-foot .t-actions a:hover.t-reply i { background-position: -23px -30px; } 
.tweet .t-foot .t-actions a:hover.t-retweet i { background-position: -27px -48px; } 
.tweet .t-foot .t-actions a:hover.t-favorite i { background-position: -21px -66px; }

The jquery-twitterfeed.js jQuery extension that actually does the rendering, etc.

/*
	Twitter jQuery Feed
	Version 1.0
	By: Josh Santomieri (http://www.santsys.com/)

	Options:
		user - The twitter user
		num - The number of tweets to display

	Documentaion on the twitter feeds here:
	https://dev.twitter.com/docs/api/1/get/statuses/user_timeline
*/

(function ($) {
	jQuery.fn.twitterfeed = function (options) {
		var settings = $.extend({
			'user': '',
			'num': 10
		}, options);

		if (this.length <= 0) return;

		var _this = this;
		var _url = 'http://api.twitter.com/1/statuses/user_timeline.json';
		_url += '?screen_name=' + escape(settings.user);
		_url += '&count=' + settings.num;
		_url += '&exclude_replies=true';
		_url += '&callback=?';
		
		$.ajax({
			url: _url,
			type: 'GET',
			dataType: 'jsonp',
			crossDomain: true,
			cache: false,
			contentType: 'application/javascript',
			jsonpCallback: 'parseTwitterFeed',
			success: function (json) {
				if (json == null) {
					_this.text('No response from feed!');
				}
				else {

					if (json instanceof Array) {
						var html = '';
						var months = [
							"JAN", "FEB", "MAR",
							"APR", "MAY", "JUN",
							"JUL", "AUG", "SEP",
							"OCT", "NOV", "DEC"
						];

						for (var i = 0; i < json.length; i++) {
							var tweet = json[i];
							
							var date = new Date(tweet.created_at);
							var replyLink = 'https://twitter.com/intent/tweet';
							replyLink += '?in_reply_to=' + tweet.id;
							replyLink += '&tw_i=' + tweet.id;
							replyLink += '&tw_e=reply';
							replyLink += '&tw_p=tweetembed';
							replyLink += '&source=tweetembed';

							html += '<div class="tweet">';
							html += '<div class="t-head">';
							html += '<a href="https://twitter.com/' + tweet.user.screen_name + '">';
							html += '<span class="t-avatar"><img src="' + tweet.user.profile_image_url_https + '" alt="">';
							html += '<span class="t-name">' + tweet.user.name + '';
							html += '<span class="t-nickname">@<b>' + tweet.user.screen_name + '</b>';
							html += '';
							html += '';
							html += '<div class="t-body">';
							html += '<div class="t-content">' + parseTweet(tweet.text) + '</div>';
							html += '</div>';
							html += '<div class="t-foot">';
							html += '<a class="t-details" href="https://twitter.com/twitterapi/statuses/' + tweet.id + '">';
							html += '<span class="t-updated " title="' + date.toLocaleDateString() + '">';
							html += date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear().toString().substr(2, 2);
							html += '</span>' + result[0] + '');
			}

			rx = new RegExp("#[a-zA-Z0-9]+", "gi");
			while (result = rx.exec(text)) {
				out = out.replace(result[0], '<a href="https://twitter.com/search/?src=hash&q=' + escape(result[0]) + '">' + result[0] + '</a>');
			}

			return out;
		}
	};
})(jQuery);

Update – 9/5/2012
Twitter just released an updated set of code for embedded timelines, check it out here, https://dev.twitter.com/docs/embedded-timelines.

Public Link Feed: bitly + feedburner

I’ve been wanting a quick and simple way to get my public links from bitly to my site… something automatic that I wouldn’t have to mess with. 

I’ve done some searching, but couldn’t find anything that worked how I wanted it.

Bitly’s APIs offer a lot of basic options, but they are mostly around authenticated processes for users to login and see their content but there are not many ways for me to stream my content.

Bitly offers some basic methods such as http://bitly.com/u/santsys.json and http://bitly.com/u/santsys.rss to see my feed in JSON and RSS formats. The only problem is this causes a lot of JavaScript cross-site scripting issues if you want to integrate the information on your site. Especially because they don’t seem to support any JSONP functionality.

So the quick and dirty workaround I found was to load up the RSS feed into feedburner (http://feedburner.google.com) and use their JSONP API to load the data on my site.

There are some interesting lag times due to caching within feedburner. At the time of writing this, FeedBurner updates every 30 minutes. I have noticed it often takes much longer for the actual page feed to update. Possibly there are some other systems out there that might update more frequently?

Here is the basic code, and some simple usage samples (also in use on this blog, on the right side of the page titled “Links”).

/*
	Feedburner jQuery Feed
	Version 1.0
	By: Josh Santomieri (http://www.santsys.com/)

	Options:
		feedUrl 
			- The URL to your feed on feedburner, for example
			  http://feeds.feedburner.com/RecentBookmarksFromSantsysOnBitly.
		numLinks 
			- The number of links you want to be displayed.
		feedBurnerUrl 
			- The url for the feedburner API, currently
			  https://ajax.googleapis.com/ajax/services/feed/load.
		removeBitlyPlus 
			- If your feed pulls from Bitly, there will be '+' at the end of
			  the shortened URLs; set this to true to remove them.
		userIP 
			- If you want to set the user ip to send to feedburner, go for it.
			"Google is less likely to mistake requests for abuse when they include userip"

	Most of the documentation on the feedburner/Google APIs can be found here, 
	https://developers.google.com/feed/v1/jsondevguide
*/

(function ($) {
	jQuery.fn.feedBurn = function (options) {
		try {

			var settings = $.extend({
				'feedUrl' : 'http://feeds.feedburner.com/RecentBookmarksFromSantsysOnBitly',
				'numLinks': 10,
				'feedBurnerUrl': 'https://ajax.googleapis.com/ajax/services/feed/load',
				'removeBitlyPlus': true,
				'userIP' : ''
			}, options);

			var _feedUrl = settings.feedBurnerUrl;
			_feedUrl += '?q=' + settings.feedUrl;
			_feedUrl += '&amp;v=1.0';
			_feedUrl += '&amp;num=' + settings.numLinks;
			if (settings.userIP != null &amp;&amp; settings.userIP != '') {
				_feedUrl += '&amp;userip=' + settings.userIP;
			}
			_feedUrl += '&amp;callback=?';
			var container = this;

			$.ajax({
				url: _feedUrl,
				type: 'GET',
				dataType: 'jsonp',
				crossDomain: true,
				cache: false,
				jsonpCallback: 'parseFeed',
				success: function(json) {
					if (json == null) {
						container.text('Invalid response from server.');
					} else if (json.responseData == null) {
						container.text('Status Code: ' + json.responseStatus + '; Status: ' + json.responseDetails);
					}
					else {
						var links = json.responseData.feed.entries;
						var html = '';

						for (var i = 0; i &lt; links.length; i++) {
							var link = links[i];
							var linkHref = link.link;

							// Bitly adds a '+' to the end of the links so they open in a Bitly UI
							if (settings.removeBitlyPlus) {
								if (linkHref.charAt(linkHref.length - 1) == '+') {
									linkHref = linkHref.substring(0, linkHref.length - 1);
								}
							}

							html += '&lt;div class="feed-history"&gt;';
							html += '&lt;div class="feed-link"&gt;';
							html += '&lt;a href="' + linkHref + '" title="' + link.title + '"&gt;' + link.title + '&lt;/a>';
							html += '&lt;/div&gt;';
							html += '&lt;/div&gt;';
						}

						container.html(html);
					}
				}
			});
		}
		catch (e) { container.text(e); }
	};
})(jQuery);

And here is some sample usage code.

(function ($) {
	$(document).ready(function () {
		try {
			var options = {
				'numLinks': 6,
				'feedUrl': 'http://feeds.feedburner.com/RecentBookmarksFromSantsysOnBitly'
			};
			$("#bitly-feed div.side").feedBurn(options);
		}
		catch (e) { }
	});
})(jQuery);