Load more with Ajax
Add a load more button to your listings and append items via Ajax.
- A fresh Kirby Starterkit as our basis
- Familiarity with the Kirby API, templates and controllers is useful
When creating a portfolio, blog or image gallery webpage, we often want to show a limited number of projects, articles or images at first load, but give visitors the possibility to load more with a button. In this recipe, we will go through the steps needed to implement such a solution.
We will base this recipe off the photography page in Kirby's Starterkit which lists a number of subpages with an image and a title.
Kirby's pagination class will help us break up the projects into digestible chunks and make the logic pretty straightforward.
The photography page uses the
photography.php template, which looks like this when untouched:
As a first step, let's move the
<li> element from the template into a separate snippet called
… and replace this element with the newly created snippet in the template:
We do this so that we can reuse this snippet again later.
Next, we add a load-more button after the project list, and replace
$projects. We will define this variable in the next step in a controller.
To allow triggering the button via keyboard, the button gets a global accesskey attribute with the value of
m, so that depending on the used browser and operating system, users can trigger the button with a key combination +
Now, let's put the logic we need into a new controller. Create a new
photography.php controller in
site/controllers with the following code:
Here we define the
$projects variable and paginate the pages collection into chunks of 4 as defined in the
$limit variable. You can change the chunk size as needed, we use 4 here because we only have a limited set of data.
If we visit the page in the browser at this point, we will see four projects and our super fancy load-more button. However, as much as you might click on the button, nothing will happen yet.
To implement the logic for the load-more functionality, we create the controller for the JSON content representation.
This content representation will be available at the url
$more variable is a boolean and checks if the pagination object still has a next page.
Additionally, we initialize the
$json variables, which will be assigned their values in the
photography.json.php template in the next step.
We are slowly getting there… Our JSON representation controller now needs a corresponding template that returns the JSON encoded data:
Again we loop through the projects as before in the HTML template and call the same snippet. This time, we store everything in the
$html variable, which we add to the
$json array together with the
$more variable defined in the controller. Finally, we encode the array so that the template returns the data in JSON format.
If all went well and you open
localhost/photography.json in your browser, you will see the generated JSON data.
ul element in the template a little bit. We add the class name
projects and the
data-page attribute, so that we can fetch the number of the next pagination page in our JS.
Finally, our last step. Add the following script in
assets/js/templates/photography.js. By adding this script to this location, we make sure that it is only loaded for the
photography.php template. And because the footer already has the
@auto parameter to auto-load all template specific JS files, we don't have to require it specifically.
First we define a set of variables that we need for fetching the projects later on:
// the container `ul` to which we will later append the other chunks of projects const element = document.querySelector('.projects'); // the load-more button const button = document.querySelector('.load-more'); // the next page from the data attribute as explained in the last step const page = parseInt(element.getAttribute('data-limit'));
we add an event listener to the button which calls the closure (anonymous function) stored in the
Inside the closure, we make a fetch request to the JSON representation (current URL suffixed with
.json plus the pagination page), and on success, we add the elements to the DOM and increment the page counter by one.
When there are no more pages to fetch, we hide the button:
button.hidden = !data.more;
Thanks to the pagination object, we can add such a fallback with a few lines of code.
header.php snippet, let's add a
no-js class on the
In the JS file we created above, we add a onliner to replace this class with a
js class name instead.
In the following, we can now use this class to conditionally show either the load-more button or a standard pagination navigation.
Below the load-more button in the
photography.php template, we add the pagination controls:
At this point, if you visit the photography page in the browser, you can already use this navigation to navigate between the pagination pages. Almost done!
However, we don't want two different types of navigation at the same time. So let's change this with some CSS.
Let's add a new stylesheet in
/assets/css/templates and name it
photography.css so that it is loaded automatically via the
@auto parameter used for loading template specific stylesheets in the
Thanks to Scott Boms for the progressive enhancement suggestions.
That was it. If you want to check if a second or third button trigger works as well, publish the draft that is still lurking in the photography page or add some new cool projects of your own.