Ajax Filters

The Filters block that ships with PorterWP Premium can work in two modes:

Mode
How it works

Static (default)

The page reloads and the next Query Loop with class .use-filters receives the updated query args.

AJAX

A JavaScript helper calls Porter_Filters_Ajax_Handler so only the results list is repainted, giving users a faster, app‑like experience.

Below is a step‑by‑step guide to bolt the AJAX version onto any archive or landing page.


1 – Create an output template

Add a PHP file inside porter/inc/templates. The file name must follow the pattern filter-template-{slug}.php – the slug is what you will reference in the block settings.

<?php
/**
 * Filter template: {slug}
 *
 * Receives:
 *  - $filters (array)         Current filter values keyed by parameter
 *  - $query   (WP_Query)      The query that will be (re)run
 *  - $attrs   (array)         Block attributes
 *
 * Return a string of HTML.
 */

defined( 'ABSPATH' ) || exit;

// Example: build a list of taxonomy terms as checkboxes.
ob_start();

$terms = get_terms( [
    'taxonomy'   => 'sector',
    'hide_empty' => false,
] );
?>
<form class="porter-filters" data-filters-form>
    <?php foreach ( $terms as $term ) : ?>
        <label>
            <input type="checkbox"
                   name="sector[]"
                   value="<?php echo esc_attr( $term->slug ); ?>"
                   <?php checked( in_array( $term->slug, $filters['sector'] ?? [], true ) ); ?> />
            <?php echo esc_html( $term->name ); ?>
        </label>
    <?php endforeach; ?>

    <button type="submit">Apply</button>
</form>
<?php

return ob_get_clean();

🔍 Tip: Keep all markup self‑contained – no wrapper divs are injected by the handler.


2 – Add the JavaScript

Create a file inside assets/src/js (or wherever you keep custom scripts) – e.g. filters-{slug}.js.

/**
 * Minimal AJAX driver for PorterWP Filters
 * Assumes there is ONE Filters block per page.
 */
document.addEventListener('DOMContentLoaded', () => {
  const form = document.querySelector('[data-filters-form]');
  if (!form) return;

  form.addEventListener('submit', async (e) => {
    e.preventDefault();

    const params = new URLSearchParams(new FormData(form));
    // Tell Porter which template to use
    params.append('template', '{slug}');

    const res = await fetch(window.porterFiltersAjax || '/wp-json/porterwp/v1/filter', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
      body: params.toString(),
    });

    if (!res.ok) return console.error('Filters AJAX failed');

    const { html } = await res.json();           // HTML string for the Query Loop
    const target = document.querySelector('.use-filters');
    if (target) target.innerHTML = html;
  });
});

Compile & watch – the existing Gulp task compileJS will copy the minified file into assets/dist/js/.


3 – Reference the template inside the block

  1. Insert a Filters block in the editor.

  2. In the Block › Inspector Panel › Template tail field, enter just the slug:

    {slug}

    PorterWP concatenates this with the filter-template- prefix and .php suffix to locate the file you created in step 1.

  3. Add the Query Loop block you want to reload immediately after the Filters block and give it the class use-filters.


4 – Enqueue the script

The simplest way is via your theme’s functions.php:

add_action( 'wp_enqueue_scripts', function () {
    if ( is_archive() || is_post_type_archive( 'my_cpt' ) ) {
        wp_enqueue_script(
            'filters-{slug}',
            get_theme_file_uri( 'assets/dist/js/filters-{slug}.js' ),
            [ 'wp-element' ],
            null,
            true
        );
    }
} );

When the page loads, your script intercepts form submits and talks to the REST endpoint provided by Porter_Filters_Ajax_Handler.


5 – Caching & CLS considerations

  • The HTML fragment returned by the REST endpoint is auto‑escaped and cacheable (if you’re on production and object‑cache is set up).

  • To avoid layout shift, add a min‑height to the .use-filters container in CSS.


Summary

Piece
Location
Responsibility

filter-template-{slug}.php

porter/inc/templates/

Renders the filter UI; returns string.

filters-{slug}.js

assets/src/js/ → compiled to assets/dist/js/

Handles submit, calls REST, injects HTML.

Template tail

ACF field inside Filters block

Tells PorterWP which template file to load.

.use-filters Query Loop

Editor

Container that receives the refreshed markup.

That’s all – you now have lightning‑fast, infinitely chainable faceted search 🎉

Last updated