3

I have a page that lists all entries from the section 'work'. I would like the user to be able to filter these entries by category but via ajax so I don't have to go to a new page.

{% set entries = craft.entries.section('work').limit(null).search('categories:hotel') %}

This works but each category has to have its own page. Is it possibly to load the filtered entries by ajax? Something using {% if craft.request.isAjax %}? Here is the relevant html:

    <div class="filters">
        <p class="categories">Categories</p>
        <p><a href="{{ url('work') }}">All</a></p>
        <p><a href="{{ url('work/hotel') }}">Hotel</a></p>
        <p><a href="{{ url('work/bar') }}">Bar</a></p>
        <p><a href="{{ url('work/club') }}">Club</a></p>
        <p><a href="{{ url('work/restaurant') }}">Restaurant</a></p>
        <p><a href="{{ url('work/retail') }}">Retail</a></p>
    </div>

    {% set entries = craft.entries.section('work').limit(null) %}
    <div class="projects">
        <ul>
            {% for entry in entries %}
                <li class="project">
                    <a href="{{ entry.url }}">
                        {% for asset in entry.mainPhoto %}
                            <div class="unveil-wrapper">
                                <img class="unveil" src="{{ url('assets/images/placeholder.png') }}" alt="{{ entry.title }}">
                            </div>
                        {% endfor %}
                        <p class="name">{{ entry.title }}</p>
                        <p class="address">{{ entry.address }}</p>
                    </a>
                </li>
            {% endfor %}
        </ul>
    </div>

</section>
7

There's a ton of ways to pull this off, but in my opinion setting up pages/URLs for each category is probably a good idea, for SEO and sharing reasons. It's not something that requires a lot of work and would make implementing the AJAX part a breeze. Another win is that you're going to have a solution which will actually work without AJAX or JavaScript, too – which is always a plus :)

To start, make sure the template path for your category group points to the same template that you use for your work index listing page (e.g. work/index), and that the URL pattern is work/{slug}.

Then, amend the listing template like this – the idea is to add the category filter to the craft.entries query only when one of the category URLs are requested.

{# Your basic work entries query #}
{% set query = {
    section : 'work',
    limit : null
} %}

{% If a category URL is requested, Craft will have populated your template with a global `category` variable #}
{% if category is defined %}
    {% set query = query|merge({
        search : 'categories:'~category.slug
    }) %}
{% endif %}

{# Fetch entries #}
{% set entries = craft.entries(query) %}

<div class="projects">
    <ul>
        {% for entry in entries %}
            <li class="project">
                ...
            </li>
        {% endfor %}
    </ul>
</div>

For the AJAX part, here's a super simple implementation – this example uses jQuery, and the concept is basically that we'll have the category URLs load using AJAX, and then just swap out the contents of the .projects DOM element with the returned HTML – no need for a separate AJAX template, JSON or even a craft.request.isAjaxRequest test. It's probably also a good idea to use the HTML5 History API to update the browser's address bar:

// Check if the browser supports HTML5 history
var historySupport = !!(window.history && history.pushState);

// Add an event listener for filter clicks
$(function () {
    $('body').on('click', '.filters a', onFilterClick);
});

// Event handler for the filter click
function onFilterClick (e)
{
    e.preventDefault();
    var $filter = $(e.currentTarget);
    var href = $filter.attr('href');
    if (historySupport) {
        // Update the browser's address bar
        history.pushState(null, null, href);  
    }
    $.ajax($filter.attr('href), {
        dataType : 'html',
        success : function (response) {
            refreshProjects(response);
        }
    });
}

function refreshProjects (html)
{

    // Update the <title> tag
    document.title = $(html).filter('title').text();

    // Update the .projects DOM element with new HTML
    var $html = $('<div />').append(html);
    $('.projects').html($html.find('.projects').html());

}
|improve this answer|||||
  • Thanks for such a thorough answer Mats. And having one that can work without ajax too is great. Just one last question, I'm using jquery unveil and the images don't load when I click a filter. Where should I use $(".unveil").trigger("unveil"); in the js? I appreciate all of the help, its my first time using ajax and I'm not great with js. – patrick.altair May 26 '16 at 14:57
  • Oh never mind, I figured it out. Thanks again Mats! – patrick.altair May 26 '16 at 15:11
  • No problem, @patrick.altair – nice to be able to help out :) – Mats Mikkel Rummelhoff May 27 '16 at 5:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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