# 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
<?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`.

```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`:

```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 🎉
