Hi all,
Complete tutorial below.
If you have some listing of items with <cms:pages /> or <cms:query /> tags, here is how to add "loading more" functionality with auto-loading on scroll or click.
Here is the button.

- load more button
- ScreenCut-01-03---14-14-32-.png (5.13 KiB) Viewed 20570 times
When visitor scrolls to the bottom of the container with your listing, this button should be 'clicked' via JS script, which follows:
- Code: Select all
// BEST SCROLLER: http://stackoverflow.com/a/15382570
var _throttleTimer = null;
var _throttleDelay = 100;
var $window = $(window);
var $document = $(document);
$document.ready(function () {
$window
.off('scroll', ScrollHandler)
.on('scroll', ScrollHandler);
});
function ScrollHandler(e) {
//throttle event:
clearTimeout(_throttleTimer);
_throttleTimer = setTimeout(function () {
console.log('scroll');
//do work
if ($(window).scrollTop() >= $("div#listing-wrapper").height() - 200 ) {
console.log('Click triggered')
$('a#load-more').trigger('click');
}
}, _throttleDelay);
}
Code above looks for the
div id="listing-wrapper" and calculates dynamically current position on page.
div id="listing-wrapper" is just my imaginary div, that has inside all the items listed by <cms:pages />. If the height from the top of the page goes beyond the height of "listing-wrapper" minus 200px, then script takes our button (
<a id="load-more"></a>) and clicks it.
Now here is what happens in JS when we click the button. The following code is self-explanatory for a JS guy, but please ask questions is it's not clear.
- Code: Select all
$('a#load-more').on('click', function (event){
var $_button = $(this); // link that is clicked
var _options = $_button.attr('data-filt'); // custom_fields of cms:pages
var _current = $_button.attr('data-curr'); // already loaded page
var _mylimit = $_button.attr('data-limt'); // limit of cms:pages - number of items per page
var _mytotal = $_button.attr('data-totl'); // how many total pages are there # used to hide button
$.ajax({
url: 'assets/php/ajax/connect.php', // template that handles ajax request
data: {
// name of the snippet that has all the code that processes ajax request
// and sends back the complete html for new items:
snippet: 'ajax-files/load-more.html',
options: _options,
curpage: _current,
mylimit: _mylimit
},
timeout: 10000
})
.done(function(result) {
// general utility function
$('div#listing-wrapper').append( result );
$(document).ready(function () {
// Action after append is completly done
console.log('New content added.');
// Update the info about current loaded page, by incrementing after each successful ajax request.
$_button.attr('data-curr', parseInt( _current ) + 1 );
// Completely replace the content of the button #this can be done differently via inner span..
$_button.html("LOAD MORE<br><br>[" + _current + "/" + _mytotal + "]");
// If this load hits the last page, hide the button, since there is no items left to be loaded again.
if ( parseInt( _current ) + 1 >= _mytotal ) $("#load-more-container").fadeOut('fast').remove();
});
/* following sample is for disgusting 'Cube Portfolio'
$('#js-grid-lightbox-gallery').cubeportfolio('appendItems', result, function(){
// Upon successful loading of items into Cube, update all infos:
$("#load-more").attr('data-curr', parseInt( _current ) + 1 );
$("#load-more").html("LOAD MORE<br><br>[" + _current + "/" + _mytotal + "]");
if ( parseInt( _current ) + 1 >= _mytotal ) $("#load-more-container").fadeOut('fast').remove();
});
*/
})
.fail(function(msg) {
console.log('Ajax error: ' + msg.statusText); // log errors
});
return false; // don't follow href of the link
});
Before we can use these scripts, let's get through the changes to the couch code for listing.
I'll assume that all items are wrapped inside the
div id="listing-wrapper" with some default illustrative code:
- Code: Select all
<div id="listing-wrapper">
<cms:pages masterpage='blog.php' custom_field="author=admin" >
<div class="post">
<!-- Post Title -->
<h3 class="title"><a href="<cms:show k_page_link />"><cms:show k_page_title /></a></h3>
<!-- Post Date -->
<p class="sub"><cms:date k_page_date format='jS M, y'/></p>
<!-- Post Image -->
<img class="thumb" alt="" src="<cms:show blog_image />" />
<!-- Post Content -->
<cms:excerptHTML count='75' ignore='img'><cms:show blog_content /></cms:excerptHTML>
<!-- Read More Button -->
<p class="clearfix"><a href="<cms:show k_page_link />" class="button right"> Read More...</a></p>
</div>
</cms:pages>
</div>
With the code above, for example, you listed all posts in
blog.php that were written by author
admin. That listing can have pagination or can be a plain list without pagination as in the sample above.
First, let's add pagination and limit our listing to some default value, that administrator may comfortably set, for example, in some variable in
globals.php template.
- Code: Select all
<!-- Reference code for globals.php template (note: my /couch folder has been renamed to /cms): -->
<?php require_once( 'cms/cms.php' ); ?>
<cms:template title='Global settings' clonable='0' executable='0' >
<cms:embed "editables/<cms:show k_template_name />.html" />
</cms:template>
<?php COUCH::invoke(); ?>
<!-- /end of template-->
<!-- And code in the embedded file /snippets/editables/globals.php.html is: -->
<cms:editable type='text' name='records_visible' label='Number of visible records on the front page' desc='Enter any number or 0 to display all' order='10' >8</cms:editable>
<!-- /end of snippet -->
So, we let admin to enter some number in 'Global settings' template and start to use it for our listing:
- Code: Select all
<!-- before: -->
<cms:pages masterpage='blog.php' custom_field="author=admin" >
<!-- after: -->
<cms:set global_limit = "<cms:get_custom_field 'records_visible' masterpage='globals.php' />" scope='global' />
<cms:pages masterpage='blog.php' custom_field="author=admin" paginate='1' limit=global_limit >
...
</cms:pages>
To make sure more posts are loaded via ajax correctly, with the same 'author=admin' filter, let's also convert
custom_field parameter to a variable.
- Code: Select all
<!-- before: -->
<cms:set global_limit = "<cms:get_custom_field 'records_visible' masterpage='globals.php' />" scope='global' />
<cms:pages masterpage='blog.php' custom_field="author=admin" paginate='1' limit=global_limit >
...
</cms:pages>
<!-- after: -->
<cms:set global_limit = "<cms:get_custom_field 'records_visible' masterpage='globals.php' />" scope='global' />
<cms:set my_filtering = 'author=admin' scope=global' />
<cms:pages masterpage='blog.php' custom_field=my_filtering paginate='1' limit=global_limit >
...
</cms:pages>
By now we have almost everything in place. Let's add the 'a#load-more' button to our complete listing:
- Code: Select all
<div id="listing-wrapper">
<cms:set global_limit = "<cms:get_custom_field 'records_visible' masterpage='globals.php' />" scope='global' />
<cms:set my_filtering = 'author=admin' scope=global' />
<cms:pages masterpage='blog.php' custom_field=my_filtering paginate='1' limit=global_limit >
<div class="post">
<!-- Post Title -->
<h3 class="title"><a href="<cms:show k_page_link />"><cms:show k_page_title /></a></h3>
<!-- Post Date -->
<p class="sub"><cms:date k_page_date format='jS M, y'/></p>
<!-- Post Image -->
<img class="thumb" alt="" src="<cms:show blog_image />" />
<!-- Post Content -->
<cms:excerptHTML count='75' ignore='img'><cms:show blog_content /></cms:excerptHTML>
<!-- Read More Button -->
<p class="clearfix"><a href="<cms:show k_page_link />" class="button right"> Read More...</a></p>
</div>
<cms:if k_paginated_bottom>
<cms:if k_paginator_required >
<!-- Pagination -->
<div id="load-more-container" >
<div style="margin:0 auto; width:15vw; border:1px solid black; text-align:center; ">
<a href="#" id="load-more"
data-limt="<cms:show global_limit />"
data-curr="<cms:show k_current_page />"
data-totl="<cms:show k_total_pages />"
data-filt="<cms:show my_filtering />"
style="display:block; padding:15px 20px; text-align: center; letter-spacing: 4px; color: rgb(0, 0, 0);">
LOAD MORE
<br>
<br>
[<cms:show k_current_page />/<cms:show k_total_pages />]
</a>
</div>
</div>
<!-- /pagination -->
</cms:if>
</cms:if>
</cms:pages>
</div>
A successful loading of more elements will happen if we send to Ajax-processing code the same parameters that we use for listing the initial items. We should send the
my_filtering options, then
global_limit and, of course, use
offset parameter to skip already visible posts. It means that for
offset we will also send the
current page number to the code. I used
data-* attributes to keep those values and have them easily accessible later from JS script.
In JS script I used the external template, that is very comfortable to use in all ajax scripts.
url: 'assets/php/ajax/connect.php', // template that handles ajax request
Code for this ajax connector is below. Use it for all your ajax things, it's a pretty good one.
- Code: Select all
<?php require_once( "../../../cms/cms.php" ); ?>
<cms:template title='Ajax connector' hidden='1' order='1000' />
/** Extra debugging / logging for superadmins
<cms:if k_user_access_level = '10' >
<cms:php>error_log( print_r( $_REQUEST, true) ); </cms:php>
<cms:php>if( $_FILES ) error_log( print_r( $_FILES, true) ); </cms:php>
</cms:if>
/** Alow only ajax requests.
<cms:if "<cms:not "<cms:is_ajax />" />" >
<cms:abort msg="ERROR: Page can't be accessed directly." />
</cms:if>
/** Get snippet from POST ajax request.
<cms:if "<cms:gpc 'file' />" >
<cms:set snippet = "<cms:gpc 'file' />" />
</cms:if>
<cms:if "<cms:gpc 'filename' />" >
<cms:set snippet = "<cms:gpc 'filename' />" />
</cms:if>
<cms:if "<cms:gpc 'snippet' />" >
<cms:set snippet = "<cms:gpc 'snippet' />" />
</cms:if>
/** Prepare list of allowed snippets
<cms:capture into='whitelist' >
ajax-files/load-more.html |
</cms:capture>
/** Validate the snippet name
<cms:each whitelist >
<cms:if item = snippet >
<cms:if "<cms:exists item />">
/** Check if file exists on disk
<cms:set snippet_is_valid='1' scope='global' />
</cms:if>
</cms:if>
</cms:each>
/** Store the result of snippet code
<cms:capture into='ajax_output' >
<cms:if snippet_is_valid >
<cms:embed snippet />
<cms:else />ERROR: File not found: '<cms:show snippet />'
</cms:if>
</cms:capture>
/** Send back to JS only the result
<cms:abort msg=ajax_output is_404='0' />
<?php COUCH::invoke(); ?>
Now our JS script sends data to this
connect.php template and this template simply embeds the necessary snippet, that will handle all the processing.
snippet: 'ajax-files/load-more.html',
Create in
/snippets folder a folder
/ajax-files and place there a file
load-more.html with code below. It is a good-practice to have all code in different snippets, since there can be very many different ajax requests that you might want to add to your project. So for each different request it is comfy to simply send the snippet name which does the job.
The best part is that our snippet has almost the same code as we use for listing. Remember, that we sent following data in the ajax request:
mylimit - number of items per 'page', i.e. limit
options - content of custom_field parameter
curpage - current page that was already loaded, i.e. 5th of 12.
We need to calculate offset to skip already loaded items. Formula should help:
offset =
global_limit X
current_page. So complete listing of the snippet goes like this:
- Code: Select all
<cms:set global_limit = "<cms:gpc 'mylimit' />" scope='global' />
<cms:set my_filtering = "<cms:gpc 'options' />" scope='global' />
<cms:set current_page = "<cms:gpc 'curpage' />" scope='global' />
<cms:set pages_offset = "<cms:mul global_limit current_page />" scope='global' />
<cms:pages masterpage='blog.php' custom_field=my_filtering paginate='1' limit=global_limit offset=pages_offset >
<div class="post">
<!-- Post Title -->
<h3 class="title"><a href="<cms:show k_page_link />"><cms:show k_page_title /></a></h3>
<!-- Post Date -->
<p class="sub"><cms:date k_page_date format='jS M, y'/></p>
<!-- Post Image -->
<img class="thumb" alt="" src="<cms:show blog_image />" />
<!-- Post Content -->
<cms:excerptHTML count='75' ignore='img'><cms:show blog_content /></cms:excerptHTML>
<!-- Read More Button -->
<p class="clearfix"><a href="<cms:show k_page_link />" class="button right"> Read More...</a></p>
</div>
</cms:pages>
Now, once this snippet generates the markup for more items with limit, offset and custom_field, our connect.php template aborts the execution with showing of this markup and so it gets sent back to JS script, which appends the result html to the listing wrapper. In the end, the button gets updated with new visible text.
Final part:
Put the content of both scripts from the top of this post to some
blog.js like this
- Code: Select all
$(document).ready(function () {
// here goes the clicking part - $('a#load-more').on('click', function (event){...
// here goes the scrolling part - // BEST SCROLLER ....
});
and load it in your template
- Code: Select all
<script src="assets/js/pages/blog.js"></script>
Or maybe with <cms:rel /> tag

like this:
- Code: Select all
<cms:rel src="assets/js/pages/blog.js" />
Ask any questions.