Several our readers asked us to implement an infinite scroll for our Pinterest script, thus I decided to implement it today. I made some research, and came to http://www.infinite-scroll.com/. I think that the library is ideal for the realization of our objectives. It let us make some kind of endless pages. It means that initially we can render a certain amount of images, when we want to see more images, we can easily scroll down, and new set of images will be loaded ajaxy. If you are ready – let’s start.
You are welcome to try our updated demo and download the fresh source package:
[sociallocker]
[/sociallocker]
Step 1. HTML
The first thing you have to do is – to download the plugin jquery.infinitescroll.min.js and put it into your ‘js’ directory. Now, we can link this new library in the header of our ‘templates/index.html’, now, full list of attached libraries looks like:
templates/index.html
2 | < script src = "js/jquery.min.js" ></ script > |
3 | < script src = "js/jquery.colorbox-min.js" ></ script > |
4 | < script src = "js/jquery.masonry.min.js" ></ script > |
5 | < script src = "js/jquery.infinitescroll.min.js" ></ script > |
6 | < script src = "js/script.js" ></ script > |
Another small change in this file – a new template key (in the end of main container) – {infinite}
2 | < div class = "main_container" > |
Exactly the same changes we have to repeat in ‘templates/profile.html’ file (in our plans is to add infinite scroll for both: index and profile pages)
Step 2. PHP
Well, in the previous step we prepared our template files, now – we have to exchange our {infinite} key for a certain value. The first file is index page. Please replace our previous set of template keys
index.php
3 | '{menu_elements}' => $sLoginMenu , |
4 | '{extra_data}' => $sExtra , |
5 | '{images_set}' => $sPhotos |
with next code:
03 | if ( $_SERVER [ 'HTTP_X_REQUESTED_WITH' ] == 'XMLHttpRequest' ) { |
05 | $sPage = (int) $_GET [ 'page' ] + 1; |
07 | <div class = "main_container" > |
11 | <a href= "index.php?page={$sPage}&per_page={$sPerpage}" ></a> |
17 | $sInfinite = ( $sPhotos == '' ) ? '' : <<<EOF |
19 | <a href= "index.php?page=2&per_page={$sPerpage}" ></a> |
24 | '{menu_elements}' => $sLoginMenu , |
25 | '{extra_data}' => $sExtra , |
26 | '{images_set}' => $sPhotos , |
27 | '{infinite}' => $sInfinite |
The main idea – to get fresh data each time we request this page (of course, it depends on next GET params: ‘page’ and ‘per_page’). The similar changes I prepared for our profile page, look at the fresh version:
profile.php
01 | require_once ( 'classes/CMySQL.php' ); |
02 | require_once ( 'classes/CMembers.php' ); |
03 | require_once ( 'classes/CPhotos.php' ); |
05 | list ( $sLoginMenu , $sExtra ) = $GLOBALS [ 'CMembers' ]->getLoginData(); |
07 | $i = (int) $_GET [ 'id' ]; |
09 | $aMemberInfo = $GLOBALS [ 'CMembers' ]->getProfileInfo( $i ); |
12 | $sPhotos = $GLOBALS [ 'CPhotos' ]->getAllPhotos( $i ); |
15 | if ( $_SERVER [ 'HTTP_X_REQUESTED_WITH' ] == 'XMLHttpRequest' ) { |
17 | $sPage = (int) $_GET [ 'page' ] + 1; |
19 | <div class = "main_container" > |
23 | <a href= "profile.php?id={$i}&page={$sPage}&per_page={$sPerpage}" ></a> |
29 | $sInfinite = ( $sPhotos == '' ) ? '' : <<<EOF |
31 | <a href= "profile.php?id={$i}&page=2&per_page={$sPerpage}" ></a> |
36 | '{menu_elements}' => $sLoginMenu , |
37 | '{extra_data}' => $sExtra , |
38 | '{images_set}' => $sPhotos , |
39 | '{profile_name}' => $aMemberInfo [ 'first_name' ], |
40 | '{infinite}' => $sInfinite |
42 | echo strtr ( file_get_contents ( 'templates/profile.html' ), $aKeys ); |
46 | header( 'Location: error.php' ); |
Pay attention, that by default we display 20 images per page. My final touches were in the main Photos class (CPhotos.php). As you remember, we had to operate with two new GET params for pagination: ‘page’ and ‘per_page’. I added the processing of both parameters in the function ‘getAllPhotos’:
classes/CPhotos.php
01 | function getAllPhotos( $iPid = 0, $sKeyPar = '' ) { |
05 | $aWhere [] = "`owner` = '{$iPid}'" ; |
08 | $sKeyword = $GLOBALS [ 'MySQL' ]->escape( $sKeyPar ); |
09 | $aWhere [] = "`title` LIKE '%{$sKeyword}%'" ; |
11 | $sFilter = ( count ( $aWhere )) ? 'WHERE ' . implode( ' AND ' , $aWhere ) : '' ; |
13 | $iPage = (isset( $_GET [ 'page' ])) ? (int) $_GET [ 'page' ] : 1; |
14 | $iPerPage = (isset( $_GET [ 'per_page' ])) ? (int) $_GET [ 'per_page' ] : 20; |
15 | $iPage = ( $iPage < 1) ? 1 : $iPage ; |
16 | $iFrom = ( $iPage - 1) * $iPerPage ; |
17 | $iFrom = ( $iFrom < 1) ? 0 : $iFrom ; |
18 | $sLimit = "LIMIT {$iFrom}, {$iPerPage}" ; |
26 | $aPhotos = $GLOBALS [ 'MySQL' ]->getAll( $sSQL ); |
29 | foreach ( $aPhotos as $i => $aPhoto ) { |
30 | $iPhotoId = (int) $aPhoto [ 'id' ]; |
31 | $sFile = $aPhoto [ 'filename' ]; |
32 | $sTitle = $aPhoto [ 'title' ]; |
33 | $iCmts = (int) $aPhoto [ 'comments_count' ]; |
34 | $iLoggId = (int) $_SESSION [ 'member_id' ]; |
35 | $iOwner = (int) $aPhoto [ 'owner' ]; |
36 | $iRepins = (int) $aPhoto [ 'repin_count' ]; |
37 | $iLikes = (int) $aPhoto [ 'like_count' ]; |
38 | $sActions = ( $iLoggId && $iOwner != $iLoggId ) ? '<a href="#" class="button repinbutton">Repin</a><a href="#" class="button likebutton">Like</a>' : '' ; |
40 | $sFile = ( file_exists ( $sFolder . $sFile )) ? $sFile : 'blank_photo.jpg' ; |
41 | $aPathInfo = pathinfo ( $sFolder . $sFile ); |
42 | $sExt = strtolower ( $aPathInfo [ 'extension' ]); |
44 | <!-- pin element { $iPhotoId } --> |
45 | <div class = "pin" pin_id= "{$iPhotoId}" > |
49 | <a href= "#" class = "button comment_tr" >Comment</a> |
51 | <a class = "image ajax" href= "service.php?id={$iPhotoId}" title= "{$sTitle}" > |
52 | <img alt= "{$sTitle}" src= "{$sFolder}{$sFile}" > |
55 | <p class = "desc" >{ $sTitle }</p> |
57 | <span class = "LikesCount" ><strong>{ $iLikes }</strong> likes</span> |
58 | <span>{ $iRepins } repins</span> |
59 | <span>{ $iCmts } comments</span> |
61 | <form class = "comment" method= "post" action= "" style= "display: none" onsubmit= "return submitComment(this, {$iPhotoId})" > |
62 | <textarea placeholder= "Add a comment..." maxlength= "255" name= "comment" ></textarea> |
63 | <input type= "submit" class = "button" value= "Comment" /> |
As you can see, both params affect SQL limits only.
Step 3. Javascript
Final changes I made in the main javascript file. There are only two new event handlers:
js/script.js
001 | function fileSelectHandler() { |
003 | var oFile = $( '#image_file' )[0].files[0]; |
005 | var formData = new FormData($( '#upload_form' )[0]); |
010 | beforeSend: function () { |
012 | success: function (e) { |
013 | $( '#upload_result' ).html( 'Thank you for your photo' ).show(); |
014 | setTimeout( function () { |
015 | $( "#upload_result" ).hide().empty(); |
016 | window.location.href = 'index.php' ; |
020 | $( '#upload_result' ).html( 'Error while processing uploaded image' ); |
030 | function submitComment(form, id) { |
034 | data: 'add=comment&id=' + id + '&comment=' + $(form).find( 'textarea' ).val(), |
036 | success: function (html){ |
044 | function initiateColorboxHandler() { |
045 | $( '.ajax' ).colorbox({ |
050 | onComplete: function (){ |
051 | $( this ).colorbox.resize(); |
052 | var iPinId = $( this ).parent().parent().attr( 'pin_id' ); |
055 | data: 'get=comments&id=' + iPinId, |
057 | success: function (html){ |
058 | $( '.comments' ).append(html); |
059 | $( this ).colorbox.resize(); |
063 | onCleanup: function (){ |
069 | $(document).ready( function (){ |
071 | $( '#image_file' ).change( function (){ |
072 | var file = this .files[0]; |
078 | if (! file.type.match( 'image.*' )) { |
079 | alert( "Select image please" ); |
086 | var $container = $( '.main_container' ); |
087 | $container.imagesLoaded( function (){ |
090 | itemSelector: '.pin' , |
093 | isAnimatedFromBottom: true |
096 | $container.infinitescroll({ |
097 | navSelector : '#page-nav' , |
098 | nextSelector : '#page-nav a' , |
099 | itemSelector : '.pin' , |
101 | finishedMsg: 'No more pages to load.' |
105 | function ( newElements ) { |
107 | var $newElems = $( newElements ).css({ opacity: 0 }); |
109 | $newElems.imagesLoaded( function (){ |
111 | $newElems.animate({ opacity: 1 }); |
112 | $container.masonry( 'appended' , $newElems, true ); |
114 | initiateColorboxHandler(); |
119 | $( '.comment_tr' ).click( function () { |
120 | $( this ).toggleClass( 'disabled' ); |
121 | $( this ).parent().parent().parent().find( 'form.comment' ).slideToggle(400, function () { |
122 | $( '.main_container' ).masonry(); |
126 | initiateColorboxHandler(); |
128 | $( '.pin .actions .likebutton' ).click( function () { |
129 | $( this ).attr( 'disabled' , 'disabled' ); |
130 | var iPinId = $( this ).parent().parent().parent().attr( 'pin_id' ); |
134 | data: 'add=like&id=' + iPinId, |
136 | success: function (res){ |
137 | $( '.pin[pin_id=' +iPinId+ '] .info .LikesCount strong' ).text(res); |
143 | $( '.pin .actions .repinbutton' ).click( function () { |
144 | var iPinId = $( this ).parent().parent().parent().attr( 'pin_id' ); |
148 | data: 'add=repin&id=' + iPinId, |
150 | success: function (res){ |
151 | window.location.href = 'profile.php?id=' + res; |
As you remember, in the first step we added a new jQuery library: infinitescroll. I added initialization of infinitescroll library here (for our infinite scroll) and modified initialization of masonry. Because we have to sort the new images, plus we have to handle onclick event for all new images (colorbox).
Conclusion
We have just finished our sixth lesson where we are writing our own Pinterest-like script. I hope you enjoy this series. It would be kind of you to share our materials with your friends. Good luck and welcome back!