Making Our Own Twitter Timeline
Making Our Own Twitter Timeline
Introduction
Twitter has grown into a real social phenomenon. This is an incredible achievement for such a simple service. But as you know, great ideas are not necessarily complex.
This time we are going to create our own twitter-like timeline, where you can view and post your tweets. You can use the code I’ve provided here for all kinds of purposes and be sure that the possibilities are endless. So grab the demo files and start learning!
View The DEMO
Creating the DB
If you’d like to run a working demo on your own site, you will have to create a MySQL table where all your tweets are going to be stored. You can run the following SQL code through phpMyAdmin (the code is also available in table.sql in the tutorial files):
table.sql
1.CREATE TABLE `demo_twitter_timeline` ( 2. `id` int(10) NOT NULL auto_increment, 3. `tweet` varchar(140) collate utf8_unicode_ci NOT NULL default ´´, 4. `dt` datetime NOT NULL default ´0000-00-00 00:00:00´, 5. PRIMARY KEY (`id`) 6.) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;The table can be in any mysql database. Just remember to update the data in connect.php.
The XHTML
Thanks to CSS and jQuery, our XHTML code is quite simple. This is what you can see in index.php in the demo files.
index.php
01.<div id="twitter-container"> 02.<form id="tweetForm" action="submit.php" method="post"> 03. 04.<span class="counter">140</span> 05.<label for="inputField">What are you doing?</label> 06.<textarea name="inputField" id="inputField" tabindex="1"rows="2" cols="40"></textarea> 07.<input class="submitButton inact" name="submit" type="submit" value="update" /> 08. 09.<span class="latest"><strong>Latest: </strong><span id="lastTweet"><?=$lastTweet?></span></span> 10. 11.<div class="clear"></div> 12.</form> 13. 14.<h3 class="timeline">Timeline</h3> 15.<ul class="statuses"><?=$timeline?></ul> 16.</div>Our entire timeline is positioned inside a div container with an id of twitter-container. It has some interesting styles attached to it in the CSS file below.
Next we have the form with an id of tweetForm. This form is submitted via AJAX, so it doesn’t really matter what the action and submit attributes are set to.
Inside the form we have a special span element. It acts as the counter, that shows the current number of characters that are filled in the box. As in twitter, the limit here is set to 140 characters.
Later we have the label for the textarea, the textarea itself and the submit button, which is disabled by default (this is done with jQuery and a special CSS class – inact, as you will see later).
After that we have our latest tweet and a clearing div. With that div we address an interesting shortcoming of CSS, as you will see in a minute.
Finally, there is the timeline itself, containing our latest tweets.
Lines 9 and 16 are highlighted to show you that we are displaying PHP variables. We will explain them and generate the list in a moment.

