انتقل إلى المحتوى الرئيسي

Filters Module

The Filters module provides URL-based collection filtering with automatic server-side section rerendering. Every filter change updates the URL and rerenders registered widgets by fetching fresh HTML from the server.

URL Format

# Search
?q=shoes

# Multi-select
?filters[gender][]=men&filters[gender][]=women

# Search-text
?filters[material][]=cotton

# Range
?filters[price][min]=100&filters[price][max]=500

# Sort
?sort=price-asc

# Combined
?q=shoes&filters[gender][]=men&filters[price][min]=50&filters[price][max]=200&sort=price-desc

Quick Start

1. Register a section

Tell the filters module which widget to rerender and where to put the HTML:

// Programmatic
qumra.filters.section('products-grid', '#products-container');

Or use the HTML attribute (auto-detected):

<div data-qumra-section="products-grid">
{% widget "products-grid" %}
</div>

2. Use filters

// Toggle a filter — auto-rerenders the section
await qumra.filters.toggle('color', 'red');

// Set a price range
await qumra.filters.range('price', { min: 100, max: 500 });

// Search
await qumra.filters.search('shoes');

// Sort
await qumra.filters.sort('price-asc');

// Clear everything
await qumra.filters.clear();

How It Works

1. Developer registers section: qumra.filters.section('products-grid', '#container')
— or — SDK auto-detects <div data-qumra-section="products-grid">
2. User clicks a filter
3. FiltersModule syncs the URL (history.replaceState)
4. FiltersModule sends: GET /collection/shoes?widgetKey=products-grid&filters[color][]=red
5. Server renders only the widget — returns HTML fragment
6. FiltersModule replaces innerHTML of the target element
7. FiltersModule emits collection:filtered

API Reference

qumra.filters.section(widgetKey, target)

Register a widget for auto-rerendering when filters change.

ParamTypeDescription
widgetKeystringThe widget key (folder name in widgets/)
targetstringCSS selector for the container element
qumra.filters.section('products-grid', '#products-container');
qumra.filters.section('sidebar-filters', '#filters-sidebar');

Multiple sections can be registered — they all rerender in parallel.

qumra.filters.add(name, value)Promise<void>

Add a value to a multi-select filter.

await qumra.filters.add('gender', 'men');
await qumra.filters.add('gender', 'women');
// URL: ?filters[gender][]=men&filters[gender][]=women

qumra.filters.toggle(name, value)Promise<void>

Toggle a value on/off. If the value exists, it's removed; otherwise, it's added.

// First call — adds
await qumra.filters.toggle('gender', 'men');

// Second call — removes
await qumra.filters.toggle('gender', 'men');

qumra.filters.range(name, { min?, max? })Promise<void>

Set a range filter with min and/or max bounds.

await qumra.filters.range('price', { min: 100, max: 500 });
// URL: ?filters[price][min]=100&filters[price][max]=500

// Min only
await qumra.filters.range('price', { min: 50 });

qumra.filters.searchText(name, value)Promise<void>

Set a search-text filter. The value is free text matched against keywords defined in the filter configuration. Replaces any previous value for this filter.

await qumra.filters.searchText('material', 'cotton');
// URL: ?filters[material][]=cotton

// Empty string removes the filter
await qumra.filters.searchText('material', '');

qumra.filters.remove(name, value?)Promise<void>

Remove a specific value from a filter, or remove the entire filter.

// Remove specific value
await qumra.filters.remove('gender', 'men');

// Remove entire filter
await qumra.filters.remove('gender');

qumra.filters.clear()Promise<void>

Clear all active filters and search query. Emits collection:reset.

await qumra.filters.clear();

qumra.filters.search(query)Promise<void>

Set search query and rerender.

await qumra.filters.search('shoes');
// URL: ?q=shoes

// Combine search + filters
await qumra.filters.search('shoes');
await qumra.filters.add('gender', 'men');
// URL: ?q=shoes&filters[gender][]=men

qumra.filters.sort(value)Promise<void>

Set sort order and rerender. Emits collection:sorted.

await qumra.filters.sort('price-asc');
// URL: ?sort=price-asc

Supported values:

ValueDescription
createdAt-ascOldest first
createdAt-descNewest first (default)
price-ascCheapest first
price-descMost expensive first
rating-ascLowest rated first
rating-descHighest rated first

qumra.filters.apply(state, options?)Promise<void>

Apply multiple filters at once — sends a single rerender. Optionally set sort, search query, and merge behavior in the same call.

