Create  a Twitter-Like

Create a Twitter-Like “Load More” Widget

Both Twitter and the Apple App Store use a brilliant technique for loading more information; you click the link and fresh items magically appear on the screen. This tutorial teaches you to use AJAX, CSS, Javascript, JSON, PHP, and HTML to create that magic. This tutorial will also feature both jQuery and MooTools versions of the script.


Assumptions

There are a few assumptions and notes that we’re going into this system with:

  • The server needs to be running PHP5 so that we can use PHP5′s JSON functions.
  • We’ll be pulling database records from a WordPress “posts” MySQL table. What’s great about the code provided is that you may use it with any database system; all you’ll need to do is modify the MySQL query and the JSON properties used by jQuery or MooTools.
  • The client must support javascript.
  • We’re using MooTools 1.2.3 Core and More 1.2.3.1. If jQuery is the preferred framework, jQuery 1.3.2 and Ariel Flesler’s ScrollTo plugin.

This tutorial will feature an explanation of the MooTools javascript. While jQuery’s
syntax differs from MooTools, the beauty in modern javascript frameworks is that
they differ mainly in syntax, not in logic. The jQuery javascript will be provided below.

The Plot

Here’s the sequence of events that will take place in our slick widget:

  • The page loads normally with an initial amount of posts showing
  • The user clicks the “Load More” element at the bottom of the list
  • An AJAX/JSON request will fire, retrieving a specified amount of new posts
  • Our jQuery/MooTools javascript will receive the result and build a series of new HTML elements containing the JSON’s information
  • Each element will slide into the widget’s container element
  • Once all of the elements have been loaded into the page, the window will scroll down to the first new item
  • Rinse and repeat.

Load More Widget

Step One: The PHP/MySQL

The first step is deciding how many posts need to be loaded during the initial page load. Since our widget will remember how many posts were loaded during the last load (in case a user visits another page and comes back), we’ll need to use the session.

	/* settings */
	session_start();
	$number_of_posts = 5; //5 posts will load at a time
	$_SESSION['posts_start'] = $_SESSION['posts_start'] ? $_SESSION['posts_start'] : $number_of_posts; //where we should start

The above code snippet contains all the “settings” content that we need. Next we need to create a PHP function that connects to our database, grabs more records, and returns their contents in JSON format:

	/* grab stuff */
	function get_posts($start = 0, $number_of_posts = 5) {
		/* connect to and select the db */
		$connection = mysql_connect('localhost','username','password'); //hostname, username, password
		mysql_select_db('davidwalsh83_blog',$connection);
		/* create the posts array */
		$posts = array();
		/* get the posts */
		$query = "SELECT post_title, post_content, post_name, ID FROM wp_posts WHERE post_status = 'publish' ORDER BY post_date DESC LIMIT $start,$number_of_posts";
		$result = mysql_query($query);
		/* for every post... */
		while($row = mysql_fetch_assoc($result)) {
			/* set the post content equal to the first paragraph...a "preview" regular expression */
			preg_match("/<p>(.*)<\/p>/",$row['post_content'],$matches);
			$row['post_content'] = strip_tags($matches[1]);
			/* add this record to the posts array */
			$posts[] = $row;
		}
		/* return the posts in the JSON format */
		return json_encode($posts);
	}

The above PHP contains a very simple regular expression that grabs the first paragraph of my post’s content. Since the first paragraph of most blog posts serves as a introduction to the rest of the content, we can assume that paragraph will server as a nice preview of the post.

Once the above function is ready, we need to create our AJAX request listener. We’ll know that someone has sent an AJAX request if the $_GET['start'] variable is set in the request URL.
If a request is sensed, we grab 5 more posts via our get_posts() function and echo their JSON out. Once we’ve output the new posts in JSON format, we save the number of items that the user has requested and kill the script, as seen below.

/* loading of stuff */
if(isset($_GET['start'])) {
	/* spit out the posts within the desired range */
	echo get_posts($_GET['start'],$_GET['desiredPosts']);
	/* save the user's "spot", so to speak */
	$_SESSION['posts_start']+= $_GET['desiredPosts'];
	/* kill the page */
	die();
}