The CSS
We already mentioned that with the use of CSS we are able to drastically reduce the amount of XHTML code we write. An additional bonus is that it is really easy to change the looks of our projects at any time, just by changing the style sheet.
Now lets see what lays in our demo.css file.
demo.css
001./* Page styles */002. 003.body,h1,h2,h3,p,td,quote,small,form,input,ul,li,ol,label{ 004. margin:0px; 005. padding:0px; 006.} 007. 008.body{ 009. margin-top:20px; 010. color:#51555C; 011.} 012. 013./* Form & timeline styles */014. 015.#twitter-container{ 016. -moz-border-radius:12px; 017. -khtml-border-radius: 12px; 018. -webkit-border-radius: 12px; 019. border-radius:12px; 020. 021. border:6px solid #f5f5f5; 022. 023. padding:10px; 024. width:600px; 025. 026. font-size:11px; 027. font-family:´Lucida Grande´,sans-serif; 028. color:#333333; 029.} 030. 031.label{ 032. font-size:20px; 033. display:block; 034.} 035. 036..counter{ 037. color:#CCCCCC; 038. float:right; 039. font-family:Georgia,serif; 040. font-size:32px; 041. font-weight:bold; 042. height:40px; 043. overflow:hidden; 044.} 045. 046.textarea{ 047. width:594px; 048. height:38px; 049. margin:5px 0 10px 0; 050. 051. border:1px solid #AAAAAA; 052. padding: 4px 2px; 053. 054. font-family:´Lucida Grande´,sans-serif; 055. overflow:auto; 056. font-size:14px; 057.} 058. 059..clear{ 060. clear:both; 061.} 062. 063..submitButton{ 064. color:#666666; 065. font-size:14px; 066. height:32px; 067. width:115px; 068. 069. -moz-border-radius:6px; 070. -khtml-border-radius: 6px; 071. -webkit-border-radius: 6px; 072. border-radius:6px; 073. 074. border:1px solid #cccccc; 075. background:url(img/button_bg.gif) repeat-x #f5f5f5; 076. 077. cursor:pointer; 078. float:right; 079.} 080. 081..submitButton:hover{ 082. background-position:bottom; 083. border-color:#dddddd; 084. color:#333333; 085.} 086. 087..inact,.inact:hover{ 088. background:#f5f5f5; 089. border:1px solid #eeeeee; 090. color:#aaaaaa; 091. cursor:auto; 092.} 093. 094..latest{ 095. color: #666666; 096.} 097. 098.ul.statuses{ 099. margin:10px 0; 100.} 101. 102.ul.statuses li { 103. position:relative; 104. border-bottom:1px dashed #D2DADA; 105. padding:15px 15px 15px 10px; 106. list-style:none; 107. font-size:14px; 108.} 109. 110.ul.statuses li:first-child{ 111. border-top:1px dashed #D2DADA; 112.} 113. 114.ul.statuses li:hover { 115. background-color:#F7F7F7; 116.} 117. 118.h3.timeline{ 119. margin-top:20px; 120. color:#999999; 121. font-size:20px; 122. font-weight:normal; 123.} 124. 125.div.tweetTxt{ 126. float:left; 127. width:498px; 128. overflow:hidden; 129.} 130. 131.ul.statuses a img.avatar{ 132. float:left; 133. margin-right:10px; 134. border:1px solid #446600; 135.} 136.div.date{ 137. line-height:18px; 138. font-size:12px; 139. color:#999999; 140.} 141. 142.li a, li a:visited { 143. color:#007bc4; 144. text-decoration:none; 145. outline:none; 146.} 147. 148.li a:hover{ 149. text-decoration:underline; 150.}We start off by defining the page styles. First we reset our page (nullifying the margin and padding of some of the page elements, which differ by default on the different browsers). After that on line 8 we set a top margin for the body and a font color for all the text on the page.
Lines 16 to 19 is where we round the div, containing our form and timeline. Not until recently, you had to manually create rounded corner graphics and insert additional div elements for each corner. But recent versions of Firefox and Safari can make it with pure CSS.
Unfortunately this method is not supported by other browsers. Another small disadvantage of the technique is that you have to target each browser with browser specific CSS properties – such as -moz-border-radius, because rounded corners are not a part of the current CSS specification. But in case it is included in a future specification, we include the property that should be directly supported – border-radius, which results in the aforementioned 4 lines of code.
The CSS is pretty straightforward up to line 59. This is an important CSS hack (called clearfix) I mentioned earlier. When a div contains floated elements, its height is not enlarged to the height of its children elements. For this purpose another div is inserted which has the CSS poperty clear:both. This forces it to go on a new line, below the floated elements and thus expanding its parent element’s height.
Line 63 is where we style our submit button. Here we use the rounded border property again, which also works on buttons as you can see yourself. Another important thing to note is that we define a background graphic for the button. It is exactly twice the height of the button. In its normal state, the top part of the image works as the background, and on hover – the bottom part. This what we do on line 82.
On line 87 is the inact class. This class is assigned to the button only when it is disabled (on the initial page load, or when the text area is empty) to prevent the user from submitting it. It also has a defined normal and :hover state, but they are absolutely the same. This is done to stop the other :hover action, defined on line 81 that affects the button. In other words, we set up a new :hover class to overwrite the previous one.
Lines 102 to 116 define the styles of the timeline elements. The timeline is nothing more than an unordered list. The interesting thing to note here is how we address only the first li element with the :first-child selector and give it a top border.
The jQuery code
Once again I’ve chosen jQuery because of its agile and simple methods that get more work done with fewer lines of code.
script.js
01.$(document).ready(function(){ 02. 03. $(´#inputField´).bind("blur focus keydown keypress keyup", function(){recount();}); 04. $(´input.submitButton´).attr(´disabled´,´disabled´); 05. 06. $(´#tweetForm´).submit(function(e){ 07. 08. tweet(); 09. e.preventDefault(); 10. 11. }); 12. 13.}); 14. 15.function recount() 16.{ 17. var maxlen=140; 18. var current = maxlen-$(´#inputField´).val().length; 19. $(´.counter´).html(current); 20. 21. if(current<0 || current==maxlen) 22. { 23. $(´.counter´).css(´color´,´#D40D12´); 24. $(´input.submitButton´).attr(´disabled´,´disabled´).addClass(´inact´); 25. } 26. else27. $(´input.submitButton´).removeAttr(´disabled´).removeClass(´inact´); 28. 29. if(current<10) 30. $(´.counter´).css(´color´,´#D40D12´); 31. 32. else if(current<20) 33. $(´.counter´).css(´color´,´#5C0002´); 34. 35. else36. $(´.counter´).css(´color´,´#cccccc´); 37. 38.} 39. 40.function tweet() 41.{ 42. var submitData = $(´#tweetForm´).serialize(); 43. 44. $(´.counter´).html(´<img style="padding:12px" src="img/ajax_load.gif" alt="loading" width="16" height="16" />´); 45. 46. $.ajax({ 47. type: "POST", 48. url: "submit.php", 49. data: submitData, 50. dataType: "html", 51. success: function(msg){ 52. 53. if(parseInt(msg)!=0) 54. { 55. $(´ul.statuses li:first-child´).before(msg); 56. $("ul.statuses:empty").append(msg); 57. 58. $(´#lastTweet´).html($(´#inputField´).val()); 59. 60. $(´#inputField´).val(´´); 61. recount(); 62. } 63. } 64. 65. }); 66. 67.}We can divide this code into three important paths. The one that gets executed after the page is loaded (line 1). The recount() function, that fills our counter span with the number of characters left, and the tweet() function that handles the AJAX communication and the subsequent page update to include the new tweet in the timeline.
In the first part, on line 3 you see that we bind the recount() function to a number of events that may happen in the text area. This is because any one of those events by its own cannot guarantee fast enough updates on the counter.
On the next line we disable the submit button – we do not need the user to be able to submit an empty form.
Later we bind the onsubmit event of the form to the tweet() function, preventing the actual form submit on line 9.
In the recount function there are a number of things that are worth mentioning. In lines 17-19 we calculate the remaining characters and on lines 21-36, depending on how close we are to the maximum number, set the color of the counter.
We also decide whether we should disable the button (if there is no text in the text area, or we are over the limit) and enabling it otherwise. The disabling / enabling of the button happens by setting the attribute disabled and assigning our custom CSS class – inact, which removes the hand cursor and changes its color to light grey.
The tweet function is where the magic happens. We serialize the form in the submitData variable and replace the counter with a rotating gif animation.
After this, the data gets sent to submit.php and depending on the return value, inserts the received tweet in the time line on line 55 and 56.
What do actually those two lines of code do? Line 55 uses the same :first-child selector as in our style sheet above. This means that it will insert the formatted tweet it receives before the first element. Then why do we need the second line? Well, if we haven’t posted any tweets, the :first-child won’t find any elements. That is why we use the :empty selector. Only one of those two lines can insert the element at the same time, thus eliminating the need of checking manually whether there are elements in the timeline.
After inserting our newly created tweet, we empty the text area and recount the remaining characters.
The PHP
Our PHP code manages the insertion of data in the MySQL database and the formatting of our tweets and timeline.
submit.php
01.define(´INCLUDE_CHECK´,1); 02.require "functions.php"; 03.require "connect.php"; 04. 05.if(ini_get(´magic_quotes_gpc´)) 06.$_POST[´inputField´]=stripslashes($_POST[´inputField´]); 07. 08.$_POST[´inputField´] = mysql_real_escape_string(strip_tags($_POST[´inputField´]),$link); 09. 10.if(mb_strlen($_POST[´inputField´]) < 1 || mb_strlen($_POST[´inputField´])>140) 11.die("0"); 12. 13.mysql_query("INSERT INTO demo_twitter_timeline SET tweet=´".$_POST[´inputField´]."´,dt=NOW()"); 14. 15.if(mysql_affected_rows($link)!=1) 16.die("0"); 17. 18.echo formatTweet($_POST[´inputField´],time());First we check whether magic_quotes_gpc is set. This is enabled on some hosts and what it does is escape the incoming data automatically, which is considered a bad practice. That is why if it is on, we remove the escaped code and can continue normally with our script.
We escape the data, do a check of the length of $_POST[´inputField´] and insert the row in our database. We echo a formatted tweet using formatTweet (more on that in a minute), that is returned to the tweet() jQuery function as the variable msg.
functions.php
01.if(!defined(´INCLUDE_CHECK´)) die(´You are not allowed to execute this file directly´); 02. 03.function relativeTime($dt,$precision=2) 04.{ 05. $times=array( 365*24*60*60 => "year", 06. 30*24*60*60 => "month", 07. 7*24*60*60 => "week", 08. 24*60*60 => "day", 09. 60*60 => "hour", 10. 60 => "minute", 11. 1 => "second"); 12. 13. $passed=time()-$dt; 14. 15. if($passed<5) 16. { 17. $output=´less than 5 seconds ago´; 18. } 19. else20. { 21. $output=array(); 22. $exit=0; 23. foreach($times as $period=>$name) 24. { 25. if($exit>=$precision || ($exit>0 && $period<60)) break; 26. $result = floor($passed/$period); 27. 28. if($result>0) 29. { 30. $output[]=$result.´ ´.$name.($result==1?´´:´s´); 31. $passed-=$result*$period; 32. $exit++; 33. } 34. 35. else if($exit>0) $exit++; 36. 37. } 38. $output=implode(´ and ´,$output).´ ago´; 39. } 40. 41. return $output; 42.} 43. 44.function formatTweet($tweet,$dt) 45.{ 46. if(is_string($dt)) $dt=strtotime($dt); 47. 48. $tweet=htmlspecialchars(stripslashes($tweet)); 49. 50. return´ 51. <li><a href="#"><img class="avatar" src="img/avatar.jpg" width="48" height="48" alt="avatar" /></a> 52. <div class="tweetTxt"> 53. <strong><a href="#">demo</a></strong> ´. preg_replace(´/((?:http|https|ftp):\/\/(?:[A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?[^\s\"\´]+)/i´,´<a href="$1" rel="nofollow" target="blank">$1</a>´,$tweet).´ 54. <div class="date">´.relativeTime($dt).´</div> 55. </div> 56. <div class="clear"></div> 57. </li>´; 58.}Here you can see 2 functions. The first one – relativeTime() is a function I made a while back, that displays the relative period that has passed since a given time (it supports both a unix time stamp as well as a mysql date string as a parameter) .
The other one is made especially for this tutorial. It formats and returns a tweet using only the tweet text and a time variable. If you plan on making changes to the example, this is the place to start.
The formatTweet function is nothing special – first we decide whether we should convert the given time parameter from a mysql data string into a timestamp. After this, we prevent possible vulnerabilities by using htmlspecialchars, and then return a formatted tweet. An interesting thing to note here is line 53. With the preg_replace function we convert the links that are included in the tweet into real hyperlinks, complete with a target and nofollow attribute.

Now lets see how our timeline is actually generated.
index.php
01.define(´INCLUDE_CHECK´,1); 02. 03.require "functions.php"; 04.require "connect.php"; 05. 06.// remove tweets older than 1 hour to prevent spam 07.mysql_query("DELETE FROM demo_twitter_timeline WHERE id>1 AND dt<SUBTIME(NOW(),´0 1:0:0´)"); 08. 09.//fetch the timeline 10.$q = mysql_query("SELECT * FROM demo_twitter_timeline ORDER BY ID DESC"); 11. 12.$timeline=´´; 13.while($row=mysql_fetch_assoc($q)) 14.{ 15. $timeline.=formatTweet($row[´tweet´],$row[´dt´]); 16.} 17. 18.// fetch the latest tweet 19.$lastTweet = ´´; 20.list($lastTweet) = mysql_fetch_array(mysql_query("SELECT tweet FROM demo_twitter_timeline ORDER BY id DESC LIMIT 1")); 21. 22.if(!$lastTweet) $lastTweet = "You don´t have any tweets yet!";This code is positioned before any XHTML code on the page. For the purposes of the demo, I’ve set the tweets to be automatically deleted after an hour. You can remove this line to keep the tweets indefinitely.
The main purpose of this code is to generate the $timeline and $lastTweet variables, that are included in our XHTML code in the beginning of the tutorial.
With this our own twitter timeline is complete.

22 January 2010 Friday




