Using MooTools ScrollSpy to Load More Items via JSON/AJAX
Last July I wrote an epic dominant unbelievable fantastic outstanding awesome NetTuts post called Create a Twitter-Like "Load More" Widget Using CSS, HTML, JSON, and jQuery or MooTools where I used a MooTools/AJAX/JSON system for loading additional items when the user clicks a "Load More" link. The functionality is sweet but I see room for improvement. If a user scrolls down toward the end of the container, why not load more items for them automatically? Armed with the premier MooTools scrolling plugin, ScrollSpy, we can do so.
The HTML
Load More
As you can see, the page starts with very little HTML. Obviously this isn't solution which will work without JavaScript enabled. Making this system work without JavaScript wouldn't be too difficult...but we'll skip that in this tutorial.
The CSS
#posts { height:300px; overflow:scroll; }
The CSS from my original NetTuts stays the same -- we simply added the #posts selector properties.
The MooTools JavaScript
Quite a bit gets added to the original tutorials so I'll point out each change individually:
/* via @appden, Scott Kyle, http://appden.com/javascript/fun-with-custom-events-on-elements-in-mootools/ */ Native.implement([Element, Window, Document, Events], { oneEvent: function(type, fn) { return this.addEvent(type, function() { this.removeEvent(type, arguments.callee); return fn.apply(this, arguments); }); } });
The code above represents a function that gets run only once per element. We'll use this in a moment.
//NEW! var spy; var spyContainer = $('posts'); var spyAct = function() { var min = spyContainer.getScrollSize().y - spyContainer.getSize().y - 150 /* tolerance */; spy = new ScrollSpy({ container: spyContainer, min: min, onEnter: function() { loadMore.fireEvent('click'); } }); }; //wait for first load... window.oneEvent('load',function() { spyAct(); });
We create the ScrollSpy listener to keep track of scrolling. If the user scrolls within 150 pixels of the bottom, we fire a virtual click on the "Load More" link.
//Request.JSON above.... onSuccess: function(responseJSON) { //reset the message loadMore.set('text','Load More'); //increment the current status start += desiredPosts; //add in the new posts postHandler(responseJSON); //spy calc! spyAct(); }, //more below....
Lastly, we call the worker function on the JSON request's success event. Here's the entire MooTools JavaScript snippet:
/* via @appden, Scott Kyle, http://appden.com/javascript/fun-with-custom-events-on-elements-in-mootools/ */ Native.implement([Element, Window, Document, Events], { oneEvent: function(type, fn) { return this.addEvent(type, function() { this.removeEvent(type, arguments.callee); return fn.apply(this, arguments); }); } }); //safety closure (function($) { //domready event window.addEvent('domready',function() { //settings on top var domain = 'https://davidwalsh.name/'; var initialPosts = ; var start = ; var desiredPosts = ; var loadMore = $('load-more'); //NEW! var spy; var spyContainer = $('posts'); var spyAct = function() { var min = spyContainer.getScrollSize().y - spyContainer.getSize().y - 150 /* tolerance */; spy = new ScrollSpy({ container: spyContainer, min: min, onEnter: function() { loadMore.fireEvent('click'); } }); }; //wait for first load... window.oneEvent('load',function() { spyAct(); }); //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: '' + post.post_title + '' + post.post_content + '
' }); //inject into the container postDiv.inject($('posts')); }); }; //place the initial posts in the page postHandler(initialPosts); //ajax! 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); //spy calc! spyAct(); }, 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);
Read more...
The PHP
The following PHP loads more items. My example uses WordPress posts:
/* settings */ session_start(); $number_of_posts = 5; //5 at a time $_SESSION['posts_start'] = $_SESSION['posts_start'] ? $_SESSION['posts_start'] : $number_of_posts; /* 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(); } /* grab stuff */ function get_posts($start = 0, $number_of_posts = 5) { /* connect to the db */ $connection = mysql_connect('localhost','username','password'); mysql_select_db('blogname',$connection); $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); while($row = mysql_fetch_assoc($result)) { preg_match("/(.*)<\/p>/",$row['post_content'],$matches); $row['post_content'] = strip_tags($matches[1]); $posts[] = $row; } /* return the posts in the JSON format */ return json_encode($posts); }
A Few Thoughts
- This type of system only works well if you remember how many items were last loaded. Making the user start from the default number is annoying as hell.
- I've found that listening for scrolling is browsers is shotty -- sometimes browsers simply don't fire the scroll event properly, which makes the "Load More" link all the more important.
If you want an in-depth description of the system, please read the original post at NetTuts.
Cool functionality though, huh?
That seems like something I’ll find useful… Thanks!
W00t! Does this work/can be implemented for select boxes?
When I first saw an implementation of this I liked it, though I have seen it confusing people when they lost track of where they were before the added content jumped into view and they were still scrolling/dragging with their mouse etc. (bit hard to explain)… anyway, I still dig it. Nice implementation too.
However, I wonder what is the benefit of using the oneEvent in this case. I mean, how is it executed twice. And, also regarding oneEvent, do you think it will replace the common
window.addEvent('domready', function() {...
intowindow.oneEvent('domready', function() {...
?Like the idea, but seems like it has a small flaw:
If I scroll down so the script loads more content 3-4 times, and then scroll up to the top and begin scrolling down again, the script will load more content several times before I reach the bottom.
Seems like it loads more content each time I have scrolled X pixels rather than each time I reach the end of the scrollable area.
@Torkil Johnsen: I think the relevant var needs reseting – it’s firing 150px from the previous last post (when you scroll back and forward again.)
@Torkil Johnson and others with this problem
The fix for your problem is:
Change this bit of code:
onEnter: function() {
loadMore.fireEvent(‘click’);
}
to this:
onEnter: function(pos,ent) {
if (ent == “1”) loadMore.fireEvent(‘click’);
}
Good luck!
Nice tutorial, any chance of a Jquery version? :D
i david. great post once again.
i hope you can help me.
is it possible to implement this “Load More” script on a blogger blog? i guess the problem is the php implementation..
any idea to do the job? maybe a go around trick…i dont know..
thanks very much in advance
antónio torres
http://www.vaiumagasosa.com