I’ll build this as a standalone advanced filter module for RX Theme: AJAX-ready, REST-ready, search/category/tag/date/sort/pagination support, debounce, history URL sync, loading states, accessibility, and safe fallbacks.
Below is an advanced production-style file for:
assets/static-js/static-chunks-js/chunk-028-filter-posts.js
It supports:
Search, category, tag, author, post type, date range, sort, order, pagination, load more, reset filter, URL sync, AJAX/REST fallback, loading skeleton, accessibility messages, abort previous request, debounce, cache, and custom events.
/*!
* RX Theme - Advanced Filter Posts Chunk
* File: assets/static-js/static-chunks-js/chunk-028-filter-posts.js
* Version: 1.0.0
*
* Purpose:
* Advanced post filtering system for RX Theme.
*
* Expected wrapper example:
*
* <section class="rx-post-filter" data-rx-post-filter>
* <input data-rx-filter="search" type="search" placeholder="Search posts">
*
* <select data-rx-filter="category">
* <option value="">All Categories</option>
* </select>
*
* <select data-rx-filter="tag">
* <option value="">All Tags</option>
* </select>
*
* <select data-rx-filter="sort">
* <option value="date">Latest</option>
* <option value="title">Title</option>
* <option value="modified">Updated</option>
* <option value="comment_count">Popular</option>
* </select>
*
* <select data-rx-filter="order">
* <option value="DESC">DESC</option>
* <option value="ASC">ASC</option>
* </select>
*
* <button data-rx-filter-reset>Reset</button>
*
* <div data-rx-filter-status aria-live="polite"></div>
* <div data-rx-post-results></div>
* <nav data-rx-post-pagination></nav>
* <button data-rx-load-more>Load More</button>
* </section>
*
* Optional localized object from WordPress:
*
* wp_localize_script( 'rx-filter-posts', 'RX_FILTER_POSTS', array(
* 'ajaxUrl' => admin_url( 'admin-ajax.php' ),
* 'restUrl' => esc_url_raw( rest_url( 'wp/v2/posts' ) ),
* 'nonce' => wp_create_nonce( 'wp_rest' ),
* 'action' => 'rx_filter_posts',
* 'perPage' => 9,
* 'postType' => 'post',
* ) );
*/
(function () {
'use strict';
/**
* Prevent duplicate initialization.
*/
if (window.RXFilterPostsChunkLoaded) {
return;
}
window.RXFilterPostsChunkLoaded = true;
const GLOBAL_CONFIG = window.RX_FILTER_POSTS || {};
const DEFAULTS = {
wrapperSelector: '[data-rx-post-filter]',
resultsSelector: '[data-rx-post-results]',
paginationSelector: '[data-rx-post-pagination]',
statusSelector: '[data-rx-filter-status]',
loadMoreSelector: '[data-rx-load-more]',
resetSelector: '[data-rx-filter-reset]',
inputSelector: '[data-rx-filter]',
ajaxUrl: GLOBAL_CONFIG.ajaxUrl || '',
restUrl: GLOBAL_CONFIG.restUrl || '',
nonce: GLOBAL_CONFIG.nonce || '',
action: GLOBAL_CONFIG.action || 'rx_filter_posts',
postType: GLOBAL_CONFIG.postType || 'post',
perPage: Number(GLOBAL_CONFIG.perPage || 9),
page: 1,
requestMode: GLOBAL_CONFIG.requestMode || 'ajax', // ajax | rest
syncUrl: GLOBAL_CONFIG.syncUrl !== false,
cache: GLOBAL_CONFIG.cache !== false,
cacheLimit: Number(GLOBAL_CONFIG.cacheLimit || 30),
debounceDelay: Number(GLOBAL_CONFIG.debounceDelay || 450),
skeletonCount: Number(GLOBAL_CONFIG.skeletonCount || 6),
autoInit: true,
loadMoreMode: GLOBAL_CONFIG.loadMoreMode || false,
messages: {
loading: 'Loading posts...',
empty: 'No posts found.',
error: 'Something went wrong. Please try again.',
success: 'Posts loaded successfully.',
reset: 'Filters reset.',
},
};
const RxUtils = {
merge(target, source) {
const output = Object.assign({}, target);
if (!source || typeof source !== 'object') {
return output;
}
Object.keys(source).forEach((key) => {
if (
source[key] &&
typeof source[key] === 'object' &&
!Array.isArray(source[key])
) {
output[key] = RxUtils.merge(output[key] || {}, source[key]);
} else {
output[key] = source[key];
}
});
return output;
},
debounce(callback, delay) {
let timer = null;
return function debouncedFunction() {
const context = this;
const args = arguments;
window.clearTimeout(timer);
timer = window.setTimeout(() => {
callback.apply(context, args);
}, delay);
};
},
sanitizeText(value) {
if (typeof value !== 'string') {
return '';
}
return value
.replace(/[<>]/g, '')
.replace(/javascript:/gi, '')
.trim();
},
toQueryString(params) {
const query = new URLSearchParams();
Object.keys(params).forEach((key) => {
const value = params[key];
if (
value !== undefined &&
value !== null &&
value !== '' &&
value !== false
) {
query.set(key, value);
}
});
return query.toString();
},
parseJSON(text) {
try {
return JSON.parse(text);
} catch (error) {
return null;
}
},
createElementFromHTML(html) {
const template = document.createElement('template');
template.innerHTML = html.trim();
return template.content.firstChild;
},
emit(element, eventName, detail) {
element.dispatchEvent(
new CustomEvent(eventName, {
bubbles: true,
detail: detail || {},
})
);
},
getUrlParams() {
const params = {};
const searchParams = new URLSearchParams(window.location.search);
searchParams.forEach((value, key) => {
if (key.indexOf('rx_') === 0) {
params[key.replace('rx_', '')] = value;
}
});
return params;
},
updateUrl(params) {
if (!window.history || !window.history.replaceState) {
return;
}
const url = new URL(window.location.href);
const currentParams = new URLSearchParams(url.search);
Array.from(currentParams.keys()).forEach((key) => {
if (key.indexOf('rx_') === 0) {
currentParams.delete(key);
}
});
Object.keys(params).forEach((key) => {
const value = params[key];
if (
value !== undefined &&
value !== null &&
value !== '' &&
value !== false
) {
currentParams.set(`rx_${key}`, value);
}
});
url.search = currentParams.toString();
window.history.replaceState({}, '', url.toString());
},
escapeHTML(value) {
if (value === undefined || value === null) {
return '';
}
return String(value)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
},
};
class RXPostFilter {
constructor(wrapper, options) {
this.wrapper = wrapper;
this.options = RxUtils.merge(DEFAULTS, options || {});
this.results = wrapper.querySelector(this.options.resultsSelector);
this.pagination = wrapper.querySelector(this.options.paginationSelector);
this.status = wrapper.querySelector(this.options.statusSelector);
this.loadMoreButton = wrapper.querySelector(this.options.loadMoreSelector);
this.resetButton = wrapper.querySelector(this.options.resetSelector);
this.inputs = Array.from(wrapper.querySelectorAll(this.options.inputSelector));
this.state = {
search: '',
category: '',
tag: '',
author: '',
post_type: this.options.postType,
date_from: '',
date_to: '',
sort: 'date',
order: 'DESC',
page: this.options.page,
per_page: this.options.perPage,
};
this.cache = new Map();
this.abortController = null;
this.isLoading = false;
this.lastRequestKey = '';
this.handleInputChange = RxUtils.debounce(
this.handleInputChange.bind(this),
this.options.debounceDelay
);
this.handleImmediateChange = this.handleImmediateChange.bind(this);
this.handlePaginationClick = this.handlePaginationClick.bind(this);
this.handleLoadMore = this.handleLoadMore.bind(this);
this.handleReset = this.handleReset.bind(this);
}
init() {
if (!this.wrapper || !this.results) {
return;
}
this.wrapper.dataset.rxFilterReady = 'true';
this.applyUrlParams();
this.bindEvents();
this.readInputsToState(false);
this.fetchPosts({ replace: true, reason: 'init' });
RxUtils.emit(this.wrapper, 'rxFilter:init', {
state: this.state,
instance: this,
});
}
bindEvents() {
this.inputs.forEach((input) => {
const tag = input.tagName.toLowerCase();
const type = input.getAttribute('type');
if (tag === 'input' && (type === 'search' || type === 'text')) {
input.addEventListener('input', this.handleInputChange);
} else {
input.addEventListener('change', this.handleImmediateChange);
}
});
if (this.pagination) {
this.pagination.addEventListener('click', this.handlePaginationClick);
}
if (this.loadMoreButton) {
this.loadMoreButton.addEventListener('click', this.handleLoadMore);
}
if (this.resetButton) {
this.resetButton.addEventListener('click', this.handleReset);
}
window.addEventListener('popstate', () => {
this.applyUrlParams();
this.readInputsToState(false);
this.fetchPosts({ replace: true, reason: 'popstate' });
});
}
applyUrlParams() {
if (!this.options.syncUrl) {
return;
}
const params = RxUtils.getUrlParams();
Object.keys(params).forEach((key) => {
if (Object.prototype.hasOwnProperty.call(this.state, key)) {
this.state[key] = RxUtils.sanitizeText(params[key]);
}
});
this.inputs.forEach((input) => {
const key = input.dataset.rxFilter;
if (!key || params[key] === undefined) {
return;
}
if (input.type === 'checkbox') {
input.checked = params[key] === input.value;
} else if (input.type === 'radio') {
input.checked = params[key] === input.value;
} else {
input.value = params[key];
}
});
}
readInputsToState(resetPage) {
const nextState = Object.assign({}, this.state);
this.inputs.forEach((input) => {
const key = input.dataset.rxFilter;
if (!key) {
return;
}
if (input.type === 'checkbox') {
if (input.checked) {
nextState[key] = RxUtils.sanitizeText(input.value);
} else if (nextState[key] === input.value) {
nextState[key] = '';
}
} else if (input.type === 'radio') {
if (input.checked) {
nextState[key] = RxUtils.sanitizeText(input.value);
}
} else {
nextState[key] = RxUtils.sanitizeText(input.value);
}
});
if (resetPage) {
nextState.page = 1;
}
nextState.per_page = Number(nextState.per_page || this.options.perPage);
nextState.page = Number(nextState.page || 1);
this.state = nextState;
return this.state;
}
handleInputChange() {
this.readInputsToState(true);
this.fetchPosts({ replace: true, reason: 'input' });
}
handleImmediateChange() {
this.readInputsToState(true);
this.fetchPosts({ replace: true, reason: 'change' });
}
handlePaginationClick(event) {
const button = event.target.closest('[data-rx-page]');
if (!button) {
return;
}
event.preventDefault();
const page = Number(button.dataset.rxPage || 1);
if (!page || page === this.state.page || this.isLoading) {
return;
}
this.state.page = page;
this.fetchPosts({ replace: true, reason: 'pagination' });
this.scrollToResults();
}
handleLoadMore(event) {
event.preventDefault();
if (this.isLoading) {
return;
}
this.state.page = Number(this.state.page || 1) + 1;
this.fetchPosts({ replace: false, reason: 'load_more' });
}
handleReset(event) {
if (event) {
event.preventDefault();
}
this.state = {
search: '',
category: '',
tag: '',
author: '',
post_type: this.options.postType,
date_from: '',
date_to: '',
sort: 'date',
order: 'DESC',
page: 1,
per_page: this.options.perPage,
};
this.inputs.forEach((input) => {
if (input.type === 'checkbox' || input.type === 'radio') {
input.checked = false;
} else if (input.dataset.rxFilter === 'sort') {
input.value = 'date';
} else if (input.dataset.rxFilter === 'order') {
input.value = 'DESC';
} else {
input.value = '';
}
});
this.setStatus(this.options.messages.reset);
this.fetchPosts({ replace: true, reason: 'reset' });
}
getRequestParams() {
return {
search: this.state.search,
category: this.state.category,
tag: this.state.tag,
author: this.state.author,
post_type: this.state.post_type,
date_from: this.state.date_from,
date_to: this.state.date_to,
sort: this.state.sort,
order: this.state.order,
page: this.state.page,
per_page: this.state.per_page,
};
}
getRequestKey() {
return JSON.stringify(this.getRequestParams());
}
async fetchPosts(options) {
const fetchOptions = Object.assign(
{
replace: true,
reason: 'manual',
},
options || {}
);
const params = this.getRequestParams();
const requestKey = this.getRequestKey();
this.lastRequestKey = requestKey;
if (this.options.syncUrl) {
RxUtils.updateUrl(params);
}
if (this.options.cache && this.cache.has(requestKey)) {
const cachedData = this.cache.get(requestKey);
this.renderResponse(cachedData, fetchOptions);
return;
}
if (this.abortController) {
this.abortController.abort();
}
this.abortController = new AbortController();
this.setLoading(true, fetchOptions.replace);
RxUtils.emit(this.wrapper, 'rxFilter:beforeRequest', {
state: this.state,
params,
reason: fetchOptions.reason,
});
try {
const response =
this.options.requestMode === 'rest'
? await this.fetchViaRest(params)
: await this.fetchViaAjax(params);
if (this.lastRequestKey !== requestKey) {
return;
}
const normalized = this.normalizeResponse(response);
if (this.options.cache) {
this.saveCache(requestKey, normalized);
}
this.renderResponse(normalized, fetchOptions);
RxUtils.emit(this.wrapper, 'rxFilter:afterRequest', {
state: this.state,
response: normalized,
reason: fetchOptions.reason,
});
} catch (error) {
if (error && error.name === 'AbortError') {
return;
}
this.renderError(error);
RxUtils.emit(this.wrapper, 'rxFilter:error', {
state: this.state,
error,
reason: fetchOptions.reason,
});
} finally {
this.setLoading(false, fetchOptions.replace);
}
}
async fetchViaAjax(params) {
if (!this.options.ajaxUrl) {
throw new Error('RX Filter Posts: ajaxUrl is missing.');
}
const body = new URLSearchParams();
body.set('action', this.options.action);
body.set('nonce', this.options.nonce || '');
Object.keys(params).forEach((key) => {
body.set(key, params[key]);
});
const response = await window.fetch(this.options.ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
},
body: body.toString(),
signal: this.abortController.signal,
});
const text = await response.text();
const json = RxUtils.parseJSON(text);
if (!response.ok) {
throw new Error(`Request failed with status ${response.status}`);
}
return json || {
success: true,
data: {
html: text,
pagination: '',
found_posts: 0,
max_pages: 1,
},
};
}
async fetchViaRest(params) {
if (!this.options.restUrl) {
throw new Error('RX Filter Posts: restUrl is missing.');
}
const restParams = {
search: params.search,
page: params.page,
per_page: params.per_page,
order: params.order.toLowerCase(),
orderby: this.mapRestOrderBy(params.sort),
};
if (params.category) {
restParams.categories = params.category;
}
if (params.tag) {
restParams.tags = params.tag;
}
if (params.author) {
restParams.author = params.author;
}
if (params.date_from) {
restParams.after = new Date(params.date_from).toISOString();
}
if (params.date_to) {
restParams.before = new Date(params.date_to).toISOString();
}
const url = `${this.options.restUrl}?${RxUtils.toQueryString(restParams)}`;
const response = await window.fetch(url, {
method: 'GET',
credentials: 'same-origin',
headers: {
'X-WP-Nonce': this.options.nonce || '',
},
signal: this.abortController.signal,
});
const posts = await response.json();
if (!response.ok) {
throw new Error(`REST request failed with status ${response.status}`);
}
const total = Number(response.headers.get('X-WP-Total') || 0);
const totalPages = Number(response.headers.get('X-WP-TotalPages') || 1);
return {
success: true,
data: {
html: this.renderRestPosts(posts),
pagination: this.buildPagination(params.page, totalPages),
found_posts: total,
max_pages: totalPages,
},
};
}
mapRestOrderBy(sort) {
const map = {
date: 'date',
title: 'title',
modified: 'modified',
comment_count: 'comment_count',
id: 'id',
slug: 'slug',
};
return map[sort] || 'date';
}
normalizeResponse(response) {
if (!response) {
return {
html: '',
pagination: '',
found_posts: 0,
max_pages: 1,
};
}
if (response.success === false) {
throw new Error(response.data && response.data.message ? response.data.message : 'Request failed.');
}
const data = response.data || response;
return {
html: data.html || '',
pagination: data.pagination || '',
found_posts: Number(data.found_posts || data.total || 0),
max_pages: Number(data.max_pages || data.total_pages || 1),
message: data.message || '',
};
}
renderResponse(data, options) {
const replace = options && options.replace !== false;
if (!data.html || String(data.html).trim() === '') {
if (replace) {
this.results.innerHTML = this.getEmptyHTML();
}
this.setStatus(this.options.messages.empty);
} else if (replace) {
this.results.innerHTML = data.html;
this.setStatus(data.message || this.options.messages.success);
} else {
const temp = document.createElement('div');
temp.innerHTML = data.html;
Array.from(temp.children).forEach((child) => {
this.results.appendChild(child);
});
this.setStatus(data.message || this.options.messages.success);
}
if (this.pagination) {
this.pagination.innerHTML = data.pagination || this.buildPagination(this.state.page, data.max_pages);
}
this.updateLoadMoreButton(data);
RxUtils.emit(this.wrapper, 'rxFilter:render', {
state: this.state,
data,
});
}
renderError(error) {
const message = error && error.message ? error.message : this.options.messages.error;
this.results.innerHTML = `
<div class="rx-filter-error" role="alert">
<p>${RxUtils.escapeHTML(this.options.messages.error)}</p>
<small>${RxUtils.escapeHTML(message)}</small>
</div>
`;
this.setStatus(this.options.messages.error);
}
renderRestPosts(posts) {
if (!Array.isArray(posts) || posts.length === 0) {
return '';
}
return posts
.map((post) => {
const title = post.title && post.title.rendered ? post.title.rendered : 'Untitled';
const excerpt = post.excerpt && post.excerpt.rendered ? post.excerpt.rendered : '';
const link = post.link || '#';
const date = post.date ? new Date(post.date).toLocaleDateString() : '';
let image = '';
if (
post._embedded &&
post._embedded['wp:featuredmedia'] &&
post._embedded['wp:featuredmedia'][0] &&
post._embedded['wp:featuredmedia'][0].source_url
) {
image = post._embedded['wp:featuredmedia'][0].source_url;
}
return `
<article class="rx-filter-card">
${
image
? `<a class="rx-filter-card__image" href="${RxUtils.escapeHTML(link)}">
<img src="${RxUtils.escapeHTML(image)}" alt="${RxUtils.escapeHTML(this.stripHTML(title))}" loading="lazy">
</a>`
: ''
}
<div class="rx-filter-card__body">
<time class="rx-filter-card__date">${RxUtils.escapeHTML(date)}</time>
<h2 class="rx-filter-card__title">
<a href="${RxUtils.escapeHTML(link)}">${title}</a>
</h2>
<div class="rx-filter-card__excerpt">${excerpt}</div>
</div>
</article>
`;
})
.join('');
}
buildPagination(currentPage, maxPages) {
currentPage = Number(currentPage || 1);
maxPages = Number(maxPages || 1);
if (maxPages <= 1) {
return '';
}
const pages = [];
const range = 2;
const start = Math.max(1, currentPage - range);
const end = Math.min(maxPages, currentPage + range);
if (currentPage > 1) {
pages.push(`
<button class="rx-page-btn rx-page-btn--prev" data-rx-page="${currentPage - 1}" aria-label="Previous page">
Previous
</button>
`);
}
if (start > 1) {
pages.push(this.getPageButton(1, currentPage));
if (start > 2) {
pages.push(`<span class="rx-page-dots" aria-hidden="true">…</span>`);
}
}
for (let page = start; page <= end; page += 1) {
pages.push(this.getPageButton(page, currentPage));
}
if (end < maxPages) {
if (end < maxPages - 1) {
pages.push(`<span class="rx-page-dots" aria-hidden="true">…</span>`);
}
pages.push(this.getPageButton(maxPages, currentPage));
}
if (currentPage < maxPages) {
pages.push(`
<button class="rx-page-btn rx-page-btn--next" data-rx-page="${currentPage + 1}" aria-label="Next page">
Next
</button>
`);
}
return `
<div class="rx-pagination" role="navigation" aria-label="Post pagination">
${pages.join('')}
</div>
`;
}
getPageButton(page, currentPage) {
const active = page === currentPage;
return `
<button
class="rx-page-btn ${active ? 'is-active' : ''}"
data-rx-page="${page}"
${active ? 'aria-current="page"' : ''}
>
${page}
</button>
`;
}
updateLoadMoreButton(data) {
if (!this.loadMoreButton) {
return;
}
const currentPage = Number(this.state.page || 1);
const maxPages = Number(data.max_pages || 1);
if (currentPage >= maxPages) {
this.loadMoreButton.hidden = true;
this.loadMoreButton.disabled = true;
} else {
this.loadMoreButton.hidden = false;
this.loadMoreButton.disabled = false;
}
}
setLoading(isLoading, replace) {
this.isLoading = isLoading;
this.wrapper.classList.toggle('is-loading', isLoading);
this.wrapper.setAttribute('aria-busy', isLoading ? 'true' : 'false');
this.inputs.forEach((input) => {
input.classList.toggle('is-filtering', isLoading);
});
if (this.loadMoreButton) {
this.loadMoreButton.disabled = isLoading;
}
if (isLoading) {
this.setStatus(this.options.messages.loading);
if (replace) {
this.results.innerHTML = this.getSkeletonHTML();
}
}
}
setStatus(message) {
if (!this.status) {
return;
}
this.status.textContent = message || '';
}
getSkeletonHTML() {
const count = this.options.skeletonCount;
let html = '<div class="rx-filter-skeleton-grid" aria-hidden="true">';
for (let index = 0; index < count; index += 1) {
html += `
<div class="rx-filter-skeleton-card">
<div class="rx-filter-skeleton-media"></div>
<div class="rx-filter-skeleton-line rx-filter-skeleton-line--short"></div>
<div class="rx-filter-skeleton-line"></div>
<div class="rx-filter-skeleton-line"></div>
</div>
`;
}
html += '</div>';
return html;
}
getEmptyHTML() {
return `
<div class="rx-filter-empty">
<p>${RxUtils.escapeHTML(this.options.messages.empty)}</p>
</div>
`;
}
saveCache(key, value) {
if (this.cache.size >= this.options.cacheLimit) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
scrollToResults() {
if (!this.results || typeof this.results.scrollIntoView !== 'function') {
return;
}
const prefersReducedMotion = window.matchMedia &&
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
this.results.scrollIntoView({
behavior: prefersReducedMotion ? 'auto' : 'smooth',
block: 'start',
});
}
stripHTML(html) {
const temp = document.createElement('div');
temp.innerHTML = html;
return temp.textContent || temp.innerText || '';
}
destroy() {
this.inputs.forEach((input) => {
input.removeEventListener('input', this.handleInputChange);
input.removeEventListener('change', this.handleImmediateChange);
});
if (this.pagination) {
this.pagination.removeEventListener('click', this.handlePaginationClick);
}
if (this.loadMoreButton) {
this.loadMoreButton.removeEventListener('click', this.handleLoadMore);
}
if (this.resetButton) {
this.resetButton.removeEventListener('click', this.handleReset);
}
if (this.abortController) {
this.abortController.abort();
}
this.wrapper.dataset.rxFilterReady = 'false';
RxUtils.emit(this.wrapper, 'rxFilter:destroy', {
instance: this,
});
}
}
function initRXPostFilters(context) {
const root = context || document;
const wrappers = Array.from(root.querySelectorAll(DEFAULTS.wrapperSelector));
wrappers.forEach((wrapper) => {
if (wrapper.dataset.rxFilterInitialized === 'true') {
return;
}
wrapper.dataset.rxFilterInitialized = 'true';
const customOptions = parseOptions(wrapper);
const instance = new RXPostFilter(wrapper, customOptions);
wrapper.rxPostFilter = instance;
instance.init();
});
}
function parseOptions(wrapper) {
const options = {};
if (wrapper.dataset.rxPerPage) {
options.perPage = Number(wrapper.dataset.rxPerPage);
}
if (wrapper.dataset.rxPostType) {
options.postType = wrapper.dataset.rxPostType;
}
if (wrapper.dataset.rxRequestMode) {
options.requestMode = wrapper.dataset.rxRequestMode;
}
if (wrapper.dataset.rxSyncUrl) {
options.syncUrl = wrapper.dataset.rxSyncUrl !== 'false';
}
if (wrapper.dataset.rxLoadMoreMode) {
options.loadMoreMode = wrapper.dataset.rxLoadMoreMode === 'true';
}
return options;
}
function ready(callback) {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', callback);
} else {
callback();
}
}
window.RXPostFilter = RXPostFilter;
window.initRXPostFilters = initRXPostFilters;
ready(() => {
initRXPostFilters(document);
});
})();
For WordPress AJAX, this JavaScript needs a PHP handler later, usually in:
inc/ajax/filter-posts.php
And enqueue/localize from:
inc/core/enqueue.php
The most important thing: this JS file is frontend logic only. To make it fully work with WordPress posts, you need the matching PHP AJAX function that receives search, category, tag, sort, order, page, and returns post HTML plus pagination.