Enable AJAX pagination in SilverStripe in three easy steps!
Step 1: wrap your DataList
with a list decorator:
$pages = new AjaxPaginatedList(Page::get(), $this->request);
Step 2a: add pagination metadata markup into your template:
<div class='pagination" $Pages.PaginationMetadata(2)>
Step 2b: prepare the target area for page content in the template:
<div class='pagination-content'></div>
Step 3: apply the widget (assuming the requirements for the JavaScript libraries are met):
$('div.pagination').sspagination({contentSelector: '.pagination-content'});
Also, it's just as easy to apply an endless-style pagination (like Google images or Twitter have) - instead of a page list you will get a more button and pages will be appended at the end, instead of replaced!
There might be more to come ;-) If you have an idea for things that would fit nicely into widgets, let me know!
frontend/javascript/underscore.js
)framework/admin/javascript/lib.js
)docs/sspagination-how-to.md
.Add following line to your composer.json
and run composer update
:
"mateusz/frontend": "*"
Note the package name has recently been changed from silverstripe/frontend
, but both names should still work. The
former is preferred for future compatibility.
The widget completely replaces a specified PHP-driven pagination control with a JS one. The original markup is hidden and not used, and internal underscore templates are used instead. Variety of classes are available on the DOM elements for customisation. A couple of hooks have also been provided.
By default the widget is using a "poor man's PJAX" - which means it fetches the entire page via AJAX, and then uses jQuery to pick up only the element that needs to be replaced. This is perfect in two situations:
The downside for the dynamic scenario is that we are rendering entire pages which is slower as we are invoking entire stack.
If the speed (or server load) is of concern, it's easy to enable PJAX fetching. PJAX will ensure just the required snippet is rendered and server via JSON. See the PJAX how-to for details.
jQuery and jQuery UI are required for this widget to run. This module does not supply these, usually you'd like to be able to choose your own version of jQuery so loading of these is up to you. However the two other requirements are provided by Framework and by the module, so you can just copy and paste these.
Here is an example how this could be done via Requirements
API in your Controller
:
public function init() {
parent::init();
// First, load jQuery - don't forget to update the paths!
Requirements::javascript('<your-script-path>/jquery-2.0.0.js');
Requirements::javascript('<your-script-path>//jquery-ui-1.10.3.custom.js');
// Second, load scripts for this module - order os important.
Requirements::javascript('framework/admin/javascript/lib.js');
Requirements::javascript('frontend/javascript/underscore.js');
Requirements::javascript('frontend/javascript/jquery.ss.pagination.js');
Requirements::javascript('frontend/javascript/jquery.ss.endless.js'); // choose one or both.
// Finally, pull in your custom code.
Requirements::javascript('<your-script-path>/your-script.js');
}
The widget relies on the pagination metadata to be supplied during creation. This can be done via normal jQuery UI
option mechanism, but the easiest way to achieve that is to use the AjaPaginatedList
as a wrapper for the DataList.
public function Pages() {
return new AjaxPaginatedList(Page::get(), $this->request);
}
This provides you with an API call to generate HTML5 data attributes containing the pagination metadata that the widget
can automatically pick up. On the template side add it to the element containing the static pagination
control. The optional attribute is the same as for the
PaginationSummary
- it specifies the amount of context to be shown around current page.
<div class="pagination" $Pages.PaginationMetadata(2)>
// Static pagination follows.
</div>
It doesn't matter what is the structure of the static pagination markup. The widget will completely replace it using
it's own format for the pagination, based on the data attributes provided via the Pages.PaginationMetadata
(the format
can be changed - see "Template customisation" below). The static pagination is included as a fallback mechanism for non
JS enabled clients.
A more comprehensive example of usage can be found in the sspagination how-to.
Apply the widget on the frontend by at minimum specifying the contentSelector
option. This is a selector that will be
used to find the content element to replace, and also to find the relevant piece of the content received via a regular
AJAX call. Also specify the spinner (an indicator) that will automatically be shown when the pages are loading. You
should add this indicator element yourself, and hide it with CSS, so it doesn't appear if JS is broken/disabled.
$('div.pagination').sspagination({
contentSelector: '.pagination-content',
indicatorElement: $('.pagination-indicator')
});
The dynamic pagination should now be running.
You can invoke functions on the widget in the usual jQuery UI way:
// This will invoke the page fetch, and refresh the pagination control.
$('div.pagination').sspagination('setCurrentPage', 2);
// Hook into the sspagination events.
$('div.pagination').bind('sspaginationafterpagefetch', function(event) {
// Do processing.
});
// This uses the item number instead (as opposed to the page), which is how the backend handles the pagination.
$('div.pagination').sspagination({pageStart: 2});
// You can also dynamically change page size and the widget will refresh itself accordingly (this does not invoke a fetch).
$('div.pagination').sspagination({pageLength: 1});
// Destroy the widget and revert to static navigation.
$('div.pagination').sspagination('destroy');
The widget DOM is built up from parametrised underscore.js templates. You can redefine them to get a custom layout. One thing to keep in mind is that you can't update just one template using the jQuery widget API - the whole object will get replaced, so make sure all templates are provided. You can customise them on initialisation, or later:
$('div.pagination').sspagination({
templates: {
// All templates need to be provided here
// ...
}
});
A way to get around the verbosity if you want to update just a single template is to extract the option out like this:
// Extract the templates
var templates = $('div.pagination').sspagination('option', 'templates');
// Update just one.
templates.main = '<div class="extra-div"><ul class="ss-pagination"><%= inside %></ul></div>';
// You need to reset it to trigger a refresh, otherwise the template will not update immediately.
$('div.pagination').sspagination({templates: templates});
To see all available templates for a widget and their default values, have a look at the top of the relevant source file. More information on working with underscore templates can be found in underscore docs.
contentSelector
pageStart
pageLength
totalItems
getParam
context
indicatorElement
getTotalPages
getCurrentPage
setCurrentPage
destroy
beforepagefetch
: before the AJAX call is made. Return false to prevent fetching.afterpagefetch
: after a successful fetch & refresh (i.e. after the relevant afterrefresh)beforerefresh
: before the widget is updated (removed and recreated to fit with the current options). Return false to
prevent refreshing.afterrefresh
: after the widget has been updated.ontransition
: called just before the pagination-content element is about to be manipulated. Return false to prevent
default behaviour.This widget extends the sspagination (both js files have to be included). It replaces the classical, skip-to-page, pagination with a endless rollout behaviour. Instead of removing the displayed page content it appends the consecutive page at the end, and removes itself from the way if there is no more pages to be displayed.
The behaviour of dynamic changes to options is unspecified with this widget - i.e. it should be preconfigured on creation.
The widget usage is similar to sspagination:
$('div.pagination').ssendless({
contentSelector: '.pagination-content',
indicatorElement: $('.pagination-indicator')
});
Nothing yet.