Load more with Ajax
Requirements
- A fresh Kirby Starterkit as our basis
- Familiarity with the Kirby API, templates and controllers is useful
Intro
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.
HTML Template
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 project.php
…
… 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 $page->children()->listed()
with $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 + m
.
HTML controller
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.
JSON controller
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 localhost/photography.json
and is the URL we will use in the JavaScript in the next step.
The $more
variable is a boolean and checks if the pagination object still has a next page.
Additionally, we initialize the $html
and $json
variables, which will be assigned their values in the photography.json.php
template in the next step.
JSON template
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.
HTML template, part 2
Before we can get to our last missing piece, the JavaScript, we have to modify the 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.
Javascript
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.
If you put the script file into another location, make sure to load it in the footer.
If you use the JSON representations on the homepage, make sure to add home
to url
variable (or whatever page name you have set for the home
option in your config):
First we define a set of variables that we need for fetching the projects later on:
With
we add an event listener to the button which calls the closure (anonymous function) stored in the fetchProjects
variable.
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:
When JavaScript is disabled…
But what if JavaScript is disabled? Then our fancy load-more button is of no use anymore. To make our content accessible for users when JavaScript is disabled, we need a basic fallback solution that always works, and the load-more button provides progressive enhancement for supporting browsers.
Thanks to the pagination object, we can add such a fallback with a few lines of code.
Check if JavaScript is disabled
In the header.php
snippet, let's add a no-js
class on the html
element:
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.
Pagination controls
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.
Hide controls conditionally
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 header.php
snippet.
If JavaScript is enabled, we hide the pagination controls, if JavaScript is disabled, we hide the load-more button. To make the pagination look a little bit nicer, we also align the next page text on the right for some basic styling.
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.