That concludes the server-side code for our widget. Simple, no?

Load More Widget

Step 2: The HTML

There’s not much raw HTML to this widget initially. We’ll create one main widget container. Inside the widget container will be a posts wrapper and our “Load More” element which will server as a virtual like to trigger the loading of more content.

<!-- Widget HTML Starts Here -->
<div id="posts-container">
	<!-- Posts go inside this DIV -->
	<div id="posts"></div>
	<!-- Load More "Link" -->
	<div id="load-more">Load More</div>
</div>
<!-- Widget HTML Ends Here -->

Though we don’t insert individual post elements yet, it’s important to know the HTML structure of post DIV elements that will be injected into the posts wrapper:

<div class="post">
	<a href="{postURL}" class="post-title">{post_title}</a>
	<p class="post-content">
		{post_content}
		<br />
		<a href="{postURL}" class="post-more">Read more...</a>
	</p>
</div>

Sample Item

Step 3: The CSS

Time to add some flare to our widget. Feel free to format the widget’s elements any way you’d like. I’ve chosen to add my caricature on the left and the post title, content, and link to the right. We’ll need to add CSS for the static HTML elements and the javascript-generated elements as show below.

#posts-container			{ width:400px; border:1px solid #ccc; -webkit-border-radius:10px; -moz-border-radius:10px; }
.post						{ padding:5px 10px 5px 100px; min-height:65px; border-bottom:1px solid #ccc; background:url(dwloadmore.png) 5px 5px no-repeat; cursor:pointer;  }
.post:hover					{ background-color:lightblue; }
a.post-title 				{ font-weight:bold; font-size:12px; text-decoration:none; }
a.post-title:hover			{ text-decoration:underline; color:#900; }
a.post-more					{ color:#900; }
p.post-content				{ font-size:10px; line-height:17px; padding-bottom:0; }
#load-more					{ background-color:#eee; color:#999; font-weight:bold; text-align:center; padding:10px 0; cursor:pointer; }
#load-more:hover			{ color:#666; }	

One extra CSS class we’ll create is called “activate”, which we’ll show whenever an AJAX request starts and hide when the request completes.

.activate					{ background:url(/dw-content/loadmorespinner.gif) 140px 9px no-repeat #eee; }

AJAX Spinner

Step 4: The MooTools Javascript

Our MooTools javascript will make the magic happen. We’ll use a closure pattern to contain the MooTools code as a best practice:

//safety closure
(function($) {
	//when the DOM is ready...
	window.addEvent('domready,function() {
		
		/* ALL JAVASCRIPT WILL BE IN HERE */
		
	});
})(document.id);

Once the DOM is ready, we provide the initial javascript settings. Note that one of those settings, initialPosts, contains the JSON for the first batch of posts that should show when the page loads. We also define variables for how many posts we load initially and the number of posts to grab during each AJAX request.

//settings on top
var domain = 'http://davidwalsh.name/'; //your domain or directory path goes here
var initialPosts = <?php echo get_posts(0,$_SESSION['posts_start']); ?>;
var start = <php echo $_SESSION['posts_start']; ?>;
var desiredPosts = <?php echo $number_of_posts; ?>;

Initial JSON

Once our settings are in place, we define a function to handle the JSON we receive at page load as well as via future AJAX requests. For every post in the JSON, we…

  • Create a post URL variable which we’ll use a bit later in the loop
  • Create a DIV “post” element which will contain the post title, content, and link (in the format shown above)
  • Inject the newly created “post” element into the posts wrapper
  • Create a Fx.Slide object for the new “post” element so we can hide the element instantly, then slide it into view
  • Scroll the window down to the first newly-injected post

Here’s the MooTools javascript code that gets it done.

//function that creates the posts
var postHandler = function(postsJSON) {
	postsJSON.each(function(post,i) {
		//post url
		var postURL = '' + domain + post.post_name;
		//create the HTML "post" element
		var postDiv = new Element('div',{
			'class': 'post',
			events: {
				//click event that makes the entire DIV clickable
				click: function() {
					window.location = postURL;
				}
			},
			id: 'post-' + post.ID,
			html: '<a href="' + postURL + '" class="post-title">' + post.post_title + '</a><p class="post-content">' + post.post_content + '<br /><a href="' + postURL + '" class="post-more">Read more...</a></p>'
		});
		//inject into the container
		postDiv.inject($('posts'));
		//create the Fx Slider
		var fx = new Fx.Slide(postDiv).hide().slideIn();
		//scroll to first NEW item
		if(i == 0) {
			var scroll = function() {
				new Fx.Scroll(window).toElement($('post-' + post.ID));
			};
			scroll.delay(300); //give time so scrolling can happen
		}
	});
};

Scrolls

Now that our postHandler function is defined, it’s time to handle the initial JSON string of elements.

//place the initial posts in the page
postHandler(initialPosts);

Next we create a few more variables to store the value of our AJAX request and hold the values of the PHP session’s start value, the number of posts to grab at a time, and the “Load More” element.

var start = ;
var desiredPosts = ;
var loadMore = $('load-more');

To cut down on memory usage, we’ll create our Request.JSON object outside of the click event we’ll soon add. The Request.JSON object looks long but it’s really quite simple. Breaking it down…

We create the request object with basic settings…

	var request = new Request.JSON({
		url: 'load-more.php', //ajax script -- same script
		method: 'get',
		link: 'cancel',
		noCache: true,
		//more settings coming...

Add an onRequest parameter that adds our “activate” CSS class to the “Load More” clickable element and change the “Load More” element’s text to “Loading…”….

onRequest: function() {
	//add the activate class and change the message
	loadMore.addClass('activate').set('text','Loading...');
},

Add an onSuccess parameter that resets the “Load More” element text, keeps track of the current start spot for grabbing future elements, and handle the JSON response the same way we did with the initial posts…

onSuccess: function(responseJSON) {
	//reset the message
	loadMore.set('text','Load More');
	//increment the current status
	start += desiredPosts;
	//add in the new posts
	postHandler(responseJSON);
},

Add an onFailure function to update the “LoadMore” text upon failure…

onFailure: function() {
	//reset the message
	loadMore.set('text','Oops! Try Again.');
},

Lastly, add an onComplete function that removes the spinner once the request is complete, regardless of success or failure.

onComplete: function() {
	//remove the spinner
	loadMore.removeClass('activate');
}

The last step is adding the click event to “Load More” element. Upon click we make the AJAX request and all of the work above gets triggered. Success!

//add the "Load More" click event
loadMore.addEvent('click',function(){
	//begin the ajax attempt
	request.send({
		data: {
			'start': start,
			'desiredPosts': desiredPosts
		},
	});
});

AJAX JSON

MooTools Complete Code

	//safety closure
	(function($) {
		//domready event
		window.addEvent('domready',function() {
			//settings on top
			var domain = 'http://davidwalsh.name/';
			var initialPosts = <?php echo get_posts(0,$_SESSION['posts_start']);  ?>;

			//function that creates the posts
			var postHandler = function(postsJSON) {
				postsJSON.each(function(post,i) {
					//post url
					var postURL = '' + domain + post.post_name;
					//create the HTML
					var postDiv = new Element('div',{
						'class': 'post',
						events: {
							click: function() {
								window.location = postURL;
							}
						},
						id: 'post-' + post.ID,
						html: '<a href="' + postURL + '" class="post-title">' + post.post_title + '</a><p class="post-content">' + post.post_content + '<br /><a href="' + postURL + '" class="post-more">Read more...</a></p>'
					});
					//inject into the container
					postDiv.inject($('posts'));
					//create the Fx Slider
					var fx = new Fx.Slide(postDiv).hide().slideIn();
					//scroll to first NEW item
					if(i == 0) {
						var scroll = function() {
							new Fx.Scroll(window).toElement($('post-' + post.ID));
						};
						scroll.delay(300); //give time so scrolling can happen
					}
				});
			};

			//place the initial posts in the page
			postHandler(initialPosts);

			//a few more variables
			var start = <?php echo $_SESSION['posts_start']; ?>;
			var desiredPosts = <?php echo $number_of_posts; ?>;
			var loadMore = $('load-more');
			var request = new Request.JSON({
				url: 'load-more.php', //ajax script -- same page
				method: 'get',
				link: 'cancel',
				noCache: true,
				onRequest: function() {
					//add the activate class and change the message
					loadMore.addClass('activate').set('text','Loading...');
				},
				onSuccess: function(responseJSON) {
					//reset the message
					loadMore.set('text','Load More');
					//increment the current status
					start += desiredPosts;
					//add in the new posts
					postHandler(responseJSON);
				},
				onFailure: function() {
					//reset the message
					loadMore.set('text','Oops! Try Again.');
				},
				onComplete: function() {
					//remove the spinner
					loadMore.removeClass('activate');
				}
			});
			//add the "Load More" click event
			loadMore.addEvent('click',function(){
				//begin the ajax attempt
				request.send({
					data: {
						'start': start,
						'desiredPosts': desiredPosts
					},
				});
			});
		});
	})(document.id);

jQuery Version

If you prefer the jQuery javascript framework, it’s your lucky day; here’s the jQuery version:

	//when the DOM is ready
	$(document).ready(function(){
		//settings on top
		var domain = 'http://davidwalsh.name/';
		var initialPosts = <?php echo get_posts(0,$_SESSION['posts_start']); ?>;
		//function that creates posts
		var postHandler = function(postsJSON) {
			$.each(postsJSON,function(i,post) {
				//post url
				var postURL = '' + domain + post.post_name;
				var id = 'post-' + post.ID;
				//create the HTML
				$('<div></div>')
				.addClass('post')
				.attr('id',id)
				//generate the HTML
				.html('<a href="' + postURL + '" class="post-title">' + post.post_title + '</a><p class="post-content">' + post.post_content + '<br /><a href="' + postURL + '" class="post-more">Read more...</a></p>')
				.click(function() {
					window.location = postURL;
				})
				//inject into the container
				.appendTo($('#posts'))
				.hide()
				.slideDown(250,function() {
					if(i == 0) {
						$.scrollTo($('div#' + id));
					}
				});
			});	
		};
		//place the initial posts in the page
		postHandler(initialPosts);
		//first, take care of the "load more"
		//when someone clicks on the "load more" DIV
		var start = <?php echo $_SESSION['posts_start']; ?>;
		var desiredPosts = <?php echo $number_of_posts; ?>;
		var loadMore = $('#load-more');
		//load event / ajax
		loadMore.click(function(){
			//add the activate class and change the message
			loadMore.addClass('activate').text('Loading...');
			//begin the ajax attempt
			$.ajax({
				url: 'load-more.php',
				data: {
					'start': start,
					'desiredPosts': desiredPosts
				},
				type: 'get',
				dataType: 'json',
				cache: false,
				success: function(responseJSON) {
					//reset the message
					loadMore.text('Load More');
					//increment the current status
					start += desiredPosts;
					//add in the new posts
					postHandler(responseJSON);
				},
				//failure class
				error: function() {
					//reset the message
					loadMore.text('Oops! Try Again.');
				},
				//complete event
				complete: function() {
					//remove the spinner
					loadMore.removeClass('activate');
				}
			});
		});
	});

The MooTools and jQuery versions are exactly the same logic with different syntax!

Mission Accomplished!

Implementing this widget on your website is a great way to add dynamism and creativity to your website. I look forward to seeing your widget! Have questions suggestions for improvements? Post them below!


Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • http://www.aediscreative.com Christopher

    leave it to nettuts to have the answer every time I go looking for something!

    • http://www.webstudio34.com luke

      I second that!

  • http://www.livepage.info SkyTrips

    Very useful script, going to implement on my http://www.livepage.info website to display latest world news.

    Thanks!

  • Michiel

    Hi David, many thanks for this excellent piece of code. It works great on my testpage, but now have two questions I hope someone can answer:

    - I have this script on my homepage for updates and on the first load it focusses on the first post. How can I remove the focus on initial load?
    - Where in this script can I add numbers to a batch? I would like to present a pagenumber on top of each batch of posts that load after clicking the magic button.

    Thanks again,
    Michiel

    • Simon

      Great tutorial…I’d really like the answer for this one as well. How do you remove the focus on the first post on initial load? Thanks

    • Simon

      Actually, in the jQuery version I found that you have to remove this live from the code to get the desired result:

      $.scrollTo($(‘div#’ + id));

  • hi

    is there any way i can integrate this widget with wordpress php and wordpress is a bit confusing for me help please

  • Serkan

    Hi

    What can we do for WordPress?

  • http://sabac.net sabac red

    I’m getting “mysql_fetch_assoc():” error. how can i solve this issue?

  • http://- sabac

    i can’t load the post content, any advice?
    http://i55.tinypic.com/wasqs3.png

  • http://www.classyscripts.com/ Resources

    Great read. As usual I find all your posts very infomative and I wnted to take this opportunity to let you know that.

  • khaja

    i had a problem on displaying posts , it displaying each post ,can any one help me

  • kyra

    i have the same problem either. david hear us!

  • Ilya

    Hi, Great tutorial I really like it!!!

    On the tutorial page you save a spot on the list, is there a way to not do this? I mean how can I not save that spot so I only get 10 new articles every time? Can anyone reply to idaderko@live.com please?
    thank you, it’s a great tut!!!!

  • Julio

    Hello Dave, great tutorial!! I need a similar function built for a custom WordPress theme called Classipress. It’s a classified ad theme and does not use the traditional loop for posts. I’m willing to coministate you or anyone on this forum if you can built a function or plugin to work with this theme. Here is the URL for the theme http://www.appthemes.com/demo/classipress/. I can provide the source code to the custom loop, index and functions.php to help it get started. Please email me if you are interested. thesanerone @ gmail .com

    Again, great tutorial!!

  • PixelTunnelVision

    This seemed like an awesome solution until I got to the “WordPress” part in the assumptions. Kinda sucks that a database has to be involved, let alone WordPress.

    • PixelTunnelVision

      Also, a final code shot with everything together in one index.php file, along with extraneous php files and a “set up the DB” section would make this tutorial a heck of a lot more coherent. As it is, it’s just a bunch of code snippets shown one after another. Kinda unhelpful.

  • Alex

    Hi,
    Thank you Dave for this awesome posting, and codes.

    I have a simple idea that I am sure a lot of smart folks on this post can help with -:)

    How do you make sure the first time the page is loaded, it does not scroll to the first new item.

    Hence, I am using the code at the end of a webpage, and I want to make sure the first time I get a visitor the page, they don’t miss on the what is on top of the page…

    thanks in advance..

  • http://flickr Peter

    That what I looking for! :)
    how can I do auto update new messages from database?
    everywhere I saw XML but can I do that with this script?

  • http://web-newz.com waqas

    very impressive and great tutorial

  • Daniel

    another mysql_fetch_assoc() error.

    Ok, search another example.

  • http://www.zonaj.net Juan S

    Great Script. Sadly, it doesn’t work for us with friendly urls :( When we give the widget a non friendly url it works great. Does anyone how to make it work when you are using friendly urls?

  • Michael

    It doesn’t work in latest jQuery versions (from 1.4)
    Please, help me, how can I fix it?

  • calle

    Hey, thanks for a great demo!
    It works as should, and its just as smooth as i thought. I have a problem with the sessions though, could you please point me in the right direction as to how to destroy the sessions on page refresh? cause now itll output the same amount of posts as ive loaded when i refresh. would want it to go back to default 5 posts on page refresh.

    Again, thanks for this =)

  • http://www.eprofle.co Mekey Salaria

    Not Working :/

  • Guest

    Your demo does not work!
    There is a better tutorial here: http://www.problogdesign.com/wordpress/load-next-wordpress-posts-with-ajax/

  • http://julienrenaux.fr Julien

    I have created an updated version of this fantastic post using PHP, Mootools, Bootstrap and Mustache.js bit.ly/QTAIAp

  • jordanmartins

    Muito bom.. era o que eu estava procurando.. Obrigado!