Update a Craft CMS Query Using Ajax

May 21, 2023 by Jason McKinney


Welcome to the next installment of our on-going "Tips" series - DIY tutorials for web developers and the casual coder.

For this example you'll need the basics:

  1. A basic understanding of Craft CMS query construction
  2. A basic understanding of Javascript including how an Ajax request is formatted

We're going to build a list of items (in this case - events) that can be filtered by category.

We'll start by showing the 3 most recent events and let the user filter the full list of events by their location - creating a list that's more relevant to them.

Let's get started...

1) Create the select element and the initial query

This will be the state seen by the user before any interaction occurs.

Below, we will:

  1. Build the dropdown by listing the categories we created in Craft.
  2. Create the query to generate the initial list of events. We are ordering by a date field called "Event Date".
  3. List our first three upcoming events.
<!-- create the select element -->
<select id="select_event_region">
    <option>Select your region</option>
   {% set myCategoryQuery = craft.categories().group('locationRegion') %}
   {% set categories = myCategoryQuery.all() %}
   {% for list in categories %}
       <option value="{{ list.slug }}">{{ list.title }}</option>
   {% endfor %}

<!-- initial query -->
{% set params = { 
    section : 'events', 
    order : 'eventDate asc', 
    eventDate : '>=' ~ 'now'|date('U'), 
    limit : '3' 
} %}

<!-- get the entries -->
<div id="events">
   {% if craft.entries(params)|length %}
       {% for entry in craft.entries(params) %}
          <h4>{{ entry.title }}</h4>
       {% endfor %}
   {% endif%}

2) Create the JS

Retrieve the user settings from the select element and set it as a variable for our include. Once we have the option from the select element, we'll turn it into a variable and store it in the url.

Below we will:

  1. Establish the location of our include and where to insert it as a variable.
  2. Get the option from the select element onChange.
  3. Append the select option to the url.
<!-- get the select option onChange. Save it as a variable we can retrieve from our include. -->
{% js %}

    const base_url = '/ajax/events';
    const $patient_events = $('#events');

    $('#select_event_region').change(function() {
        const region = this.value;
        const url = (region.length) ? `${base_url}?region=${region}` : base_url;
        $.get(url, function(response) {

{% endjs %}

This refers to the container that will be replaced by our updated results:

const $patient_events = $('#events');

Let's quickly look at this line and how it sets up our ajax call:

const url = (region.length) ? `${base_url}?region=${region}` : base_url;

We're creating a constant array with a conditional that says: If the variable is set, generate the url with the region variable or else just use the base url. The use of back tick is for a string where you're inserting variables so you don't have concatenate using the plus sign.

3) Create the include file

This will be pulled into our original template via ajax when the user filters by region. We'll keep this out of the way in a folder called "ajax". Example: /ajax/events.html

Below we will:

  1. Retrieve the select option from the url (the region that was selected by the user.
  2. Merge the option into our original query.
  3. Loop through the results.
<!-- Repeat original query but add the new variable -->
{% set params = { section: 'patientEvents', order: 'eventDate asc', endDate: '>' ~ 'now'|date('U') } %}
{% set region ='region') %}

{% set region_cat ='locationRegion').slug(region).one %}
{% if region_cat %}
    {% set params = params | merge({relatedTo: {targetElement:}}) %}
{% endif %}

<!-- Loop through the filtered entries -->
{% set entries = craft.entries(params) %}
{% if entries|length %}
  {% for entry in entries.all %}
    <h4>{{ entry.title }}</h4>
  {% endfor %}
{% endif %}

Let's look at:

{% set region ='region') %}

craft.request.param is not well documented but it grabs the variable from our ajax request that we set up in Step 2.

And that's it! More than three steps would be too many :-)

Some ideas to enhance this might include adding a loading animation and handling a null result. If you have any ideas other you'd like to share for improving this or if you found it useful, please let us know in the comments.