1

I am trying to make my entry loop have a load more button, and not sure how to do it. I tried the method from http://craftsnippets.com/articles/infinite-scrolling-and-lazy-loading-with-craft-cms but I could not get it to work.

Below is the code for my entry loop and "Load More" button. What would be the easiest way to make the "Load More" button functional?

            <div class="more-posts-wrap">

                {% set moreABetterPost = craft.entries.section('aBetterPost').all() %}
                {% for entry in moreABetterPost %}
                <a href="{{ entry.getUrl }}" class="more-post">
                    <div class="more-post-img-wrap">
                        {% set featuredImage = entry.featuredImage.one() %}
                        <img src="{{ featuredImage.getUrl }}" class="more-post-img">
                        <div class="red-overlay"></div>
                    </div>
                    <div class="more-post-content">
                        <div class="more-post-title">
                            <h3>{{ entry.title }}</h3>
                        </div>
                        <div class="next-icon">
                            <i class="fas fa-chevron-right"></i>
                        </div>
                    </div>
                </a>
                {% endfor %}

            </div>

            <div class="load-button-wrap">
                <a href="#">
                    <div class="load-button">
                        <p>Load More</p>
                    </div>
                </a>
            </div>

EDIT: Clarifying the structure of my site.

{# _main.twig #}
Header Stuff
Header Stuff
...
{% block content %}
{# All of my pages exist in here by extending _main.twig #}
Page Stuff
Page Stuff
Page Stuff
...
LOAD MORE ENTRIES AREA
{% endblock %}
Footer Stuff
Footer Stuff
Footer Stuff
...
{# End of _main.twig #}

EDIT 2: Showing the code that's not working in regards to James Smith's Answer.

_loadMore.twig

{% set theLayout = craft.app.request.isAjax and not craft.app.request.isLivePreview ? '_ajaxLayout' : '_main' %}
{% extends theLayout %}

<section>
    <div class="more-posts-section">
        <div class="section-header">
            <h2>More Posts in <span>{{ category.title }}</span></h2>
        </div>

        {% set entries = craft.entries.section('aBetterPost').limit(6) %}
        {% paginate entries as pageInfo, pageEntries %}


        {% block content %}
        <div class="more-posts-wrap-border">
            <div class="more-posts-wrap js-ajaxPagination">
                {% for entry in aBetterPost%}
                  {% include '_layouts/_post' %}
                {% endfor %}
            </div>
        </div>
        {% endblock %}
        
        <div class="load-button-wrap">
            <a href="{{ '/' ~ craft.app.request.pathInfo ~ '/p' ~ (pageInfo.currentPage + 1) }}" class="load-button js-loadMore">{{'load more'|t}}</a>
        </div>

        {% block ajaxContent %}
            {% for entry in aBetterPost%}
              {% include '_layouts/_post' %}
            {% endfor %}
        {% endblock %}
    </div>
</section>

{# ======================================
AJAX PAGINATION SCRIPT
========================================= #}

{% if pageInfo is defined and pageInfo.total > 6 %}
    {% js %}
        var currentPage = 1;
        var perPage = 6;
        var count = perPage;
        $('.js-loadMore').click(function(e){
            e.preventDefault();
            if({{ pageInfo.total }} > count){
                $.ajax({
                    url: $(this).attr('href'),
                }).done(function(data) {
                    var $new = $('.js-ajaxPagination').append(data);
                    var $button = $('.js-loadMore');
                    // increment the counters
                    count = count + perPage;
                    currentPage++;
                    $button.show().attr('href', '/{{ craft.app.request.pathInfo }}/p' + (currentPage + 1));
                    if({{ pageInfo.total }} <= count){
                        $button.remove();
                    }
                });
            }
        });
    {% endjs %}
{% endif %}

_page.twig

{% extends "_layouts/_main" %}
{% block content %}
   {# Other Page Stuff#}
   {# Other Page Stuff#}
   {% include '_layouts/_loadMore' %}
{% endblock %}
4
  • 1
    Have you set up element api endpoint as described in article and added js code to website that handle ajax requests? Also, alternative to using solution described in my article, would be using sprig plugin: putyourlightson.com/sprig-cookbook#load-more Jul 7, 2020 at 1:04
  • Why bother with the added complexity of using the Element API or adding and learning yet another abstraction like Sprig? Just call up your same listing template and have two Twig blocks - one for Ajax requests and one for regular requests. Keep it simple! Jul 7, 2020 at 9:22
  • @piotrpog I added the JS, but I think the API stuff is where I got hung up. That Sprig plugin might be a good option, but I'm always a bit leery about non-first-party plugins with keeping up with Craft updates and such. Jul 7, 2020 at 12:19
  • @JamesSmith I am definitely not married to the method that I tried. If there is an easier/better way to do it, I'm all ears! Could you explain the process you mentioned a little more? Jul 7, 2020 at 12:21

2 Answers 2

6

The Sprig plugin will save you a lot of trouble, and dynamically loading more entries is a perfect use-case for it. If you prefer to use jQuery, then by all means do so, but keep in mind that it is just as much of a dependency as Sprig. For the record, Sprig has been widely adopted and at the time of writing has over 4,700 active installs in the plugin store.

Here is the load more recipe component, adapted for your markup.

{% set limit = 10 %}
{% set offset = offset ?? 0 %}

{% set entryQuery = craft.entries.section('aBetterPost').offset(offset).limit(limit) %}
{% set entries = entryQuery.all() %}

{% for entry in entries %}
    <a href="{{ entry.getUrl }}" class="more-post">
        <div class="more-post-img-wrap">
            {% set featuredImage = entry.featuredImage.one() %}
            <img src="{{ featuredImage.getUrl }}" class="more-post-img">
            <div class="red-overlay"></div>
        </div>
        <div class="more-post-content">
            <div class="more-post-title">
                <h3>{{ entry.title }}</h3>
            </div>
            <div class="next-icon">
                <i class="fas fa-chevron-right"></i>
            </div>
        </div>
    </a>
{% endfor %}

{# If the total entry count is greater than the number that has been displayed #}
{% if entryQuery.count() > offset + entries|length %}
    <div id="more-posts">
        <div class="load-button-wrap">
            <a sprig s-val:offset="{{ offset + limit }}" s-target="#more-posts" s-swap="outerHTML" href="#">
                <div class="load-button">
                    <p>Load More</p>
                </div>
            </a>
        </div>
    </div>
{% endif %}
1
  • 2
    Thanks for the answer! I had started using Sprig shortly after this question was posted, and I have loved it! Great plugin! Feb 8, 2022 at 15:54
4

Here's a cut-down version of what I'm using on https://cld.agency/showcase

The quick example below uses jQuery, but I have a more complete updated version using vanilla JS here: https://github.com/cld-agency/craftwind-boilerplate/blob/craft-4/templates/_listings/default.twig

{% set theLayout = craft.app.request.isAjax and not craft.app.request.isLivePreview ? '_ajaxLayout' : '_layout' %}
{% extends theLayout %}

{% set entries = craft.entries.section('news').limit(12) %}
{% paginate entries as pageInfo, pageEntries %}

{# =============================================================================== #}

{% block content %}     
    <h1>News</h1>
    <p>Introduction</p>
    <ul class="js-ajaxPagination">      
        {% for card in pageEntries %}
            <li>{% include '_partials/card'  %}</li>
        {% endfor %}
    </ul>
    {# allow no-js/googlebot fallback with a regular href to the next page... #}
    <p><a class="js-loadMore" href="{{ '/' ~ craft.app.request.pathInfo ~ '/p' ~ (pageInfo.currentPage + 1) }}">Load more</a></p>
{% endblock %}

{# =============================================================================== #}

{% block ajaxContent %}
    {% for card in pageEntries %}
        <li>{% include '_partials/card' %}</li>
    {% endfor %}
{% endblock %}

{# ======================================
AJAX PAGINATION SCRIPT
========================================= #}

{% if pageInfo is defined and pageInfo.total > 12 %}
    {% js %}
        var currentPage = 1;
        var perPage = 12;
        var count = perPage;
        $('.js-loadMore').click(function(e){
            e.preventDefault();
            if({{ pageInfo.total }} > count){
                $.ajax({
                    url: $(this).attr('href'),
                }).done(function(data) {
                    var $new = $('.js-ajaxPagination').append(data);
                    var $button = $('.js-loadMore');
                    // increment the counters
                    count = count + perPage;
                    currentPage++;
                    $button.show().attr('href', '/{{ craft.app.request.pathInfo }}/p' + (currentPage + 1));
                    if({{ pageInfo.total }} <= count){
                        $button.remove();
                    }
                });
            }
        });
    {% endjs %}
{% endif %}

So the trick is forking which template gets extended depending on whether it's an ajax request. Your _ajaxLayout template would be as simple as just:

{{ block('ajaxContent') }}

10
  • Thanks for the answer, looks great on your site! I'm having a bit of trouble getting it to work on my end. Would it be possible to see where my code would plug into this system? (Sorry, still learning :) ) Jul 7, 2020 at 13:23
  • It's impossible to say without knowing your exact setup, and there are lots of ways of achieving the same result... My example assumes that you have a global layout template called _layout and that that template contains a {{ endBody() }} tag to accept the injected js and a content block to output the content. Jul 7, 2020 at 13:35
  • Ok, I'm running into some snags. I don't think I have the structure for this set up correctly. My site is currently set up with my header/footer in a file called _main then all of my pages extends _main via {% block content%}. I'm currently trying to do this "More Posts" area in its own file _loadMore which I then include in my page. Right now it is doing some weird stuff like having multiple buttons and messing up the layout. And when I click the button, it literally duplicates the entire page in the area. So, I'm a bit confused on how you're using theLayout with your ajax Jul 9, 2020 at 13:29
  • Ok, I just realized that the funky layout stuff, was an error on my part. But it is still duplicating the page when I click my "Load More" button Jul 9, 2020 at 13:39
  • What is in your ajaxLayout template? (or whatever you named it) Jul 9, 2020 at 13:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.