ParamTypeDescription
stateFiltersStateFilters to apply
options.mergebooleanMerge with existing filters instead of replacing (default: false)
options.sortstringSet sort order in the same commit
options.qstringSet search query in the same commit
// Replace all filters (default)
await qumra.filters.apply({
gender: ['men', 'women'],
brand: ['nike'],
price: { min: 100, max: 500 }
});

// Merge — adds/updates gender and brand, keeps other existing filters
await qumra.filters.apply({
gender: ['men'],
brand: ['nike'],
}, { merge: true });

// Filters + sort + search in a single commit (one rerender)
await qumra.filters.apply({
gender: ['men', 'women'],
price: { min: 100, max: 500 },
}, {
sort: 'price-asc',
q: 'shoes',
});

// Sort + query only, keep existing filters
await qumra.filters.apply({}, {
sort: 'price-desc',
q: 'sneakers',
merge: true,
});

Why use apply over individual methods?

Individual calls trigger a rerender each:

// 3 rerenders
await qumra.filters.toggle('gender', 'men');
await qumra.filters.range('price', { min: 100, max: 500 });
await qumra.filters.sort('price-asc');

apply does the same in one rerender:

// 1 rerender
await qumra.filters.apply({
gender: ['men'],
price: { min: 100, max: 500 },
}, { sort: 'price-asc' });

qumra.filters.rerender()Promise<void>

Re-render sections with current filters (no state change).

await qumra.filters.rerender();

qumra.filters.get(name?)

Get the current filter state (no network request).

// Get all filters
const all = qumra.filters.get();
// { gender: ['men', 'women'], price: { min: 100, max: 500 } }

// Get a specific filter
const gender = qumra.filters.get('gender');
// ['men', 'women']

// Returns null if filter doesn't exist
qumra.filters.get('color'); // null

qumra.filters.getQuery()

Get the current search query.

const q = qumra.filters.getQuery(); // "shoes"

qumra.filters.getSort()

Get the current sort order.

const sort = qumra.filters.getSort(); // "price-asc"

qumra.filters.has(name, value?)

Check if a filter or specific value is active.

qumra.filters.has('gender');          // true
qumra.filters.has('gender', 'men'); // true
qumra.filters.has('color'); // false

qumra.filters.hasAny()

Check if any filters are currently active.

if (qumra.filters.hasAny()) {
showClearButton();
}

qumra.filters.count()

Count the number of active filter groups.

qumra.filters.count(); // 2

Loading State

While the section is being fetched, the target element gets a data-qumra-loading="true" attribute:

[data-qumra-loading="true"] {
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s;
}

Events

EventWhenPayload
collection:filteredAfter any filter change{ filters, query }
collection:sortedAfter sort(){ sort }
collection:resetAfter clear()
section:renderingBefore section fetch starts{ widgetKey }
section:renderedAfter section DOM swap{ widgetKey, html }
qumra.on(qumra.EVENTS.COLLECTION.FILTERED, (payload) => {
updateFilterUI(payload.data.filters);
});

qumra.on(qumra.EVENTS.SECTION.RENDERED, (payload) => {
// Re-initialize any JS inside the new HTML
console.log('Rerendered:', payload.data.widgetKey);
});

Full Example

<div class="collection-page">
<!-- Filters -->
<aside>
<h3>Color</h3>
<label>
<input type="checkbox" data-qumra-filter="color" data-value="red"> Red
</label>
<label>
<input type="checkbox" data-qumra-filter="color" data-value="blue"> Blue
</label>

<h3>Price</h3>
<input type="number" id="min-price" placeholder="Min">
<input type="number" id="max-price" placeholder="Max">
<button id="apply-price">Apply</button>

<button data-qumra-filter-clear>Clear All</button>
</aside>

<!-- Products — auto-rerenders on filter change -->
<main data-qumra-section="products-grid">
{% widget "products-grid" %}
</main>
</div>

<style>
[data-qumra-loading="true"] {
opacity: 0.5;
pointer-events: none;
transition: opacity 0.2s;
}
</style>

<script>
// Price range (needs manual binding for two inputs)
document.getElementById('apply-price').addEventListener('click', async () => {
const min = +document.getElementById('min-price').value;
const max = +document.getElementById('max-price').value;
await qumra.filters.range('price', { min, max });
// Section auto-rerenders — no extra code needed
});
</script>

Zero JavaScript needed for the checkbox filters — data-qumra-filter and data-qumra-section handle everything.